Blog

Améliorez l'utilisation de la mémoire et les performances de votre application Ruby avec jemalloc

Chargement...

10 min de lecture

Améliorez l'utilisation de la mémoire et les performances de votre application Ruby avec jemalloc

Cet article a été publié à l'origine sur le blog de Level Up Solutions par Clément Joubert, fondateur de Level Up Solutions, une société de conseil et de développement d'applications web. Avec sa permission, nous le partageons ici pour les lecteurs de Scalingo.

Cet article a été initialement publié sur le blog de Level Up Solutions par Clément Joubert, Fondateur de Level Up Solutions, une société de conseil et de développement d'applications Web. Avec sa permission bienveillante, nous le partageons ici pour les lecteurs de Scalingo.

Les applications Ruby ont généralement une empreinte mémoire assez importante, qui peut augmenter régulièrement, même si le ramasse-miettes entre en action. Votre application peut consommer 150 Mo de mémoire au lancement initial, mais elle dépassera probablement rapidement les 300 Mo.

Ruby utilise la bibliothèque C malloc pour allouer, libérer et réallouer dynamiquement de la mémoire, afin de stocker des objets. Cependant, d'autres implémentations existent telles que tcmalloc ou jemalloc. Cette dernière bibliothèque a prouvé sa valeur depuis 2005 dans FreeBSD. Le populaire et puissant Redis l'utilise également depuis plusieurs années. Une preuve rassurante de fiabilité.

Pour Ruby, l'utilisation de jemalloc vous permet d'allouer, de réutiliser ou de libérer plus efficacement la mémoire de votre application. En production, de nombreux utilisateurs ont constaté une augmentation de la performance d'environ 10 % (pour le code Ruby) ainsi qu'une consommation de mémoire réduite et stable. Certaines personnes — comme nous — ont trouvé les fuites de mémoire réduites voire éliminées. Ce dernier cas peut être un véritable soulagement si vous êtes la victime de ce type d'inconvénients. Nous sommes également passés par là !

Après que cet article a été initialement publié en français, le créateur de updown.io, Adrien Jarthon l'a déployé sur la moitié de ses serveurs et a constaté une consommation de mémoire réduite de -29 % pour l'application Rails, et de -46 % pour les travailleurs Sidekiq.

Installation de jemalloc

L'implémentation Ruby la plus populaire s'appelle Ruby MRI. Depuis la version 2.2, elle peut être compilée avec jemalloc au lieu de malloc. Aucun patch ne doit être appliqué : seule la présence de la bibliothèque jemalloc sur votre système est requise ainsi qu'un drapeau de compilation.

Sur Linux, installez jemalloc en utilisant votre gestionnaire de paquets préféré. Par exemple avec Ubuntu :

$ sudo apt-get update
$ sudo

$ sudo apt-get update
$ sudo

$ sudo apt-get update
$ sudo

$ sudo apt-get update
$ sudo

Sur macOS, le moyen le plus simple est d'utiliser Homebrew pour l'installer :

$ brew install jemalloc
$ brew install jemalloc
$ brew install jemalloc
$ brew install jemalloc

Compiler Ruby avec le support de jemalloc

Avec jemalloc installé, vous devez toujours compiler Ruby avec le drapeau approprié :

$ ./configure --with-jemalloc

# Usual post-configuration commands:
$ make
$ make

$ ./configure --with-jemalloc

# Usual post-configuration commands:
$ make
$ make

$ ./configure --with-jemalloc

# Usual post-configuration commands:
$ make
$ make

$ ./configure --with-jemalloc

# Usual post-configuration commands:
$ make
$ make

Si vous utilisez un gestionnaire Ruby comme rbenv ou RVM, vous devez simplement préfixer la commande d'installation avec ceci :

$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.4.0
$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.4.0
$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.4.0
$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.4.0

Pour vous assurer que votre Ruby utilise jemalloc, exécutez la commande suivante :

$ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']"
$ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']"
$ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']"
$ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']"

Si jemalloc est utilisé, vous devriez obtenir une réponse proche de celle-ci :

-lpthread -ljemalloc -ldl -lobjc
-lpthread -ljemalloc -ldl -lobjc
-lpthread -ljemalloc -ldl -lobjc
-lpthread -ljemalloc -ldl -lobjc

La présence de -ljemalloc indique que la bibliothèque du même nom est chargée lors du démarrage de Ruby.

Scalingo et d'autres plateformes où vous ne pouvez pas compiler Ruby

Un grand nombre d'applications sont déployées sur des plateformes qui prennent en charge l'hébergement et l'exécution de vos applications Ruby. Ils s'occupent de la plupart des tâches difficiles, et il n'est ni nécessaire ni souvent possible de compiler Ruby vous-même. Dans ce cas, nous devons forcer l'utilisation de jemalloc en utilisant des buildpacks.

Si vous ne savez pas ce que sont les buildpacks, ce sont une sorte de paquet logiciel contenant tout le nécessaire pour exécuter un programme sur une plateforme d'hébergement Linux. Il existe des buildpacks pour Ruby, Node.js, Scala, Java, PhantomJS, etc.

Le buildpack jemalloc

Un buildpack ne contient pas nécessairement un programme exécutable. Comme les paquets Linux, il peut ne contenir qu'une bibliothèque C, par exemple.

C'est là que le heroku-buildpack-jemalloc buildpack de Seth Fitzsimmons s'avère utile. Il contient jemalloc précompilé pour Linux x86 et stocké dans /app/vendor/jemalloc/lib/libjemalloc.so.1. Cela le rend facilement accessible par Ruby MRI.

Le buildpack jemalloc doit être chargé avant le buildpack Ruby. Vous devez configurer votre plateforme pour utiliser les deux buildpacks simultanément.

Configuration de plusieurs buildpacks sur Scalingo

Heroku a rendu les buildpacks immensément populaires. D'autres plateformes ont suivi la même philosophie. Nous utilisons Scalingo depuis quelques années maintenant. Ce fournisseur français de Platform-as-a-Service (PaaS) d'hébergement est très similaire au géant américain et continue d'ajouter des fonctionnalités utiles comme un horloge.

Scalingo n'autorise qu'un seul buildpack à être défini pour une application, donc nous devons un peu tricher en utilisant le buildpack multi-buildpack. Lorsqu'il est utilisé, il chargera plusieurs buildpacks, un après l'autre.

Pour l'utiliser, exécutez cette commande :

$ scalingo
$ scalingo
$ scalingo
$ scalingo

Ensuite, créez un fichier .buildpacks à la racine de votre application et énumérez les buildpacks à utiliser :





Lors du déploiement de votre conteneur, Scalingo recherchera ce fichier et chargera chaque buildpack séquentiellement.

Démarrer votre application Ruby avec jemalloc

Le buildpack jemalloc contient un script shell nommé jemalloc.sh. L'auteur recommande de préfixer votre ligne de commande Ruby habituelle avec lui. Par exemple, pour lancer le serveur web Puma, nous utiliserions :

$ jemalloc
$ jemalloc
$ jemalloc
$ jemalloc

L'utilisation de ce script a ses problèmes, en plus de l'apparence. Votre configuration de serveur devient spécifique et dépendante du script de ce buildpack. La même commande puma ne fonctionnera pas sur une plateforme qui n'inclut pas le script. Sur votre plateforme de développement, il est probablement non disponible. Cela poserait un problème si vous souhaitez utiliser Foreman pour tester votre Procfile, par exemple.

Une de nos applications est déployée en continu sur 4 PaaS différents, dont 3 utilisent le même fichier Procfile pour démarrer l'application Ruby. Nous devons utiliser la même configuration exacte partout, dans le même Procfile, donc nous ne pouvons pas appliquer d'optimisations spécifiques pour Heroku ou Scalingo à cet endroit.

La solution dont nous avons besoin suit la méthodologie 12factor en stockant la configuration dans l'environnement. Nous pourrons toujours dire à Ruby de charger et d'utiliser jemalloc, mais la ligne de commande ne changera pas.

La variable d'environnement LD_PRELOAD est recherchée par MRI pour déterminer quelles bibliothèques supplémentaires il doit précharger. Si la bibliothèque jemalloc est présente, elle sera utilisée au lieu de malloc. Cela fonctionnera avec bundle exec ruby ainsi qu'avec un exécutable gem tel que rubocop ou sidekiq.

jemalloc sur Scalingo

Le tableau de bord Scalingo peut être utilisé pour modifier une variable, ou toutes à la fois :



Screenshot of the Scalingo dashboard opened on the environment variables tab



Vous pouvez également définir les variables d'environnement en utilisant l'outil CLI :

$ scalingo env-set LD_PRELOAD
$ scalingo env-set LD_PRELOAD
$ scalingo env-set LD_PRELOAD
$ scalingo env-set LD_PRELOAD

Redémarrez le conteneur de votre application pour bénéficier de cette optimisation agréable.

Expérience(s) personnelle(s) en production

Notre application la plus complexe (plus de 100 gems, 160 Mo au démarrage) consommait environ 500 Mo par travailleur Puma après traitement de dizaines de milliers de requêtes lourdes. Pour éviter de saturer la mémoire de notre serveur, les travailleurs Puma et Sidekiq devaient être régulièrement tués et redémarrés.

Avec les versions 2.3 et 2.4 compilées avec jemalloc, nous avons mesuré une consommation de mémoire stabilisée à environ 350 Mo. Les temps de réponse ont chuté de quelques pourcents et le nombre d'alertes « requêtes anormalement lentes » a également été largement réduit.

Nous avons réitéré l'expérience avec d'autres applications : une application Ruby légère dans un conteneur de 512 Mo et un CMS basé sur Rails fait maison plus lourd dans un conteneur de 2 Go. Le swap est désormais presque jamais utilisé car les conteneurs ont cessé de dépasser leurs ressources attribuées. Les temps de réponse se sont également légèrement améliorés sur le CMS.

La gem jemalloc : une alternative non fonctionnelle

Nous avons également essayé la gem jemalloc, un projet qui ne semble plus être maintenu depuis sa dernière activité en janvier 2015. Nous n'avons pas réussi à le faire fonctionner. De plus, il présente la même complexité d'utilisation que le script jemalloc.sh : toutes les lignes de commande doivent être préfixées par bundle exec je.

Un mot de Scalingo

Bien sûr, pour des raisons de conformité, la déclaration officielle de Scalingo concernant jemalloc est qu'ils utiliseront toujours les versions officielles du langage Ruby.

Cependant, avec l'extensibilité fournie par le mécanisme des buildpacks, vous pouvez facilement essayer des solutions divergentes par vous-même comme démontré dans cet article.

Yann Klis, Scalingo

Yann Klis

Yann KLIS a fondé Scalingo en 2015 avec son associé Léo Unbekandt avec la vision de proposer une plateforme cloud d'hébergement web, véritable alternative européenne et souveraine aux géants américains. Aujourd'hui Scalingo héberge plusieurs milliers d'applications web déployées par des clients du monde entier ! L'objectif de Scalingo est de devenir la plateforme cloud de référence pour les développeurs web en Europe. Auparavant, il a fondé Novelys, un studio de développement spécialisé dans la technologie Ruby on Rails.

Restez informé

Recevez des articles et des mises à jour de la plateforme dans votre boîte de réception.

Prêt à déployer en toute confiance ?

Découvrez des déploiements sans temps d'arrêt, une mise à l'échelle automatique intelligente et une infrastructure entièrement gérée. Commencez à déployer vos applications sur Scalingo dès aujourd'hui.

Aucune carte de crédit requise • Déployez en quelques minutes • Annulez à tout moment

Déployez une application ou base de données

Commencez à déployer

Rejoignez les équipes qui misent sur une plateforme conçue pour livrer rapidement, opérer sereinement, avec des valeurs européennes et un support humain.

Déployez une application ou base de données

Commencez à déployer

Rejoignez les équipes qui misent sur une plateforme conçue pour livrer rapidement, opérer sereinement, avec des valeurs européennes et un support humain.

Déployez une application ou base de données

Commencez à déployer

Rejoignez les équipes qui misent sur une plateforme conçue pour livrer rapidement, opérer sereinement, avec des valeurs européennes et un support humain.