CPython bye bye, hello PyPy! V Mergadu jsme před časem zmigrovali náš backend na nestandardní implementaci jazyka Python – PyPy. Rozhodnutí úplně opustit CPython přišlo jako reakce na stále se zvyšující nároky na výkon naší aplikace. Přechod na PyPy přitom nebyl ani z daleka tak komplikovaný, jak jsme se z počátku obávali a vyřešil mnoho problémů nejen s rychlostí aplikace Mergado.

O Mergadu a jeho evoluci#

Pokud nemáte tušení, k čemu slouží aplikace Mergado, představte si, že vlastníte e-shop, ve kterém prodáváte například PC komponenty. Váš e-shop obsahuje stovky až tisíce nejrůznějších produktů, které jsou trvale k dispozici ve vaší databázi. Snažíte se svým sortimentem konkurovat ostatním e-shopům, a protože o vašem e-shopu ještě téměř nikdo neslyšel, chtěli byste inzerovat produkty ve vyhledávačích zboží. V Česku patří mezi nejpopulárnější např. Heureka a Zboží.cz a proto se rozhodnete posílat svá data do Heureky s tím, že vás možná ani nenapadne, že někdy třeba budete chtít data posílat také do vyhledávače jiného, třeba i zahraničního.

Protože vyhledávače zboží data o produktech přijímají ve specifickém datovém formátu, tak doufáte, že zrovna ten e-shopový systém, který používáte, umí informace o vašem zboží exportovat ve formě, kterou vyhledávač – v tomto případě Heureka.cz – konkrétně potřebuje a umí zpracovat. Pokud neumí, umí jen trochu, nebo data o produktech už v samotné databázi vašeho e-shopu dávají tak málo smysl, že je Heureka.cz odmítne inzerovat, musíte situaci nějak vyřešit. Každého vývojáře by v takové situaci nejspíše jako první napadlo tento úkol vyřešit napsáním si vlastní komponenty – ta by validovala a exportovala data z databáze do XML přesně podle specifikace daného vyhledávače zboží. Z pohledu provozovatele e-shopu se problém ještě prohlubuje – musí hledat řešení u autorů jím používaného e-shop systému, případně musí shánět externí programátory, kteří by mu takovou komponentu naprogramovali na míru, za což si také nechá dát odpovídající finanční odměnu. Problém je, že toto řešení je pro většinu majitelů e-shopů mnohdy drahá a nejistá záležitost.

Aplikace Mergado je právě výsledkem snahy vytvořit spolehlivé a co možná nejuniverzálnější řešení pro export dat do mnoha českých i zahraničních vyhledávačů zboží. Řešení, které by e-shopy a agentury mohly využívat pro správu produktových dat, a to bez shánění dalších programátorů. Mezi hlavní funkce Mergada patří převodník mezi formáty různých vyhledávačů zboží a reklamních systémů. Poskytuje však spoustu dalších užitečných funkcí, umožňuje například hromadně upravovat produkty s pomocí definovatelných výběrů a pravidel; dále například pomáhá odhalit příliš drahé produkty, které jsou u konkurence levnější, nebo umí vykreslovat pokročilejší analýzy vaší inzerce.

Nyní už je jasné, kdo je koncovým uživatelem Mergada – jsou to e-shopy. Vstupním předpokladem do Mergada je pouze poskytnutí XML feedu obsahující informace o zboží daného obchodu. Mergado má navíc svůj vlastní XML formát; zároveň však podporuje přes padesát dalších formátů, mezi kterými dokáže data konvertovat (dalo by se říct, že Mergado je takové velmi univerzální jednosměrné API, které je schopno hovořit několika jazyky). Úkolem Mergada je kontinuálně aktualizovat svou produktovou databázi, provádět nadefinované úpravy a exportovat data všech v něm přítomných e-shopů do různých vyhledávačů zboží. Největší feedy v Mergadu obsahují i přes 300 tisíc produktů, jde tedy o výpočetně náročnou práci. Aby Mergado zvládlo zpracovávat tak velký a narůstající objem dat, prošlo postupnou evolucí – od malé aplikace uchované na jednom serveru s 512MB RAM až po velkou, distribuovanou aplikaci poháněnou Celery frameworkem, která běží v několika instancích na různých strojích.

Při vývoji Mergada nastalo mnoho okamžiků, kdy rychlost zpracování produktových feedů nedostačovala požadavkům klientů a bylo potřeba buď navýšit výkon nakoupením potřebného hardware nebo provést refaktoring některých klíčových komponent v aplikaci. Nakoupení hardwaru je pro většinu firem z krátkodobého hlediska levnější, proto toto řešení firmy preferují a často se snaží vyhýbat rozsáhlejšímu refaktoringu, byť ten se z dlouhodobého hlediska většinou vyplatí mnohem více.

Architektura softwaru a Python#

Pokud se na software díváme z pohledu počítačové architektury, lze říci, že se skládá z vrstev. Příkladem takové vrstvy může být například hardware nebo operační systém, kde hardware je logicky "níže" než operační systém. Podle vrstev lze rozdělit i aplikaci Mergado. Tu tvoří na nejnižší úrovni hardware sestaven z několika serverů. Na vyšší úrovni je pak operační systém, databáze a další komponenty, se kterými Mergado operuje. Nejvyšší vrstvu tvoří samotná aplikace, která je naprogramovaná především v jazycích Python a PHP. Každá vrstva nebo její část může představovat úzké místo v aplikaci, které brzdí rychlost celé aplikace. Například pokud nestíhá databáze kvůli špatně navrženému schématu, nákup sebelepšího hardware přinese minimální až nulový nárůst výkonu. Může se ovšem stát, že největší brzdu v aplikaci způsobuje vzájemná spolupráce dvou vrstev, příkladem může být neefektivní práce s relační databází, kde aplikace ponechává spojení příliš dlouho otevřená, tím brzdí databázi a ta pak reaguje s ještě větším zpožděním nebo spojení rovnou zabíjí.

Vrstvou v architektuře Mergada je i interpreter jazyka Python, ten se stará o překlad zdrojového kódu napsaném v Pythonu do bajtkódu, což je posloupnost instrukcí, která pak může být interpretována počítačem. Pro jazyk Python existuje celá řada různých implementací, výchozí (a zároveň i nejpopulárnější) je CPython. Mezi další velmi populární implementaci patří PyPy, která podle srovnávacích testů přináší v některých případech i desetinásobné zrychlení v porovnání s výchozí implementací. PyPy je na rozdíl od CPythonu Just-In-Time (JIT) kompilátor, který překládá část programu do bajtkódu až ve chvíli, kdy je to opravdu potřeba. JIT kompilátory si navíc překlad cachují, čímž mohou přinést výrazný nárůst výkonu, typicky ve for a while cyklech. Velmi patrný rozdíl v rychlosti mezi CPythonem a PyPy je znázorněn na jednoduchém příkladu níže.

# CPython 2.7.10
%timeit sum(range(1000000))
# 10 loops, best of 3: 23.5 ms per loop

# PyPy 5.3.1
%timeit sum(range(1000000))
# 100 loops, best of 3: 2.33 ms per loop

V jednu krásnou středu, kdy jsme zrovna měli po releasu nové verze, jsme v Mergadu rozpoutali diskuzi – jak bychom mohli urychlit zpracování XML souborů, které se provádí při každém importu produktového feedu e-shopu, a ukládání do naší databáze? Mergado totiž používá vlastní XML parser napsaný v čistém Pythonu, který dokáže přečíst do jisté míry i nevalidní XML. Nevýhodou našeho parseru je, že je velmi pomalý, alespoň např. ve srovnání s open-source knihovnou lxml. A tak nás napadlo, že bychom mohli porovnat rychlost parseru při použití standardní a nestandardní implementace jazyka Python. Protože jsme při instalaci PyPy nenarazili na téměř žádné problémy a náš uspěchaný test ukázal asi čtyřnásobné zrychlení, rozhodli jsme se s velkým očekáváním zkusit rozjet v PyPy nejen náš XML parser, ale i celý backend aplikace Mergado. I přes desítky závislostí (například Celery, Gevent, Flask, SQLAlchemy, Python-Requests a mnoho dalších) nebyl s instalací téměř žádný problém a na straně aplikace vlastně nakonec nebyla potřeba žádná úprava kódu. Problémy se objevily pouze u knihovny PyCrypto a driveru MySQL-Python, který jsme nahradili knihovnou PyMySQL.

Testování výkonnosti PyPy#

V tabulce je srovnání rychlosti obou implementací jazyka Python, které jsme provedli na čtyřech úlohách, jenž Mergado provádí v pravidelných intervalech a jsou tedy s ohledem na zrychlení aplikace nejvíc relevantní. Test proběhl na e-shopu s přibližně deseti tisíci položkami.

Úloha CPython PyPy
Analýza XML feedu 157,6 s 57,3 s
Import z XML do databáze 93,9 s 50,1 s
Aplikace pravidel (úprava produktů) 106,5 s 93,6 s
Export z databáze do XML 82,5 s 58,4 s

Na grafu níže je znázorněno, jak se přechod na PyPy projevil v provozu na produkci (s releasem na produkci jsme začali v 16 hodin a trval přibližně dvě hodiny). Osa X reprezentuje hodinu, ve kterou byla rychlost měřena a na ose Y je zanesen počet exportů/položek, které v danou hodinu byly zpracovány. Z obrázku je tedy patrné více než trojnásobné zrychlení.

Přehled rozložení exportů za poslední 24 hodin

U jednoho většího e-shopu, který obsahuje okolo 100 tisíc produktů, vzrostla rychlost exportu produktů z databáze do XML dokonce téměř 100 násobné (z 10 hodin na 6 minut). Jde samozřejmě pouze o extrémní příklad, ale mohlo by být zajímavé diagnostikovat, proč došlo k tak velkému zrychlení a kde přesně představuje CPython při exportu tohoto konkrétního XML feedu tak úzké místo.

Závěr#

Nelze říci, že by byl přechod na nestandardní implementaci jazyka Python úplně bezproblémový. Rozdíl oproti standardní implementaci je například (v některých případech) daleko vyšší spotřeba paměti, o které se v Python komunitě hodně diskutuje a i přes výrazné zlepšení od prvotních verzí stále může představovat velký problém. Implementace garbage collectoru v PyPy totiž používá odlišné strategie pro uvolnění paměti. Spotřebu paměti lze částečně snížit konfigurací garbage collectoru, nám se však touto konfigurací nepovedlo dosáhnout žádného výrazného zlepšení. Aby naše aplikace měla v provozu dostatek paměti, museli jsme nakonec na jednom serveru navýšit paměť RAM téměř trojnásobně.

I přes vysokou spotřebu paměti se přechod na PyPy velmi vyplatil, jelikož jsme díky několikanásobně rychlejšímu zpracování periodických úloh mohli snížit počet jader, s kterými naše servery před tím operovaly. Nepopíratelnou výhodou byla téměř nulová nutnost refaktoringu a celkově velmi malá režie ze strany vývoje.

Autor: Pavel Dedík