Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel

I økosystemet PHP findes der i øjeblikket to forbindelser til at arbejde med Tarantool-serveren - den officielle PECL-udvidelse tarantool/tarantool-php, skrevet i C, og tarantool-php/client, skrevet i PHP. Jeg er forfatteren til den sidstnævnte.

I denne artikel vil jeg dele resultaterne af ydelsestestene af begge biblioteker og vise, hvordan man kan opnå en forbedring på 3-5 i ydeevne med minimale ændringer i koden (på syntetiske tests!).

Hvad skal vi teste?

Vi vil teste de førnævnte synkrone forbindelser, der køres asynkront, parallelt og asynkront-parallelt. 🙂 Vi ønsker heller ikke at ændre koden i selve forbindelserne. I øjeblikket er der flere udvidelser, der kan hjælpe med at opnå det ønskede:

  • Swoole — en højtydende asynkron ramme for PHP. Bruges af internetgiganterne som Alibaba og Baidu. Fra version 4.1.0 er der tilføjet en magisk metode SwooleRuntime::enableCoroutine(), der giver mulighed for «med en enkelt kodelinje at konvertere synkrone netværksbiblioteker i PHP til asynkrone».
  • Async — indtil for nylig en lovende udvidelse til asynkron arbejde i PHP. Hvorfor indtil for nylig? Desværre har forfatteren af en ukendt grund slettet sit repository, og projektets fremtid er usikker. Vi må bruge en af forkingene. Som Swoole gør denne udvidelse det muligt nemt at aktivere asynkronitet ved at erstatte den standardiserede implementering af TCP og TLS-strømme med deres asynkrone versioner. Dette gøres ved hjælp af indstillingen «async.tcp = 1«.
  • Parallel — en forholdsvis ny udvidelse fra den velkendte Joe Watkins, forfatter til biblioteker som phpdbg, apcu, pthreads, pcov, uopz. Denne udvidelse leverer et API til multitrådede operationer i PHP og er positioneret som en erstatning for pthreads. En væsentlig begrænsning ved biblioteket er, at det kun fungerer med ZTS (Zend Thread Safe) versionen af PHP.

Hvordan vil vi teste?

Vi vil starte en Tarantool-instans med deaktiveret write-ahead-log (wal_mode = none) og en forøget netværksbuffer (readahead = 1 * 1024 * 1024). Den første mulighed vil udelukke arbejde med disken, den anden vil give mulighed for at indlæse flere forespørgsler fra operativsystemets buffer og dermed minimere antallet af systemopkald.

Til benchmarks, der arbejder med data (indsættelse, sletning, læsning osv.), vil der før benchmark-start blive (gen)oprettet et memtx-space, hvor værdierne for den primære indeks oprettes af en generator af ordnede heltal (sequence).
DDL-space ser sådan ud:

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}})

Om nødvendigt, før benchmark-starten, fyldes space med 10.000 tuples af typen

{id, "tuple_"}

Adgang til tuples sker efter en tilfældig værdi af nøgle.

Selve benchmarken udgør en enkelt anmodning til serveren, som udføres 10.000 gange (rotationer), som igen udføres i iterationer. Iterationerne gentages, indtil alle tidsafvigelser mellem 5 iterationer ligger inden for en tilladt fejlmargin på 3 %*. Derefter tages det gennemsnitlige resultat. Der er en pause på 1 sekund mellem iterationerne for at undgå, at processoren falder ned i throttling. Lua's garbage collector deaktiveres før hver iteration og aktiveres tvangsagtigt efter dens afslutning. PHP-processen starter kun med de nødvendige udvidelser for benchmarken, med output-buffering aktiveret og garbage collector deaktiveret.

* Antallet af rotationer, iterationer og fejlmargin kan ændres i benchmarkindstillingerne.

Testmiljø

De offentliggjorte resultater nedenfor blev gjort på en MacBookPro (2015), operativsystem — Fedora 30 (kernelversion 5.3.8-200.fc30.x86_64). Tarantool blev kørt i Docker med parameteren "--network host".

Pakkernes versioner:

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

* Desværre fungerer den officielle connector ikke med PHP-versionen > 7.2. For at kunne kompilere og køre udvidelsen på PHP 7.3, måtte jeg bruge patch.

Resultater

Synkron tilstand

Tarantools protokol bruger binært format MessagePack til serialisering af beskeder. I PECL-connectoren er serialiseringen skjult langt inde i biblioteket, og det er ikke muligt at påvirke kodningsprocessen fra userland-kode ikke kan lade sig gøre.. En connector skrevet i ren PHP giver mulighed for at tilpasse kodningsprocessen ved at udvide den standardkode, der anvendes, eller ved at bruge sin egen implementering. Der leveres to kodere fra starten, en baseret på msgpack/msgpack-php (officiel MessagePack PECL-udvidelse), den anden — på rybakit/msgpack (i ren PHP).

Før vi sammenligner connectorerne, vil vi måle ydeevnen af MessagePack-kodere til PHP-connectoren og i de videre tests vil vi bruge den, der viser det bedste resultat:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Selvom den PHP-version (Pure) er langsommere end PECL-udvidelsen, vil jeg i virkelige projekter stadig anbefale at bruge rybakit/msgpack, fordi den officielle MessagePack-udvidelse kun delvist implementerer formatets specifikation (for eksempel er der ingen støtte til brugerdefinerede datatyper, uden hvilke du ikke kan bruge Decimal — den nye datatype introduceret i Tarantool 2.3) og har en række andre problemer (herunder kompatibilitetsproblemer med PHP 7.4). Og generelt ser projektet ud til at være forladt.

Lad os måle ydeevnen af connectorerne i synkront mode:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Som det fremgår af diagrammet, viser PECL-connectoren (Tarantool) bedre ydeevne sammenlignet med PHP-connectoren (Client). Men det er ikke overraskende, da sidstnævnte, ud over at være implementeret i et langsommere sprog, også i virkeligheden laver mere arbejde: der oprettes et nyt objekt ved hver kald Request og Svar (i tilfælde af Select — også Criteria, og i tilfælde af Update/Upsert — Operations), separate enheder Connection, Packer og Handler tilføjer også overhead. Det står klart, at fleksibiliteten koster. Men generelt viser PHP-fortolkeren god ydeevne, selvom der er en forskel, er den ikke betydelig, og måske vil den blive endnu mindre med brugen af preloading i PHP 7.4, for ikke at nævne JIT i PHP 8.

Lad os komme videre. I Tarantool 2.0 blev der tilføjet SQL-understøttelse. Lad os prøve at udføre Select, Insert, Update og Delete-operationer ved hjælp af SQL-protokollen og sammenligne resultaterne med noSQL (binære) ækvivalenter:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Resultaterne fra SQL imponerer ikke meget (jeg minder om, at vi stadig tester synkront mode). Men jeg ville ikke bekymre mig alt for meget om det lige nu, da SQL-support stadig er under aktiv udvikling (for nylig blev der for eksempel tilføjet støtte til prepared statements) og, ifølge listen issues, venter der en række optimeringer for SQL-motoren i fremtiden.

Async

Lad os nu se, hvordan Async-udvidelsen kan hjælpe os med at forbedre de nævnte resultater. For at skrive asynkrone programmer tilbyder udvidelsen et API baseret på korutiner (coroutines), som vi vil bruge. Gennem erfaring finder vi ud af, at det optimale antal korutiner for vores miljø er 25:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Vi "fordeler" 10.000 operationer på 25 korutiner og ser, hvad der er kommet ud af det:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Antallet af operationer pr. sekund er steget med mere end 3 gange for tarantool-php/client!

Desværre kunne PECL-connectoren ikke startes med ext-async.

Og hvad med SQL?

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Som du kan se, er forskellen mellem binærprotokollen og SQL i asynkront mode inden for målefejl.

Swoole

Vi finder igen det optimale antal korutiner, nu for Swoole:
Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Lad os stoppe ved 25. Vi gentager det samme trick, som med Async-udvidelsen - vi fordeler 10.000 operationer mellem 25 korutiner. Derudover tilføjer vi endnu en test, hvor vi deler alt arbejdet mellem 2 processer (det vil sige, at hver proces vil udføre 5.000 operationer i 25 korutiner). Processerne vil blive oprettet med hjælp af SwooleProcess.

Resultater:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Swole viser et lidt lavere resultat sammenlignet med Async, når det køres i én proces, men med 2 processer ændrer billedet sig dramatisk (tallet 2 er ikke valgt tilfældigt, da 2 processer på min maskine viste det bedste resultat).

Forresten, Async-udvidelsen har også et API til proceshåndtering, men jeg har ikke bemærket nogen forskel mellem at køre benchmarks i én eller flere processer (jeg har sikkert lavet nogen fejl et sted).

SQL vs binær protokol:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Som med Async, udlignes forskellen mellem binære og SQL-operationer i asynkront mode.

Parallel

Eftersom Parallel-udvidelsen ikke handler om korutiner, men om tråde, lad os måle det optimale antal parallelle tråde:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Det er 16 på min maskine. Lad os køre benchmarks for connectorerne på 16 parallelle tråde:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Som du kan se, er resultatet endda bedre end med de asynkrone udvidelser (bortset fra Swoole kørt på 2 processer). Bemærk, at der ikke er noget for connectoren PECL for Update- og Upsert-operationer. Dette skyldes, at disse operationer fejlede - jeg ved ikke, om det er på grund af ext-parallel, ext-tarantool eller begge.

Lad os nu sammenligne SQL-ydeevnen:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel
Lagde du mærke til ligheden med grafen for connectorerne, der blev kørt synkront?

Alt i alt

Og til slut, lad os samle alle resultaterne i en graf, så vi kan se det samlede billede for de testede udvidelser. Vi vil kun tilføje én ny test til grafen, som vi endnu ikke har lavet - vi vil køre Async-korutiner parallelt ved hjælp af Parallel*. Idéen om at integrere de nævnte udvidelser er allerede blevet diskuteret af forfatterne, men der blev ikke opnået nogen enighed, så vi må gøre det selv.

* At køre Swoole-korutiner med Parallel var ikke muligt, tilsyneladende er disse udvidelser inkompatible.

Så, de endelige resultater:

Accelererer PHP-adaptere til Tarantool med Async, Swoole og Parallel

Afslutning

Efter min mening er resultaterne ret tilfredsstillende, og jeg er af en eller anden grund overbevist om, at dette endnu ikke er grænsen! Om dette er nødvendigt for jer i et reelt projekt, er helt op til jer selv, jeg vil blot sige, at for mig var det et interessant eksperiment, der gjorde det muligt at vurdere, hvor meget man kan "udtrække" fra en synkron TCP-connector med minimal indsats. Hvis I har ideer til forbedring af benchmarkene - ville jeg med glæde overveje jeres pull request. Hele koden med instruktioner til kørsel og resultater er offentliggjort i en separat repository.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster