Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel

V ekosystéme PHP v súčasnosti existujú dve rozhrania na prácu so serverom Tarantool - oficiálne rozšírenie PECL tarantool/tarantool-php, napísané v C, a tarantool-php/client, napísané v PHP. Som autorom toho druhého.

V tomto článku by som sa chcel podeliť o výsledky testovania výkonu oboch knižníc a ukázať, ako môžeme dosiahnuť 3-5% zlepšenie výkonu pri minimálnych zmenách v kóde (na syntetických testoch!).

Čo budeme testovať?

Budeme testovať vyššie spomenuté synchronné rozhrania, spustené asynchrónne, paralelne a asynchrónne-paralelne. 🙂 Taktiež sa nechceme dotýkať kódu samotných rozhraní. V súčasnosti je k dispozícii niekoľko rozšírení, ktoré umožňujú dosiahnuť požadovaný výsledok:

  • Swoole ― vysoce výkonný asynchrónny rámec pre PHP. Používa ho niekoľko internetových gigantov ako Alibaba a Baidu. Od verzie 4.1.0 sa objavila čarovná metóda SwooleRuntime::enableCoroutine(), ktorá "jedným riadkom kódu pretransformuje synchronné sieťové knižnice PHP na asynchrónne".
  • Async ― donedávna veľmi perspektívne rozšírenie pre asynchrónnu prácu v PHP. Prečo donedávna? Bohužiaľ, z neznámeho dôvodu autor odstránil repozitár a budúcnosť projektu je nejasná. Budeme musieť využiť jednu z forkov. Rovnako ako Swoole, toto rozšírenie umožňuje jednoduchým spôsobom premeniť synchronnosť na asynchrónnosť výmenou štandardnej implementácie TCP a TLS prúdov za ich asynchrónne verzie. Robí sa to cez možnosť „async.tcp = 1«.
  • Parallel ― pomerne nové rozšírenie od známeho Joea Watkinsa, autora takých knižníc ako phpdbg, apcu, pthreads, pcov, uopz. Rozšírenie poskytuje API pre viaceré vlákna v PHP a je poziciované ako náhrada za pthreads. Významným obmedzením knižnice je, že funguje len s verziou PHP ZTS (Zend Thread Safe).

Ako budeme testovať?

Spustíme inštanciu Tarantoolu s vypnutým žurnálovým predzápisom (wal_mode = none) a zvýšeným sieťovým bufferom (readahead = 1 * 1024 * 1024). Prvá možnosť vylúči prácu s diskom, druhá umožní čítať viac požiadaviek z bufferu operačného systému a tým minimalizovať počet systémových volaní.

Pre benchmarkov, ktoré pracujú s údajmi (vkladanie, odstraňovanie, čítanie atď.), sa pred spustením benchmarku vytvorí (alebo znovu vytvorí) memtx-špeciál, v ktorom sa hodnoty primárneho indexu generujú generátorom usporiadaných hodnôt celých čísel (sequence).
DDL špeciálu vyzerá takto:

space = box.schema.space.create(config.space_name, {id = config.space_id, temporary = true})
space:create_index('primary', {type = 'tree', parts = {1, 'unsigned'}, sequence = true})
space:format({{name = 'id', type = 'unsigned'}, {name = 'name', type = 'string', is_nullable = false}})

Ak je to potrebné, pred spustením benchmarku sa špeciál naplní 10 000 kópiami typu

{id, "tuplе_<id>"}

Prístup k kópiám je zabezpečený pomocou náhodnej hodnoty kľúča.

Sám benchmark predstavuje jednotkový dotaz na server, ktorý sa vykonáva 10 000-krát (revolúcií), ktoré sa vykonávajú v iteráciách. Iterácie sa opakujú, kým všetky odchýlky v čase medzi 5 iteráciami nebudú v rámci prípustnej chyby 3 %*. Po tom sa vezme priemerný výsledok. Medzi iteráciami je pauza 1 sekunda, aby procesor neprešiel do režimu zníženia výkonu. Zbieranie odpadu Lua je vypnuté pred každou iteráciou a nútene spustené po jej dokončení. PHP proces sa spúšťa iba s potrebnými rozšíreniami pre benchmark, s povoleným vyrovnávacím pamäťou a vypnutým zbieracím mechanizmom.

* Počet revolúcií, iterácií a prahová chyba je možné zmeniť v nastaveniach benchmarku.

Testovacie prostredie

Nižšie uvedené výsledky boli získané na MacBookPro (2015), operačný systém — Fedora 30 (verzia jadra 5.3.8-200.fc30.x86_64). Tarantool bol spustený v Dockeri s parametrom "--network host".

Verzie balíkov:

Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (postavené: 22. októbra 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ oprava pre 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3

* Žiaľ, oficiálny konektor nefunguje s verziou PHP > 7.2. Aby som skompiloval a spustil rozšírenie na PHP 7.3, musel som použiť patchom.

Výsledky

Súčasný režim

Protokol Tarantool používa binárny formát MessagePack na serializáciu správ. V konektore PECL je serializácia skrytá hlboko v vrstve knižnice a ovplyvniť proces kódovania z userland-kódu sa nedá.Konektor na čisto PHP poskytuje možnosť prispôsobenia procesu kódovania rozšírením štandardného kódovača alebo možnosťou použitia vlastnej implementácie. Z krabice sú k dispozícii dvaja kódovače, jeden založený na msgpack/msgpack-php (oficiálne rozšírenie MessagePack PECL), druhý — na rybakit/msgpack (na čisto PHP).

Pred porovnaním konektorov zmerajme výkon MessagePack kódovačov pre PHP konektor a v ďalších testoch budeme používať ten, ktorý ukáže najlepší výsledok:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Hoci PHP verzia (Pure) zaostáva rozšíreniu PECL z hľadiska rýchlosti, v reálnych projektoch by som aj tak odporúčal používať práve rybakit/msgpack, pretože v oficiálnom rozšírení MessagePack je špecifikácia formátu implementovaná iba čiastočne (napríklad neexistuje podpora používateľských typov údajov, bez ktorých nebudete môcť použiť Decimal — nový typ údajov, predstavený v Tarantool 2.3) a má niekoľko ďalších problémom (vrátane problémov s kompatibilitou s PHP 7.4). A celkovo projekt vyzerá zanedbane.

Takže zmerajme výkon konektorov v synchronnom režime:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Ako je vidieť z grafu, konektor PECL (Tarantool) ukazuje lepší výkon v porovnaní s konektorom na PHP (Client). Ale to nie je prekvapujúce, vzhľadom na to, že posledný, okrem toho, že je implementovaný na pomalšom jazyku, vykonáva v podstate viac práce: pri každom volaní sa vytvára nový objekt Požiadavka a Response (v prípade Select — ešte aj Criteria, a v prípade Update/Upsert — Operations), samostatné entity Pripojenie, Packer a Handler tiež pridávajú overhead. Je zrejmé, že za flexibilitu sa platí. Avšak, celkovo PHP interpretátor ukazuje dobrý výkon, aj keď rozdiel existuje, je nevýznamný a možno bude ešte menší pri použití preloading v PHP 7.4, nehovoriac už o JIT v PHP 8.

Posúvame sa ďalej. V Tarantool 2.0 sa objavila podpora SQL. Skúsme vykonať operácie Select, Insert, Update a Delete pomocou SQL protokolu a porovnať výsledky s noSQL (binárnymi) ekvivalentmi:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Výsledky SQL nie sú veľmi pôsobivé (pripomínam, že stále testujeme synchronný režim). Avšak, nemal by som byť z toho znepokojený predčasne, podpora SQL je stále v aktívnom vývoji (relatívne nedávno, napríklad, sa pridala podpora prepared statements) a, súdiac podľa zoznamu issues, SQL engine čaká na množstvo optimalizácií.

Async

No, pozrime sa, ako rozšírenie Async môže pomôcť zlepšiť naše výsledky vyššie. Na písanie asynchrónnych programov rozšírenie poskytuje API založené na korutinách (coroutines), ktoré využijeme. Skúsenostne zistíme, že optimálny počet korutín pre naše prostredie je 25:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Rozdeľujeme 10,000 operácií medzi 25 korutín a sledujeme, čo to prinieslo:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Počet operácií za sekundu vzrástol viac ako trojnásobne pre tarantool-php/client!

Žiaľ, konektor PECL sa nespustil s ext-async.

A čo SQL?

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Ako vidíte, v asynchrónnom režime sa rozdiel medzi binárnym protokolom a SQL dostal do medzí nepresnosti.

Swoole

Opäť zisťujeme optimálny počet korutín, tentokrát pre Swoole:
Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Zostaneme na 25. Opakujeme ten istý trik ako s rozšírením Async – rozdelíme 10,000 operácií medzi 25 korutín. Okrem toho pridáme ďalší test, v ktorom rozdelíme všetku prácu na 2 procesy (teda každý proces vykoná 5,000 operácií v 25 korutínach). Procesy budú vytvorené pomocou SwooleProcess.

Výsledky:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Swole ukazuje o niečo nižší výsledok v porovnaní s Async pri spustení v jednom procese, ale pri 2 procesoch sa situácia radikálne mení (číslo 2 bolo vybrané nie náhodou, na mojom zariadení práve 2 procesy ukázali najlepší výsledok).

Mimochodom, v rozšírení Async je tiež API na prácu s procesmi, ale tam som si nevšimol žiadny rozdiel medzi spustením benchmarkingov v jednom alebo viacerých procesoch (nie je vylúčené, že som niekde urobil chybu).

SQL vs binárny protokol:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Rovnako ako s Async, rozdiel medzi binárnymi a SQL operáciami sa vyrovnáva v asynchrónnom režime.

Parallel

Keďže rozšírenie Parallel nie je o korutinách, ale o vláknach, odmerajme optimálny počet paralelných vlákien:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Ten je 16 na môjho zariadení. Spustíme benchmarky konektorov na 16 paralelných vláknach:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Ako vidíte, výsledok je dokonca lepší ako s asynchrónnymi rozšíreniami (nepočítajúc Swoole spustené na 2 procesoch). Všimnite si, že pre konektor PECL je na mieste operácií Update a Upsert prázdno. To súvisí s tým, že tieto operácie spadli s chybou – neviem, či za to môže ext-parallel, ext-tarantool, alebo oboje.

Teraz porovnajme výkon SQL:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel
Všimli ste si podobnosť s grafom pre konektory spustené synchronne?

Všetko dokopy

A nakoniec zhrnieme všetky výsledky do jedného grafu, aby sme videli celkový obraz testovaných rozšírení. Na graf pridáme len jeden nový test, ktorý sme ešte nerobili — spustíme Async korutiny paralelne pomocou Parallel*. Myšlienka integrácie vyššie spomenutých rozšírení už bola diskutovaná autormi, avšak konsenzus nebol dosiahnutý, budeme musieť spraviť to sami.

* Spustiť Swoole korutiny s Parallel sa nepodarilo, zdá sa, že tieto rozšírenia nie sú kompatibilné.

Takže, finálne výsledky:

Zrychlujeme PHP konektory pro Tarantool pomocí Async, Swoole a Parallel

Na záver

Myslím, že výsledky sú celkom potešujúce, a neviem prečo, ale som presvedčený, že to ešte nie je konečný limit! Či to potrebujete vo svojom skutočnom projekte, rozhodnete sa sami, poviem len, že pre mňa to bol zaujímavý experiment, ktorý umožnil posúdiť, koľko sa dá „vytiahnuť“ zo synchronného TCP konektora s minimálnym úsilím. Ak máte nápady na vylepšenie benchmarkov, rád posúdim váš pull request. Celý kód s pokynmi na spustenie a výsledkami je publikovaný v samostatnom repozitári.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster