Accélérer les connecteurs PHP pour Tarantool en utilisant Async, Swoole et Parallel



Dans l'écosystème PHP, il existe actuellement deux connecteurs pour le serveur Tarantool: l'extension officielle PECL tarantool / tarantool-php écrite en C, et tarantool-php / client écrite en PHP. Je suis l'auteur de ce dernier.

Dans cet article, je voudrais partager les résultats des tests de performances de ces deux bibliothèques et montrer comment vous pouvez améliorer les performances de 3x à 5x ( sur les tests synthétiques! ) Avec un minimum de changements de code.

Qu'allons-nous tester?


Nous allons tester les connecteurs synchrones mentionnés ci-dessus lancés de manière asynchrone, en parallèle et asynchrone en parallèle. De plus, nous ne voulons aucune modification du code source des connecteurs. Pour le moment, plusieurs extensions sont disponibles pour faire le travail:

  • Swoole , un framework asynchrone hautes performances pour PHP. Utilisé par des géants de l'Internet comme Alibaba et Baidu. Depuis la version 4.1.0, l'incroyable hook d'exécution Swoole \ Runtime :: enableCoroutine () est apparu, permettant «de transformer des bibliothèques réseau PHP synchrones en bibliothèques de co-routine en utilisant une seule ligne de code».
  • Async, une extension très prometteuse pour le travail asynchrone en PHP jusqu'à récemment. Pourquoi jusqu'à récemment? Malheureusement, pour des raisons que je ne connais pas, l'auteur a supprimé le référentiel et l'avenir du projet est discutable. J'utiliserai l' une des fourches. Comme Swoole, cette extension facilite l'activation du mode asynchrone en remplaçant les implémentations de flux par défaut de PHP par leur homologue asynchrone. Cela se fait via l'option " async.tcp = 1 ".
  • Parallèle , une toute nouvelle extension du célèbre Joe Watkins, l'auteur de bibliothèques telles que phpdbg, apcu, pthreads, pcov, uopz. L'extension fournit une API multi-threading pour PHP et se positionne en remplacement de pthreads. Une limitation importante de la bibliothèque est qu'elle ne fonctionne qu'avec la version ZTS (Zend Thread Safe) de PHP.

Comment allons-nous tester?


Nous exécuterons une instance de Tarantool avec la journalisation en écriture anticipée désactivée ( wal_mode = none ) et un tampon réseau étendu ( readahead = 1 * 1024 * 1024 ). La première option empêchera les opérations d'E / S sur le lecteur de disque, la seconde permettra de lire plus de demandes à partir du tampon du système d'exploitation et donc de minimiser le nombre d'appels système.

Pour les benchmarks qui fonctionnent avec des données (insertion, suppression, lecture, etc.), un espace memtx sera (re) créé avant le benchmark et les valeurs d'index initiales pour cet espace seront créées par le générateur de séquence.

La DDL de l'espace est la suivante:

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

Si nécessaire, avant de commencer le benchmark, l'espace est rempli de 10 000 tuples de la forme suivante:

 {id, 'tuple_' .. id} 

Les tuples sont accessibles à l'aide de la valeur de clé aléatoire.

Le benchmark est une requête unique au serveur qui est exécutée 10 000 fois (révolutions), qui à leur tour sont exécutées en itérations. Les itérations sont répétées jusqu'à ce que tous les écarts de temps entre 5 itérations soient dans la marge d'erreur de 3% *. Après cela, le résultat moyen est pris. Entre les itérations, il y a une pause de 1 seconde pour empêcher le processeur de limiter. Le garbage collector Lua est désactivé avant chaque itération et est forcé de démarrer une fois l'itération terminée. Le processus PHP n'est lancé qu'avec les extensions requises pour le benchmark, avec la mise en mémoire tampon de sortie activée et le garbage collector désactivé.

* Le nombre de tours, d'itérations et de seuil d'erreur peut être modifié dans les paramètres de référence.

