
A PHP ökoszisztémájában jelenleg két csatlakozó áll rendelkezésre a Tarantool szerverrel való munkához - ez a hivatalos PECL kiterjesztés , amely C nyelven készült, és , amely PHP nyelven íródott. Én vagyok az utóbbi szerzője.
Ebben a cikkben szeretném megosztani a két könyvtár teljesítménytesztelésének eredményeit, és megmutatni, hogyan lehet minimális kódmódosításokkal 3-5%-os teljesítménynövekedést elérni (szintetikus teszteken!).
Mit fogunk tesztelni?
A fent említett szinkron csatlakozókat fogjuk tesztelni, amelyeket aszinkron, párhuzamosan és aszinkron-párhuzamosan futtatunk. 🙂 Ezenkívül nem akarjuk megérinteni maguknak a csatlakozóknak a kódját. Jelenleg elérhető néhány kiterjesztés, amelyek lehetővé teszik a kívánt eredmény elérését:
- ― egy magas teljesítményű aszinkron keretrendszer PHP-hez. Olyan internetes óriások használják, mint az Alibaba és a Baidu. A 4.1.0-ás verziótól kezdve egy varázslatos metódus jelent meg SwooleRuntime::enableCoroutine(), amely lehetővé teszi, hogy "egy kódsorral átalakítsuk a szinkron hálózati könyvtárakat PHP-ben aszinkronokká".
- Async ― nemrégiben még ígéretes kiterjesztés volt aszinkron munkára PHP-ben. Miért nemrégiben? Sajnos, számomra ismeretlen okból az író törölte a repozitóriumot, és a projekt jövője homályos. Használnunk kell a forkok közül. Mint a Swoole, ez a kiterjesztés lehetővé teszi, hogy egy egyszerű mozdulattal átváltsunk az aszinkron működésre úgy, hogy a standard TCP és TLS folyamatokat aszinkron verzióikra cseréljük. Ezt az "async.tcp = 1«.
- ― egy viszonylag új kiterjesztés a jól ismert Joe Watkins-tól, aki olyan könyvtárakat írt, mint a phpdbg, apcu, pthreads, pcov, uopz. A kiterjesztés API-t biztosít a párhuzamos munkához PHP-ben, és úgy van pozicionálva, mint a pthreads helyettesítője. A könyvtár jelentős korlátozása, hogy csak a ZTS (Zend Thread Safe) verzióval működik PHP-ben.
Hogyan fogjuk tesztelni?
Indítunk egy Tarantool példányt a naplóelőre írás kikapcsolásával (wal_mode = none), és megnövelt hálózati pufferral (readahead = 1 * 1024 * 1024). Az első opció kizárja a lemezzel való munkát, a második lehetővé teszi, hogy több lekérdezést olvassunk ki a rendszer pufferéből, ezáltal minimalizálva a rendszerhívások számát.
A benchmark that works with data (insertion, deletion, reading, etc.) will create (or recreate) a memtx space before the benchmark starts, where the values of the primary index are generated by a sequence of ordered integers.
The DDL of the space looks like this:
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}})If necessary, before starting the benchmark, the space is filled with 10,000 tuples of the form
{id, "tuplе_"}Access to the tuples is done by a random key value.
The benchmark itself is a single request to the server, executed 10,000 times (revolutions), which are in turn executed in iterations. The iterations are repeated until all time deviations among 5 iterations fall within an acceptable margin of error of 3%*. After that, the average result is taken. There is a 1-second pause between iterations to prevent the CPU from throttling. The Lua garbage collector is disabled before each iteration and forced to run after it is completed. The PHP process is launched only with the extensions necessary for the benchmark, with output buffering enabled and garbage collection disabled.
* The number of revolutions, iterations, and error threshold can be changed in the benchmark settings.
Test environment
The results published below were obtained on a MacBook Pro (2015), operating system – Fedora 30 (kernel version 5.3.8-200.fc30.x86_64). Tarantool was run in Docker with the parameter "--network host".
Package versions:
Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (built: Oct 22 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ patch for 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Unfortunately, the official connector does not work with PHP version > 7.2. To compile and run the extension on PHP 7.3, a patch had to be used .
Eredmények
Synchronous mode
Tarantool’s protocol uses a binary format for serializing messages. In the PECL connector, serialization is hidden deep within the library and it is not possible to influence the encoding process from userland code . A tiszta PHP-s csatlakozó szemben testreszabási lehetőséget biztosít a kódolási folyamatban a standard kódoló kiterjesztésének bővítésével vagy a saját megvalósítás használatával. Alapértelmezetten két kódoló érhető el, az egyik alapja a (hivatalos MessagePack PECL kiterjesztés), a másik pedig a (tisztán PHP-ban).
A csatlakozók összehasonlítása előtt mérjük meg a MessagePack kódolók teljesítményét a PHP csatlakozó számára, és a további tesztekben azt fogjuk használni, amelyik a legjobb eredményt mutatja:

Bár a PHP verzió (Pure) sebességben elmarad a PECL kiterjesztéstől, a valós projektekben mindenképpen azt ajánlanám használni, , mert a hivatalos MessagePack kiterjesztésben a formátum specifikációja csak részben van megvalósítva (például nincs támogatás a felhasználói adattípusokhoz, amely nélkül nem tudja használni a Decimal-t — az új adattípus, amely a Tarantool 2.3-ban jelent meg) és számos más, (beleértve a PHP 7.4-tel kapcsolatos kompatibilitási problémákat). A projekt egészében véve elhagyatottnak tűnik.
Tehát mérjük meg a csatlakozók teljesítményét szinkron módban:

Ahogy a grafikonon látható, a PECL csatlakozó (Tarantool) jobb teljesítményt mutat a PHP csatlakozóval (Client) szemben. De ez nem is meglepő, figyelembe véve, hogy az utóbbi, azon kívül, hogy egy lassabb nyelven van megvalósítva, tulajdonképpen több munkát végez: minden hívásnál új objektum jön létre Kérés és Válasz (Select esetén — még a Kritériumok, Update/Upsert esetén pedig — Műveletek), külön entitások Connection, Csomagoló és Kezelő szintén hozzáadnak egy plusz terhet. Nyilvánvaló, hogy a rugalmasságért fizetni kell. Ugyanakkor a PHP interpreter általánosságban jó teljesítményt mutat, bár van különbség, az elhanyagolható, és valószínűleg még kisebb lesz a PHP 7.4 preloading használatakor, nem is beszélve a PHP 8 JIT-ről.
Tovább lépve. A Tarantool 2.0-ban megjelent az SQL támogatás. Próbáljuk ki a Select, Insert, Update és Delete műveletek végrehajtását SQL protokollal, és hasonlítsuk össze az eredményeket noSQL (binaris) megfelelőikkel:

Az SQL eredmények nem túl lenyűgözőek (emlékeztetnék, hogy még mindig a szinkron módot teszteljük). Azonban nem érdemesene kétségbe esni emiatt, hiszen az SQL támogatás továbbra is aktív fejlesztés alatt áll (viszonylag nem régiben például a támogatás megjelent ), és a lista alapján , az SQL motor a jövőben számos optimalizációra számít.
Async
Nos, nézzük meg most, hogyan tud az Async kiterjesztés segíteni nekünk a fenti eredmények javításában. Aszinkron programok írásához az API-t korutinok (coroutines) alapján biztosítja, és ezt fogjuk kihasználni. Tapasztalataink szerint az optimális korutinok száma a mi környezetünkben 25:

Felosszuk 10,000 műveletet 25 korutinra, és megnézzük, mi történt:

A másodpercenkénti műveletek száma több mint háromszorosára nőtt a következő esetében: !
Sajnálatos módon a PECL csatlakozó nem indult el az ext-async-nal.
De mi a helyzet az SQL-lel?

Ahogy látjuk, aszinkron módban a bináris protokoll és az SQL közötti különbség a tűrési határokon belül van.
Swoole
Újra megkeressük az optimális korutinok számát, most már a Swoole számára:

Maradjunk a 25-nél. Ismételni fogjuk ugyanazt a trükköt, mint az Async kiterjesztésnél – 10,000 műveletet elosztunk 25 korutin között. Emellett egy másik tesztet is hozzáadunk, amelyben az egész munkát 2 folyamatra osztjuk (vagyis minden folyamat 5,000 műveletet hajt végre 25 korutinban). A folyamatokat a következő segítségével fogjuk létrehozni: SwooleProcess.
Results:

A Swoole egy kicsit alacsonyabb eredményt mutat egy folyamatban indítva, de 2 folyamattal a helyzet drámaian megváltozik (a 2-es szám nem véletlenül lett választva, a gépem pontosan 2 folyamatnál mutatta a legjobb eredményt).
Egyébként az Async kiterjesztésben is van API a folyamatok kezelésére, de itt nem vettem észre semmilyen különbséget az egy vagy több folyamatban futtatott benchmarkok között (lehet, hogy valamit félreértettem).
SQL vs bináris protokoll:

Ahogy az Async esetében is, az aszinkron módban a bináris és SQL műveletek közötti különbséget kiegyenlíti.
Parallel
Mivel a Parallel kiterjesztés nem a korutinokkal, hanem a szálakkal foglalkozik, mérjük meg az optimális párhuzamos szálak számát:

Ez 16 a gépem esetében. Indítsuk el a csatlakozók benchmarkjait 16 párhuzamos szálon:

Ahogy látjuk, az eredmény még jobban teljesít, mint az aszinkron kiterjesztésekkel (kivéve a Swoole-t, amely 2 folyamatban fut). Ne feledjük, hogy a PECL csatlakozó helyén az Update és Upsert műveleteknél üres a hely. Ennek oka, hogy ezek a műveletek hibával megszakadtak - nem tudom, hogy az ext-parallel, az ext-tarantool vagy mindkettő hibájából adódik.
Most hasonlítsuk össze az SQL teljesítményét:

Észrevette a hasonlóságot a szinkron módban futó csatlakozók grafikonjával?
Mind együtt
Végül összegezzük az összes eredményt egy grafikonra, hogy lássuk a tesztelt kiterjesztések általános képét. A grafikonra csak egy új tesztet adunk hozzá, amit még nem végeztünk el - egyesítjük az Async korutinokat párhuzamosan a Parallel* segítségével. Az említett kiterjesztések integrálása már a szerzők, de konszenzus még mindig nem született, ezt magunknak kell megoldanunk.
* A Swoole korutinok Parallel-lel való indítása nem működött, úgy tűnik, hogy ezek a kiterjesztések nem kompatibilisek.
Tehát, a végső eredmények:

Összegzésként
Szerintem az eredmények igazán figyelemre méltóak, és valamiért biztos vagyok benne, hogy ez még nem a végső határ! Hogy szükség van-e erre egy valós projektben, csakis rajtatok múlik, annyit mondhatok, hogy számomra ez egy érdekes kísérlet volt, amely lehetővé tette, hogy felmérjem, mennyit lehet 'kicsikarni' a szinkron TCP-kapcsolóból minimális erőfeszítéssel. Ha van ötleted a benchmarkok javítására - örömmel várom a pull requestedet. Az összes kód és a futtatási utasítások az alábbiakban találhatóak .
Forrás: habr.com
