
Im PHP-Ökosystem gibt es derzeit zwei Konnektoren für den Tarantool-Server: die in C geschriebene offizielle PECL-Erweiterung
tarantool / tarantool-php und
tarantool-php / client in PHP. Ich bin der Autor des letzteren.
In diesem Artikel möchte ich die Ergebnisse der Leistungstests beider Bibliotheken teilen und zeigen, wie Sie mit minimalen Codeänderungen eine 3x-5x-Leistungsverbesserung (
bei synthetischen Tests! ) Erzielen können.
Was werden wir testen?
Wir werden die oben genannten synchronen Konnektoren asynchron, parallel und asynchron parallel testen. Außerdem möchten wir keine Änderungen am Quellcode der Connectors vornehmen. Im Moment sind mehrere Erweiterungen verfügbar, die diese Aufgabe übernehmen können:
- Swoole , ein leistungsstarkes asynchrones Framework für PHP. Wird von Internetgiganten wie Alibaba und Baidu verwendet. Seit Version 4.1.0 gibt es den erstaunlichen Laufzeit-Hook Swoole \ Runtime :: enableCoroutine () , der es ermöglicht, „synchrone PHP-Netzwerkbibliotheken mit einer einzigen Codezeile in Co-Routine-Bibliotheken umzuwandeln“.
- Async, eine vielversprechende Erweiterung für das asynchrone Arbeiten in PHP bis vor kurzem. Warum bis vor kurzem? Leider hat der Autor aus mir unbekannten Gründen das Repository gelöscht und die Zukunft des Projekts ist fraglich. Ich werde eine der Gabeln benutzen. Wie Swoole macht es diese Erweiterung einfach, den asynchronen Modus zu aktivieren, indem die Standard-Stream-Implementierungen von PHP durch ihre asynchronen Gegenstücke ersetzt werden. Dies erfolgt über die Option „ async.tcp = 1 “.
- Parallel dazu eine ganz neue Erweiterung des bekannten Joe Watkins, dem Autor von Bibliotheken wie phpdbg, apcu, pthreads, pcov, uopz. Die Erweiterung bietet eine Multi-Threading-API für PHP und ist als Ersatz für pthreads positioniert. Eine wesentliche Einschränkung der Bibliothek besteht darin, dass sie nur mit der ZTS-Version (Zend Thread Safe) von PHP funktioniert.
Wie werden wir testen?
Wir werden eine Tarantool-Instanz mit deaktivierter Write-Ahead-Protokollierung (
wal_mode = none ) und einem erweiterten Netzwerkpuffer (
readahead = 1 * 1024 * 1024 )
ausführen . Die erste Option verhindert E / A-Vorgänge auf dem Festplattenlaufwerk, die zweite Option ermöglicht das Lesen weiterer Anforderungen aus dem Betriebssystempuffer und minimiert so die Anzahl der Systemaufrufe.
Für Benchmarks, die mit Daten arbeiten (Einfügen, Löschen, Lesen usw.), wird vor dem Start des Benchmarks ein Memtx-Bereich (neu) erstellt, und die anfänglichen Indexwerte für diesen Bereich werden vom Sequenzgenerator erstellt.
Die DDL des Space ist wie folgt:
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} })
Falls erforderlich, wird der Platz vor dem Start des Benchmarks mit 10.000 Tupeln der folgenden Form gefüllt:
{id, 'tuple_' .. id}
Auf die Tupel wird mit dem Zufallsschlüsselwert zugegriffen.
Der Benchmark ist eine einzelne Anforderung an den Server, die 10.000 Mal (Umdrehungen) ausgeführt wird, die wiederum in Iterationen ausgeführt werden. Die Iterationen werden wiederholt, bis alle Zeitabweichungen zwischen 5 Iterationen innerhalb der 3% igen Fehlertoleranz liegen *. Danach wird das Durchschnittsergebnis genommen. Zwischen den Iterationen gibt es eine Pause von 1 Sekunde, um ein Drosseln der CPU zu verhindern. Der Lua Garbage Collector wird vor jeder Iteration deaktiviert und muss nach Abschluss der Iteration gestartet werden. Der PHP-Prozess wird nur mit den für den Benchmark erforderlichen Erweiterungen gestartet, wobei die Ausgabepufferung aktiviert und der Garbage Collector deaktiviert ist.
* Die Anzahl der Umdrehungen, Iterationen und die Fehlerschwelle können in den Benchmark-Einstellungen geändert werden.Testumgebung
Die unten angegebenen Ergebnisse wurden auf MacBookPro (Mitte 2015) mit Fedora 30 (Kernel-Version 5.3.8-200.fc30.x86_64) erstellt. Tarantool wurde im Docker mit der Einstellung "
--network host " gestartet.
Paketversionen:Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.03, Build a872fc2f86
PHP: 7.3.11 (cli) (erstellt: 22. Oktober 2019 08:11:04)
Tarantool / Client: 0.6.0
rybakit / msgpack: 0.6.1
ext-tarantool: 0.3.2 (gepatcht) *
ext-msgpack: 2.0.3
ext-asynchron: 0,3,0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Leider funktioniert der offizielle Connector nicht mit PHP> 7.2. Um die Erweiterung auf PHP 7.3 zu kompilieren und auszuführen, musste ich einen Patch verwenden .Ergebnisse
Synchronisieren (Standard)
Das Tarantool-Protokoll verwendet das
MessagePack- Binärformat, um Nachrichten zu serialisieren. Im PECL-Konnektor ist die Serialisierung tief in der Bibliothek verborgen, sodass
es unmöglich erscheint , den Codierungsprozess über den Userland-Code zu beeinflussen. Im Gegensatz dazu bietet der reine PHP-Connector die Möglichkeit, den Codierungsprozess anzupassen, indem Sie entweder einen der Standardcodierer erweitern oder Ihre eigene Implementierung verwenden. Zwei Encoder sind sofort verfügbar: Einer basiert auf
msgpack / msgpack-php (der offiziellen MessagePack PECL-Erweiterung) und der andere auf
rybakit / msgpack (reines PHP).
Bevor wir mit dem Vergleichen der Konnektoren fortfahren, messen wir die Leistung der MessagePack-Encoder für den PHP-Konnektor, damit wir in unseren Tests den besten Performer verwenden können:
Obwohl die PHP-Version (Pure) nicht so schnell ist wie die PECL-Erweiterung, würde ich dennoch die Verwendung von
rybakit / msgpack in realen Projekten empfehlen, da die offizielle PECL-Erweiterung die MessagePack-Spezifikation nur teilweise implementiert (z. B. gibt es keine Unterstützung für benutzerdefinierte Datentypen, und ohne können Sie Decimal nicht verwenden - ein neuer Datentyp, der in Tarantool 2.3 eingeführt wurde) und eine Reihe anderer
Probleme aufweist (einschließlich Kompatibilitätsprobleme mit PHP 7.4). Und das Projekt sieht im Allgemeinen verlassen aus.
Lassen Sie uns also die Leistung der Konnektoren im synchronen Modus messen:
Wie Sie in der Grafik sehen können, ist der PECL-Connector (Tarantool) besser als der PHP-Connector (Client). Dies ist nicht verwunderlich, da letztere nicht nur in einer langsameren Sprache implementiert sind, sondern auch mehr Arbeit leisten: Mit jeder Anforderung wird ein neues
Anforderungs- und
Antwortobjekt erstellt (im Fall von Select gibt es auch
Kriterien , und im Im Falle von Update / Upsert gibt es
Operations ),
Connection ,
Packer und
Handler fügen ebenfalls etwas Overhead hinzu. Es ist unnötig zu erwähnen, dass eine höhere Flexibilität mit Kosten verbunden ist. Der PHP-Interpreter zeigt jedoch im Allgemeinen eine gute Leistung. Obwohl es einen Unterschied gibt, ist er unbedeutend und kann mit der Verwendung von Preloading in PHP 7.4 noch weniger werden, ganz zu schweigen von JIT in PHP 8.
Jetzt weitermachen. Tarantool 2.0 führte die SQL-Unterstützung ein. Versuchen wir, Select-, Insert-, Update- und Delete-Operationen unter Verwendung des SQL-Protokolls durchzuführen und die Ergebnisse mit noSQL-Entsprechungen (binär) zu vergleichen:
Die SQL-Ergebnisse sind nicht besonders beeindruckend (lassen Sie mich daran erinnern, dass wir den synchronen Modus noch testen). Ich würde mich jedoch nicht vorzeitig darüber aufregen: Die SQL-Unterstützung befindet sich noch in der aktiven Entwicklung (z. B. wurde die Unterstützung für
vorbereitete Anweisungen vor nicht allzu langer Zeit hinzugefügt), und die SQL-Engine wird es laut der Liste der
Probleme tun erhalten Sie eine Reihe von Optimierungen in der Zukunft.
Async
Nun wollen wir sehen, wie uns die Async-Erweiterung dabei helfen kann, die oben genannten Ergebnisse zu verbessern. Für die asynchrone Programmierung stellt die Erweiterung eine Coroutine-basierte API bereit, die wir hier verwenden werden. Erstens, wie wir durch Tests herausfinden, beträgt die optimale Anzahl von Koroutinen für unsere Umgebung 25:
Dann verteilen wir 10.000 Operationen auf 25 Koroutinen und überprüfen das Ergebnis:
Die Anzahl der Operationen pro Sekunde hat sich für den PHP-Connector mehr als verdreifacht! Leider konnte der PECL-Connector nicht mit ext-async gestartet werden.
Und was ist mit SQL?
Wie Sie sehen, liegt der Unterschied zwischen dem Binärprotokoll und SQL im asynchronen Modus innerhalb der Fehlergrenze.
Swoole
Lassen Sie uns noch einmal die optimale Anzahl von Koroutinen bestimmen, diesmal für Swoole:
Nehmen wir 25. Wiederholen Sie nun den gleichen Trick wie bei der Async-Erweiterung: Verteilen Sie 10.000 Operationen auf 25 Coroutinen. Ansonsten fügen wir noch einen Test hinzu, bei dem wir das Ganze in zwei Prozesse aufteilen (d. H. Jeder Prozess führt 5.000 Operationen in 25 Coroutinen aus). Die Prozesse werden mit Hilfe von
Swoole \ Process erstellt .
Ergebnisse:
Swoole zeigt eine etwas geringere Leistung als Async, wenn es in einem Prozess ausgeführt wird, aber bei 2 Prozessen ändert sich das Bild drastisch (2 ist kein Zufall, auf meinem Computer zeigte genau diese Anzahl von Prozessen das beste Ergebnis).
Übrigens gibt es auch eine API für die Arbeit mit Prozessen in der Async-Erweiterung, aber ich habe keinen Unterschied zwischen dem Starten von Benchmarks in einem einzelnen Prozess oder in mehreren Prozessen festgestellt (es ist jedoch möglich, dass ich einige Fehler gemacht habe).
SQL versus binäres Protokoll:
Wie bei Async wird der Unterschied zwischen Binär- und SQL-Operationen im asynchronen Modus ausgeglichen.
Parallel
Da es sich bei der Erweiterung Parallel um Threads und nicht um Coroutinen handelt, messen wir die optimale Anzahl paralleler Threads:
Es ist 16 auf meinem Computer. Lassen Sie uns nun die Anschlüsse auf 16 parallelen Threads vergleichen:
Wie Sie sehen, ist das Ergebnis sogar noch besser als bei asynchronen Erweiterungen (mit Ausnahme von Swoole, das mit zwei Prozessen gestartet wurde). Beachten Sie, dass für den PECL-Connector für Aktualisierungs- und Upsert-Vorgänge keine Leiste angezeigt wird. Dies liegt daran, dass diese Vorgänge mit einem Fehler abgestürzt sind und ich nicht sicher bin, was daran schuld ist: ext-parallel oder ext-tarantool oder beides.
Fügen wir nun die SQL-Leistung zum Vergleich hinzu:
Haben Sie Ähnlichkeiten mit dem Diagramm für die synchron gestarteten Connectors festgestellt?
Alles in einem
Lassen Sie uns zum Schluss alle Ergebnisse in einem Diagramm zusammenfassen, um das gesamte Bild für die getesteten Erweiterungen zu sehen. Wir werden dem Diagramm nur einen neuen Test hinzufügen, den wir noch nicht durchgeführt haben: Starten Sie Async Coroutines parallel mit Parallel *. Die Idee, die oben genannten Erweiterungen zu integrieren, wurde bereits von den Autoren
diskutiert, es wurde jedoch kein Konsens erzielt, sodass wir dies selbst tun müssen.
* Ich konnte Swoole-Coroutinen nicht mit Parallel starten. Es scheint, dass diese Erweiterungen nicht kompatibel sind.Nun sind die endgültigen Ergebnisse:
Fazit
Meiner Meinung nach sind die Ergebnisse recht anständig, aber es gibt etwas, das mich glauben lässt, dass wir noch nicht da sind! Wenn Sie Ideen zur Verbesserung der Benchmarks haben, werde ich Ihre Pull-Anfrage gerne prüfen. Der gesamte Code mit Startanweisungen und Ergebnissen wird in einem dedizierten
Repository veröffentlicht .
Ich überlasse es Ihnen, zu entscheiden, ob Sie dies für ein reales Projekt benötigen, und möchte nur sagen, dass es ein aufregendes Experiment war, mit dem ich abschätzen konnte, wie viel man mit minimalem Aufwand aus einem synchronen TCP-Connector herausholen kann.