Bluebird vs Native vs Async/Await - État des performances des promesses en 2019

Une histoire d’oiseau bleu, de performances et de promesses

Récemment nous avons commencé les travaux sur la version 2 de Kuzzle.

Cette nouvelle version majeure ne contiendra que très peu de breaking changes fonctionnels mais surtout des mises à jours de nos dépendances, notamment le passage de Elasticsearch 5 à 7 et de Node.js 6 à 10.

 

English version

 

Concernant le passage de Elasticsearch 5 à 7, j’en parlerais en détails dans un prochain article.

 

A propos du passage en Node.js 10, la plus grande question était de savoir ce que nous allions faire pour les promises actuelles utilisées dans le coeur de Kuzzle. Et surtout si nous allions enfin pouvoir utiliser async/await dans l’ensemble du coeur.

Doxbee benchmark

Pour l’ensemble de nos tests, nous avons utilisés les benchmark de Doxbee développés par l’équipe de Bluebird en s’appuyant sur un article d’analyse des différents patterns asynchrones en Javascript.

 

Ces benchmarks simulent une situation dans laquelle des requêtes sont exécutées concurrentiellement avec des actions bloquantes de lecture/écriture.

 

Pour les reproduire, vous pouvez cloner ce dépôt Github: https://github.com/petkaantonov/bluebird/

Les outils de benchmarking sont dans le dossier benchmark/.

 

TL;DR:

En Node.js 10, impossible de se passer de Bluebird dans les zones critiques de code au vu des performances de ses promises par rapport aux promises natives et à async/await.

 

En Node.js 12, les performances de async/await sont comparables à celles de Bluebird, alors nous pourrons utiliser async/await dans l’ensemble du code du coeur de Kuzzle.

 

 

Node.js 6

Actuellement nous utilisons la bibliothèque Bluebird pour des raisons de performances. En effet, les promises Bluebird sont jusqu’à 300% plus rapides et consomment 200% de mémoire en moins que les promises natives de Node.js 6.

 

$ echo "./doxbee-sequential/promises-ecmascript6-native.js ./doxbee-sequential/promises-bluebird.js" | sed -e 's|\.js||' | xargs node ./performance.js --p 1 --t 1 --n 10000

file                        time(ms)    memory(MB)
promises-bluebird.js         227       45.75
promises-ecmascript6-native  945       145.09

Platform info:
Linux 4.10.0-38-generic x64
Node.JS 6.17.1
V8 5.1.281.111
Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz × 4

Node.js 10

Avec cette montée en version, nous avons voulu savoir si nous allions enfin pouvoir nous passer de Bluebird !

Nous n’avons pas trouvé de résultats de benchmark récent comparant les performances des promises de Node.js 10 alors nous avons lancé nous même les benchmark avec Node.js 10:

 

echo "./doxbee-sequential/promises-native-async-await.js ./doxbee-sequential/promises-ecmascript6-native.js ./doxbee-sequential/promises-bluebird.js" | sed -e 's|\.js||' | xargs node ./performance.js --p 1 --t 1 --n 10000

file                            time(ms)  memory(MB)
promises-bluebird.js                 260       44.71
promises-native-async-await          322       68.64
promises-ecmascript6-native.js       332       73.80

Platform info:
Linux 4.10.0-38-generic x64
Node.JS 10.16.2
V8 6.8.275.32-node.54
Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz × 4

 

On constate que même en Node.js 10, les promises Bluebird sont 20% plus rapide que les promises natives et consomment 40% de mémoire en moins.

De même, les performances de Bluebird surpassent celles des promises gérées avec async/await.

 

Nous avons donc décidé de conserver Bluebird pour gérer les promises des zones critiques de code tel que l’écriture et la récupération de document car ces fonctions peuvent être appelées plusieurs milliers de fois par secondes et tout gain de performance est bon à prendre.

 

Par contre nous allons pouvoir utiliser async/await à des endroits non critiques pour simplifier la lisibilité et la maintenabilité du code de Kuzzle. Notamment nous allons pouvoir supprimer toutes les fonctions génératrices du coeur.

Node.js 12

Par curiosité, nous avons également benchmarké les performances des promises en Node.js 12. Pour rappel, cette nouvelle version de Node.js inclut un significatif gain de performance sur la gestion des promises avec async/await. (Voir cet excellent article sur le blog de V8 https://v8.dev/blog/fast-async)

 

echo "./doxbee-sequential/promises-native-async-await.js ./doxbee-sequential/promises-ecmascript6-native.js ./doxbee-sequential/promises-bluebird.js" | sed -e 's|\.js||' | xargs node ./performance.js --p 1 --t 1 --n 10000

file                            time(ms)  memory(MB)
promises-bluebird.js                 279       49.20
promises-native-async-await          280       53.22
promises-ecmascript6-native.js       318       65.82

Platform info:
Linux 4.10.0-38-generic x64
Node.JS 12.8.1
V8 7.5.288.22-node.16
Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz × 4

 

On constate que les performances des promises gérées avec async/await sont comparables aux promises de Bluebird!

 

Malheureusement la LTS de Node.js 12 sort en octobre et nous avons absolument besoin de sortir Kuzzle v2 avant pour palier à la fin du support officiel Elasticsearch 5 et Node.js 6.

 

Par contre, nous n’hésiterons pas à remplacer toutes les promises Bluebird du coeur de Kuzzle par des promises async/await pour Kuzzle v3 ;)

 

Adrien Maret

Postes associés