Environnement de test


Les résultats publiés ci-dessous ont été réalisés sur MacBookPro (mi-2015) avec Fedora 30 (version du noyau 5.3.8-200.fc30.x86_64). Tarantool a été lancé dans Docker avec le paramètre « --network host ».

Versions du package:

Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19/03/3, build a872fc2f86
PHP: 7.3.11 (cli) (construit: 22 octobre 2019 08:11:04)
tarantool / client: 0.6.0
rybakit / msgpack: 0.6.1
ext-tarantool: 0.3.2 (patché) *
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallèle: 1.1.3

* Malheureusement, le connecteur officiel ne fonctionne pas avec PHP> 7.2. Pour compiler et exécuter l'extension sur PHP 7.3, j'ai dû utiliser un patch .

Résultats


Sync (par défaut)


Le protocole Tarantool utilise le format binaire MessagePack pour sérialiser les messages. Dans le connecteur PECL, la sérialisation est cachée au plus profond de la bibliothèque, il semble donc impossible d'affecter le processus d'encodage à partir du code utilisateur. En revanche, le connecteur PHP pur offre la possibilité de personnaliser le processus d'encodage, soit en étendant l'un des encodeurs standard, soit en utilisant votre propre implémentation. Deux encodeurs sont disponibles prêts à l' emploi : l'un est basé sur msgpack / msgpack-php (l'extension officielle MessagePack PECL) et l'autre est basé sur rybakit / msgpack (PHP pur).

Avant de procéder à la comparaison des connecteurs, mesurons les performances des encodeurs MessagePack pour le connecteur PHP, afin d'utiliser le plus performant plus loin dans nos tests:


Bien que la version PHP (Pure) ne soit pas aussi rapide que l'extension PECL, je recommanderais toujours d'utiliser rybakit / msgpack dans de vrais projets, car l'extension PECL officielle n'implémente que partiellement la spécification MessagePack (par exemple, il n'y a pas de support pour les types de données personnalisés, et sans lui, vous ne pouvez pas utiliser Decimal - un nouveau type de données introduit dans Tarantool 2.3) et a un certain nombre d'autres problèmes (y compris des problèmes de compatibilité avec PHP 7.4). Et le projet semble abandonné en général.

Mesurons donc les performances des connecteurs en mode synchrone:


Comme vous pouvez le voir sur le graphique, le connecteur PECL (Tarantool) fonctionne mieux que le connecteur PHP (Client). Cela n'est pas surprenant, étant donné que ce dernier, en plus d'être implémenté dans un langage plus lent, fait en fait plus de travail: un nouvel objet Request and Response est créé avec chaque requête (dans le cas de Select, il y a aussi des critères , et dans le cas d'Update / Upsert, il y a des opérations ), Connection , Packer et Handler ajoutent également des frais généraux. Il va sans dire qu'une plus grande flexibilité a un coût. Cependant, l'interpréteur PHP affiche de bonnes performances en général. Bien qu'il y ait une différence, elle est insignifiante et peut obtenir encore moins avec l'utilisation du préchargement en PHP 7.4, sans parler de JIT en PHP 8.

Passons maintenant. Tarantool 2.0 a introduit le support SQL. Essayons d'effectuer des opérations de sélection, d'insertion, de mise à jour et de suppression à l'aide du protocole SQL et comparons les résultats avec des équivalents noSQL (binaires):


Les résultats SQL ne sont pas si impressionnants (permettez-moi de vous rappeler que nous testons toujours le mode synchrone). Cependant, je ne serais pas fâché à ce sujet avant l'heure: le support SQL est toujours en cours de développement (par exemple, le support des instructions préparées a été ajouté il n'y a pas si longtemps) et, selon la liste des problèmes , le moteur SQL obtenir un certain nombre d'optimisations à l'avenir.

Async


Eh bien, voyons maintenant comment l'extension Async peut nous aider à améliorer les résultats ci-dessus. Pour la programmation asynchrone, l'extension fournit une API basée sur des coroutines, que nous allons utiliser ici. Premièrement, comme nous le constatons à travers les tests, le nombre optimal de coroutines pour notre environnement est de 25:


Ensuite, nous avons réparti 10 000 opérations sur 25 coroutines et vérifié le résultat:


Le nombre d'opérations par seconde a augmenté de plus de 3 fois pour le connecteur PHP! Malheureusement, le connecteur PECL n'a pas pu se lancer avec ext-async.

Et qu'en est-il de SQL?


Comme vous pouvez le voir, en mode asynchrone, la différence entre le protocole binaire et SQL se situe dans la marge d'erreur.

Swoole


Encore une fois, déterminons le nombre optimal de coroutines, cette fois pour Swoole:


Prenons 25. Maintenant, en répétant la même astuce qu'avec l'extension Async: répartissez 10 000 opérations entre 25 coroutines. En dehors de cela, ajoutons un test supplémentaire, où nous divisons le tout en deux processus (c'est-à-dire que chaque processus effectuera 5000 opérations dans 25 coroutines). Les processus seront créés à l'aide de Swoole \ Process .

Résultats:


Swoole affiche des performances légèrement inférieures par rapport à Async lors de l'exécution en un seul processus, mais avec 2 processus, l'image change radicalement (2 n'est pas choisi par accident, sur ma machine, ce nombre exact de processus a donné le meilleur résultat).

Soit dit en passant, il existe également une API pour travailler avec les processus dans l'extension Async, mais je n'ai remarqué aucune différence entre le lancement de tests de référence dans un seul processus ou dans plusieurs processus (il est possible que j'ai fait quelques erreurs).

SQL contre protocole binaire:


Comme avec Async, la différence entre les opérations binaires et SQL est nivelée en mode asynchrone.

Parallèle


Puisque l'extension parallèle concerne les threads, pas les coroutines, nous devons mesurer le nombre optimal de threads parallèles:


C'est 16 sur ma machine. Maintenant, comparons les connecteurs sur 16 threads parallèles:


Comme vous pouvez le voir, le résultat est encore meilleur qu'avec des extensions asynchrones (sauf Swoole lancé avec 2 processus). Notez que pour le connecteur PECL, les opérations Update et Upsert n'ont pas de barre. C'est parce que ces opérations se sont écrasées avec une erreur, et je ne sais pas ce qui est à blâmer: ext-parallel, ou ext-tarantool, ou les deux.

Ajoutons maintenant les performances SQL à la comparaison:


Avez-vous remarqué des similitudes avec le graphique des connecteurs lancés de manière synchrone?

Tout en un


Enfin, combinons tous les résultats dans un graphique pour voir l'image complète des extensions testées. Nous allons ajouter un seul nouveau test au graphique, ce que nous n'avons pas encore fait: lancer les coroutines Async en parallèle en utilisant Parallel *. L'idée d'intégrer les extensions susmentionnées a déjà été discutée par les auteurs mais aucun consensus n'a été atteint, nous devrons donc le faire nous-mêmes.

* Je n'ai pas réussi à lancer les coroutines Swoole avec Parallel; il semble que ces extensions soient incompatibles.

Maintenant, les résultats finaux:


Conclusion


À mon avis, les résultats sont assez décents, mais il y a quelque chose qui me fait croire que nous n'en sommes pas encore là! Si vous avez des idées sur la façon d'améliorer les repères, je serai heureux d'examiner votre demande de pull. Tout le code avec les instructions de lancement et les résultats est publié dans un référentiel dédié.

Laissant à vous de décider si vous en aurez besoin dans un vrai projet, je dirais simplement que c'était une expérience passionnante qui m'a permis d'estimer combien on pouvait gagner d'un connecteur TCP synchrone avec un minimum d'effort.

Source: https://habr.com/ru/post/fr480032/


All Articles