Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel

V ekosystému PHP v současnosti existují dva konektory pro práci se serverem Tarantool ― oficiální rozšíření PECL tarantool/tarantool-php, napsané v C, a tarantool-php/client, napsané v PHP. Jsem autorem posledního z nich.

V tomto článku bych se chtěl podělit o výsledky testování výkonu obou knihoven a ukázat, jak pomocí minimálních změn v kódu dosáhnout 3-5% nárůstu výkonu (na syntetických testech!).

Co budeme testovat?

Budeme testovat výše zmíněné synchronní konektory, spuštěné asynchronně, paralelně a asynchronně-paralelně. 🙂 Také nechceme zasahovat do kódu samotných konektorů. V tuto chvíli je k dispozici několik rozšíření, která umožňují dosáhnout požadovaného:

  • Swoole ― vysoce výkonný asynchronní framework pro PHP. Používají ho internetoví giganti jako Alibaba a Baidu. Od verze 4.1.0 se objevil kouzelný metod SwooleRuntime::enableCoroutine(), který "jedním řádkem kódu přetváří synchronní síťové knihovny PHP na asynchronní".
  • Async ― až donedávna velmi nadějné rozšíření pro asynchronní práci v PHP. Proč až donedávna? Bohužel z neznámého důvodu autor odstranil repozitář a další osud projektu je nejasný. Budeme muset využít jednu z forků. Stejně jako Swoole, toto rozšíření umožňuje jedním jednoduchým tahem proměnit synchronní implementaci TCP a TLS na jejich asynchronní verze prostřednictvím volby „async.tcp = 1«.
  • Parallel ― poměrně nové rozšíření od známého Joe Watkinse, autora knihoven jako phpdbg, apcu, pthreads, pcov, uopz. Toto rozšíření poskytuje API pro vícetrýskové zpracování v PHP a je prezentováno jako náhrada pthreads. Značným omezením knihovny je, že funguje pouze s verzí PHP ZTS (Zend Thread Safe).

Jak budeme testovat?

Spustíme instanci Tarantool s vypnutým protokolem předběžného zápisu (wal_mode = none) a zvýšeným síťovým bufferem (readahead = 1 * 1024 * 1024). První možnost vyloučí práci s diskem, druhá umožní číst více dotazů z cache operačního systému a tím minimalizovat počet systémových volání.

Pro benchmarky, které pracují s daty (vkládání, mazání, čtení atd.), bude před spuštěním benchmarku (pře)vytvořen memtx prostor, ve kterém jsou hodnoty primárního indexu vytvářeny generátorem uspořádaných celých čísel (sequence).
DDL prostoru vypadá 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}})

Pokud je to nutné, před spuštěním benchmarku se prostor naplní 10 000 koryty ve tvaru

{id, "tuplе_"}

Přístup ke korytům se provádí pomocí náhodné hodnoty klíče.

Samotný benchmark představuje jediný dotaz na server, který se provádí 10 000krát (revolucí), které jsou prováděny v iteracích. Iterace se opakují, dokud se všechny odchylky v čase mezi 5 iteracemi nenacházejí v rámci přijatelné chyby 3 %*. Po tom se vezme průměrný výsledek. Mezi iteracemi je pauza 1 sekundu, aby se procesoru nedovolilo přejít do throttlingu. Sběrač odpadu Lua je vypnut před každou iterací a je donucen spuštěn po jejím dokončení. PHP proces je spuštěn pouze s nezbytnými rozšířeními pro benchmark, s povoleným vyrovnáváním výstupu a vypnutým sběračem odpadu.

* Počet revolucí, iterací a prahová hodnota chyby mohou být změněny v nastaveních benchmarku.

Testovací prostředí

Níže zveřejněné výsledky byly získány na MacBookPro (2015), operační systém — Fedora 30 (verze jádra 5.3.8-200.fc30.x86_64). Tarantool byl spuštěn v Dockeru s parametrem "--network host".

Verze balíčků:

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

* Bohužel, oficiální konektor nefunguje s verzí PHP > 7.2. Abychom kompilovali a spustili rozšíření na PHP 7.3, museli jsme využít patchem.

Výsledky

Synchronní režim

Protokol Tarantool využívá binární formát MessagePack pro serializaci zpráv. V konektoru PECL je serializace skryta hluboko v útrobách knihovny a ovlivnit proces kódování z userland kódu není možné. Konnektor na čistém PHP naopak poskytuje možnost přizpůsobení procesu kódování rozšířením standardního kodéru nebo možností použití vlastní implementace. K dispozici jsou dva kodérů, jeden založený na msgpack/msgpack-php (oficiální rozšíření MessagePack PECL), druhý - na rybakit/msgpack (na čistém PHP).

Před porovnáním konektorů změříme výkon MessagePack kodérů pro PHP konektor a v dalších testech budeme používat ten, který ukáže nejlepší výsledek:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
I když PHP verze (Pure) zaostává za rozšířením PECL v rychlosti, v reálných projektech bych přesto doporučil použít právě rybakit/msgpack, protože v oficiálním rozšíření MessagePack je specifikace formátu implementována pouze částečně (například není podpora uživatelských typů dat, bez které nebudete moci použít Decimal - nový typ dat představený v Tarantool 2.3) a má řadu dalších problémech (včetně problémů s kompatibilitou s PHP 7.4). Celkově projekt vypadá opuštěně.

Takže změříme výkon konektorů v synchronním režimu:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Jak je vidět z grafu, konektor PECL (Tarantool) vykazuje lepší výkon než konektor na PHP (Client). Ale to není překvapivé, vezmeme-li v úvahu, že poslední, kromě toho, že je implementován v pomalejším jazyce, vykonává v podstatě více práce: při každém volání se vytváří nový objekt Request a Odpověď (v případě Select - ještě i Criteria, a v případě Update/Upsert - Operations), jednotlivé entity Connection, Packer a Handler také přidávají overhead. Je zřejmé, že za flexibilitu se platí. Nicméně, celkově PHP interpret vykazuje dobrý výkon, i když rozdíl je a není zanedbatelný, ale možná bude ještě menší při použití preloading v PHP 7.4, nemluvě o JIT v PHP 8.

Pokračujeme dále. V Tarantool 2.0 byla přidána podpora SQL. Zkusíme provést operace Select, Insert, Update a Delete pomocí SQL protokolu a porovnat výsledky s noSQL (binárními) ekvivalenty:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Výsledky SQL nejsou příliš ohromující (připomínám, že stále testujeme synchronní režim). Nicméně, nenechal bych se tímto trápit předčasně, podpora SQL je stále v aktivním vývoji (nedávno například přibyla podpora prepared statements) a podle seznamu issues, SQL engine v budoucnu čeká řada optimalizací.

Asynchronní

Nuže, podívejme se nyní, jak rozšíření Asynchronní může pomoci zlepšit výše uvedené výsledky. Pro psaní asynchronních programů rozšíření poskytuje API založené na korutinách (coroutines), jichž využijeme. Zjistili jsme, že optimální počet korutin pro naše prostředí je 25:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
„Rozložíme“ 10 000 operací mezi 25 korutin a podíváme se, co jsme získali:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Počet operací za sekundu se zvýšil více než třikrát pro tarantool-php/client!

Bohužel, PECL konektor se nespustil s ext-async.

A co SQL?

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Jak vidíte, v asynchronním režimu je rozdíl mezi binárním protokolem a SQL v mezích chybovosti.

Swoole

Opět zjišťujeme optimální počet korutin, tentokrát pro Swoole:
Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Zůstaneme u čísla 25. Opakujeme stejný trik jako s rozšířením Asynchronní – rozdělíme 10 000 operací mezi 25 korutin. Kromě toho přidáme další test, ve kterém rozdělíme veškerou práci na 2 procesy (to znamená, že každý proces provede 5 000 operací v 25 korutinách). Procesy budou vytvářeny pomocí SwooleProcess.

Výsledky:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Swoole vykazuje mírně nižší výsledek ve srovnání s Asynchronním při spuštění v jednom procesu, ale s 2 procesy se obraz radikálně mění (číslo 2 bylo vybráno nikoli náhodou, na mém stroji skutečně 2 procesy ukázaly nejlepší výsledek).

Mimochodem, v rozšíření Asynchronní je také API pro práci s procesy, nicméně jsem tam nezaznamenal žádný rozdíl mezi spuštěním benchmarků v jednom či více procesech (není vyloučeno, že jsem někde udělal chybu).

SQL vs binární protokol:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Stejně jako u Asynchronního, rozdíl mezi binárními a SQL operacemi se v asynchronním režimu vyrovnává.

Parallel

Protože rozšíření Paralelní není o korutinách, ale o vláknech, změříme optimální počet paralelních vláken:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Ten je na mém stroji 16. Spustíme benchmarky konektorů na 16 paralelních vláknech:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Jak vidíte, výsledek je dokonce lepší než s asynchronními rozšířeními (nepočítaje Swoole spuštěném na 2 procesech). Všimněte si, že pro konektor PECL na místě Update a Upsert operací je prázdno. To je způsobeno tím, že tyto operace selhaly s chybou – nevím, jestli je to vinou ext-parallel, ext-tarantool nebo obou.

Nyní porovnáme výkon SQL:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel
Všimli jste si podobnosti s grafem pro konektory spuštěné synchronně?

Vše dohromady

A nakonec shrneme všechny výsledky do jednoho grafu, abychom viděli celkový obrázek pro testovaná rozšíření. Na graf přidáme pouze jeden nový test, který jsme ještě nedělali — spustíme korutiny Async paralelně pomocí Parallel*. Myšlenka integrace výše zmíněných rozšíření již byla diskutována autory, avšak konsenzus nebyl dosažen, bude to muset udělat sám.

* Spustit korutiny Swoole s Parallel se nepodařilo, zdá se, že tato rozšíření nejsou kompatibilní.

Takže finální výsledky:

Zrychlení PHP konektorů pro Tarantool s využitím Async, Swoole a Parallel

Na závěr

Podle mě jsou výsledky velmi slušné a nějakým způsobem jsem si jistý, že to ještě není konec! Je na vás, zda to potřebujete ve skutečném projektu, řeknu jen, že pro mě to byl zajímavý experiment, který umožnil posoudit, kolik lze "vytěžit" ze synchronního TCP konektoru s minimálními námahy. Pokud máte nějaké nápady na zlepšení benchmarků — rád se podívám na váš pull request. Celý kód s instrukcemi k spuštění a výsledky je publikován v samostatném repozitáře.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster