
V PHP ekosistemu trenutno postoje dva konektora za rad sa Tarantool serverom - to je službena PECL ekstenzija , napisana na C, i , napisana na PHP. Ja sam autor ove druge.
U ovom članku želim podeliti rezultate testiranja performansi oba biblioteke i pokazati kako uz minimalne izmene u kodu možemo postići povećanje performansi od 3-5% (na sintetičkim testovima!).
Šta ćemo testirati?
Testiraćemo gore pomenute sinkrone konektore pokrenute asinhrono, paralelno i asinhrono-paralelno. 🙂 Takođe ne želimo dirati kod samih konektora. Trenutno je dostupno nekoliko ekstenzija koje omogućavaju postizanje željenog:
- ― visokoperformantni asinhroni okvir za PHP. Koriste ga internet divovi kao što su Alibaba i Baidu. Od verzije 4.1.0 uvedena je čarobna metoda SwooleRuntime::enableCoroutine(), koja omogućava „jednom linijom koda da pretvori sinkrone mrežne biblioteke PHP u asinhrone”.
- Async ― do nedavno veoma perspektivna ekstenzija za asinhroni rad u PHP. Zašto do nedavno? Nažalost, iz meni nepoznatih razloga, autor je obrisao repozitorijum, a dalja sudbina projekta je nejasna. Moraćemo da koristimo od forka. Kao i Swoole, ova ekstenzija omogućava da se bez puno truda uključi asinhronost zamenom standardnih implementacija TCP i TLS tokova njihovim asinhronim verzijama. To se postiže putem opcije „async.tcp = 1«.
- ― prilično nova ekstenzija od poznatog Joe Watkins-a, autora biblioteka kao što su phpdbg, apcu, pthreads, pcov, uopz. Ekstenzija pruža API za multi-thread rad u PHP i pozicionira se kao zamena za pthreads. značajno ograničenje biblioteke je to što radi samo sa ZTS (Zend Thread Safe) verzijom PHP.
Kako ćemo testirati?
Pokrenućemo primerak Tarantoola sa isključenim dnevnikom predhodnog zapisa (wal_mode = none) i povećanim mrežnim baferom (readahead = 1 * 1024 * 1024). Prva opcija isključuje rad sa diskom, dok druga omogućava da se više zahteva čita iz bafera operativnog sistema i time minimizuje broj sistemskih poziva.
Za benchmarke, ki delujejo z podatki (vstavljanje, brisanje, branje itd.), se pred začetkom benchmarka (pre)ustvari memtx-prostor, v katerem vrednosti primarnega indeksa ustvarja generator urejenih celih števil (sekvenca).
DDL prostora izgleda tako:
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}})Po potrebi, pred zagonem benchmarka, se prostor napolni z 10.000 tupli v obliki
{id, "tuplе_<id>"}Dostop do tuple se izvaja preko naključne vrednosti ključa.
Sam benchmark predstavlja en sam zahtevek strežniku, ki se izvede 10.000-krat (revolucij), ki se, za redom, izvajajo v iteracijah. Iteracije se ponavljajo, dokler vse odstopanja v času med 5 iteracijami ne bodo v okviru dopustnega odklona 3 %*. Po tem se vzame povprečen rezultat. Med iteracijami je premor 1 sekundo, da se procesor ne prepusti throttlingu. Zbiralnik odpadkov Lua se izklopi pred vsakim iteracijo in se prisilno zažene po njenem zaključku. PHP-postopek se zažene le z nujnimi razširitvami za benchmark, z vklopljeno predpomnilnikom izhoda in izklopljenim zbiralnikom odpadkov.
* Število revolucij, iteracij in prag napake je mogoče spremeniti v nastavitvah benchmarka.
Testno okolje
Objavljeni rezultati so bili narejeni na MacBookPro (2015), operacijski sistem — Fedora 30 (različica jedra 5.3.8-200.fc30.x86_64). Tarantool se je zagnal v Dockerju s parametrom „--network host".
Različice paketov:
Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (zgrajen: 22. okt 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ patch za 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Žal, uradni konektor ne deluje z različico PHP > 7.2. Da bi lahko sestavili in zagnali razširitev na PHP 7.3, je bilo treba uporabiti .
Rezultati
Sinhroni način
Protokol Tarantoola uporablja binarno obliko za serializacijo sporočil. V konektorju PECL je serializacija globoko skrita v nedrih knjižnice in vplivati na postopek kodiranja iz userland-kode . PHP Connector, nasprotno, omogoča prilagoditev procesa kodiranja z razširitvijo standardnega kodirnika ali možnostjo uporabe lastne implementacije. Izključeno sta na voljo dva kodirnika, eden temelji na (uradna razširitev MessagePack PECL), drugi pa na (v čistem PHP).
Pred primerjavo konektorjev bomo izmerili zmogljivost MessagePack kodirnikov za PHP konektor in v nadaljnjih testih bomo uporabili tistega, ki bo pokazal najboljše rezultate:

Čeprav PHP različica (Pure) zaostaja za PECL razširitvijo po hitrosti, bi kljub temu v realnih projektih priporočil uporabo , ker uradna razširitev MessagePack implementira specifikacijo formata le delno (na primer, ne podpira uporabniških tipov podatkov, brez katerih ne boste mogli uporabljati Decimal — novega tipa podatkov, predstavljenega v Tarantool 2.3) in ima vrsto drugih (vključno s težavami s združljivostjo s PHP 7.4). Na splošno pa projekt deluje zapostavljeno.
Torej, izmerimo zmogljivost konektorjev v sinhronem načinu:

Kot je razvidno iz grafa, PECL konektor (Tarantool) kaže boljšo zmogljivost v primerjavi s PHP konektorjem (Client). A to ni presenetljivo, če upoštevamo, da ta, poleg tega, da je implementiran v počasnejšem jeziku, izvaja tudi več dela: pri vsakem klicu se ustvari nov objekt Request in Odgovor (v primeru Select — še tudi Criteria, medtem ko v primeru Update/Upsert — Operations), posamezne entitete Povezava, Packer in Handler tudi dodajajo ovire. Očitno je, da za prilagodljivost plačamo. Vendar pa na splošno PHP interpreter kaže dobro zmogljivost, čeprav razlika obstaja, a je zanemarljiva in bo morda še manjša pri uporabi preloading v PHP 7.4, da ne omenjam JIT v PHP 8.
Nadaljujemo. V Tarantool 2.0 je prišla podpora za SQL. Poskusimo izvesti operacije Select, Insert, Update in Delete z uporabo SQL protokola in primerjati rezultate z noSQL (binarno) ekvivalenti:

Rezultati SQL niso preveč impresivni (spomnim vas, da še vedno testiramo sinhroni način). Vendar pa se ne bi smel prehitro obupati, saj je podpora za SQL še vedno v aktivnem razvoju (pred kratkim, na primer, je bila dodana podpora ) in, soditi po seznamu , SQL engine is expected to undergo a series of optimizations.
Async
Well, let's see how the Async extension can help us improve the above results. To write asynchronous programs, the extension provides an API based on coroutines, which we will use. Through experimentation, we find that the optimal number of coroutines for our environment is 25:

We will "spread out" 10,000 operations across 25 coroutines and see what we get:

The number of operations per second has increased more than 3 times for !
Unfortunately, the PECL connector did not start with ext-async.
And what about SQL?

As you can see, in asynchronous mode, the difference between the binary protocol and SQL is within the margin of error.
Swoole
Again, we discover the optimal number of coroutines, this time for Swoole:

We will stick to 25. We'll repeat the same trick as with the Async extension — distributing 10,000 operations across 25 coroutines. In addition, we'll add another test where we divide all the work into 2 processes (meaning each process will execute 5,000 operations in 25 coroutines). The processes will be created using SwooleProcess.
Rezultati:

Swole shows a slightly lower result compared to Async when running in a single process, but with 2 processes, the picture changes dramatically (the number 2 is not chosen randomly; on my machine, exactly 2 processes showed the best result).
By the way, the Async extension also has an API for working with processes, but I did not notice any difference in running benchmarks in one or several processes (it's possible that I messed something up).
SQL vs binary protocol:

Just like with Async, the difference between binary and SQL operations is leveling out in asynchronous mode.
Parallel
Since the Parallel extension is not about coroutines but about threads, let's measure the optimal number of parallel threads:

It's 16 on my machine. Let's run connector benchmarks with 16 parallel threads:

As you can see, the result is even better than with asynchronous extensions (except for Swoole running on 2 processes). Note that for the PECL connector, there is nothing in the update and upsert operations. This is related to the fact that these operations failed with an error — I do not know whether it was due to ext-parallel, ext-tarantool, or both.
Now let's compare SQL performance:

Notice the similarity with the graphs for connectors running synchronously?
All together
In the end, let's compile all the results into a single graph to see the overall picture for the tested extensions. We will add just one new test to the graph that we haven't done yet — we'll run Async coroutines in parallel using Parallel*. The idea of integrating the aforementioned extensions has already by the authors, but a consensus was never reached, so we will have to do it ourselves.
* Running Swoole coroutines with Parallel didn't work; it seems these extensions are incompatible.
So, the final results are:

Namesto zaključka
In my opinion, the results are quite impressive, and for some reason, I believe this is still not the limit! Whether you need this in a real project is entirely up to you; I will only say that for me it was an interesting experiment that allowed me to assess how much you can "squeeze" from a synchronous TCP connector with minimal effort. If you have ideas for improving the benchmarks — I would be happy to consider your pull request. All the code with instructions for running and results is published separately .
Izvor: habr.com
