fabernovel loader

Augmenter son back end Ruby avec Elixir

Pallier les faiblesses de Ruby avec Elixir

ARCHITECTURE
BACK
fabernovel loader
Ruby est un formidable langage pour le développement back end. La gestion de la concurrence n'est cependant pas son fort. Nous relatons ici notre expérience avec Elixir pour remédier à ce point faible, dans le cadre de notre outil de spécification Pericles.

Au commencement était Ruby

On en parlait déjà dans un précédent article « Notre recette de back end« , nous sommes des adeptes de Ruby. Il n’y a donc rien d’étonnant à ce que nous ayons choisi ce langage pour créer Pericles, notre outil de spécification d’API dont la première version fut disponible en quelques semaines. Les projets affluèrent alors progressivement, et le proxy de Pericles devînt un incontournable. Une équipe travaillant pour un client avec des webservices LENTS (>20s 😬) décida un jour de brancher sa suite de test sur le proxy. C’est à ce moment là que Ruby montra enfin ses limites.

Enfin ? Cela peut paraître étonnant, mais oui l’équipe se réjouit de la nouvelle. Au fil des années nous avions en effet construit un bon nombre de back ends, et aucun d’entre eux n’avait jamais rencontré de problème de performance (ou alors résolu en seulement quelques heures de réflexion sur l’optimisation de son fonctionnement). Nous n’avions donc aucune excuse valable pour nous essayer aux langages à la mode hyper performants. L’heure était arrivée !

Le choix d’Elixir

La source de notre problème était la longueur des requêtes conditionnées par la réponse d’un webservice lent. Nous cherchions donc une technologie à l’aise avec l’asynchronisme. Node, Akka, Rust étaient tous d’excellents candidats, mais c’est Elixir qui sortit du lot.

Elixir fonctionne sur la VM (Virtual machine) Erlang, une VM conçue pour gérer une myriade de connexions parallèles comme le trafic de Whatsapp par exemple. Cependant la syntaxe d’Erlang est assez complexe, c’est pourquoi les gens de chez Platformatec (eux aussi aficionados de Ruby) ont construit, sur la VM Erlang, un langage s’inspirant de la syntaxe Ruby. C’est ainsi que naquit Elixir.

Un voyage en train et une douzaine de commits

Tout commença par une chaude après-midi de Juillet, à bord d’un train reliant Paris à Toulouse. J’ouvrais pour la première fois l’excellent site

Elixir school, et ne put en sortir pendant les 4 heures du trajet. Non seulement la syntaxe m’était familière, mais les outils de l’écosystème eux aussi me rappelaient ceux de Ruby (mix est un mélange de bundle et rake, tandis que plug est l’équivalent de rack). Dès les premiers chapitres, je n’avais qu’une idée en tête, me lancer dans un premier projet. La connectivité à bord du train réfréna mes ardeurs, ne pouvant pas télécharger la VM Erlang. Je me suis donc contenté des chargements de page de 30s, et ai lu toute la documentation de ce langage, sans pouvoir en exécuter une seule ligne.

De retour à mon bureau, je sautai sur mon terminal pour installer la VM Erlang ainsi que les outils Elixir en un clin d’oeil (les dieux du réseau sont si injustes). Le démarrage du projet – l’écriture d’un proxy – s’avéra être un jeu d’enfant tant la syntaxe et les outils me paraissaient familiers. Grâce à une base d’un projet similaire, j’ai pu penser cette application dans une optique fonctionnelle, avec des fonctions chaînées et des données immutables. Certaines syntaxes étaient parfois surprenantes comme le pattern matching dans les paramètres de fonction. Par exemple, que peut à votre avis bien faire le code ci-dessous ?

defp put_resp_headers(conn, []), do: conn
defp put_resp_headers(conn, [{header, value} | rest]) do
conn
  |> Conn.put_resp_header(header |> String.downcase, value)
  |> put_resp_headers(rest)
end

Réponse : cette fonction travaille récursivement, extrayant un couple header/valeur du second paramètre à chaque appel, jusqu’à ce que le second paramètre soit vide.

En avançant par tâtonnement, je retrouvais avec joie le sentiment grisant de tout apprendre et de progresser à vue d’oeil. Il fallut une douzaine de commits seulement pour avoir un prototype branché sur la base de données de Pericles.

Quelques commits supplémentaires pour gérer les cas de bord, tester le projet et le rendre configurable, et le projet était prêt à être déployé. Une fois encore notre fidèle ami Heroku ne nous a pas déçu. Un seul git push, et ce premier projet Elixir était accessible sur le web.

Aucune technologie n’est parfaite

Bien évidemment tout n’a pas marché du premier coup et nous avons fait face à quelques obstacles sur le chemin..

Le premier en enquêtant sur un problème de timeout déclenché au bout de 5s ( qui a son importance avec notre problème initial de webservices de 20s).

Il s’est avéré que le paramètre nommé timeout, utilisé dans mon client HTTP, n’était pas du tout ce qu’il pouvait laisser penser. Le bon paramètre était recv_timeout. J’imagine qu’une fois plus mature, la librairie n’aura plus ce problème d’ambiguïté dans la nomenclature de ses paramètres.

Mais des noms de paramètres discutables ne sont pas la seule complication quand on joue avec la dernière technologie à la mode. Parfaitement fonctionnelle sur ma machine, éprouvée par une batterie de tests unitaires, j’étais plutôt confiant au déploiement de la première version du proxy.

Mais bien que les premières requêtes retournèrent correctement la réponse des webservices via le proxy, aucun rapport de proxy n’est apparu dans la base de données.

Sans rentrer dans le détail, on pourrait comparer ce cas à une fonction retournant normalement, mais où la moitié du travail manquerait à l’appel. Le problème se produisant uniquement sur le serveur à distance, chaque tentative de modification d’un paramètre ou d’ajout d’un log nécessitait un déploiement.

Bien qu’Heroku simplifie considérablement la tâche, j’ai tout de même passé quelques heures à résoudre cette anomalie. J’ai fini par trouver une solution, en créant le rapport avant de préparer la réponse à envoyer.

Aucune trace dans la documentation d’un possible effet de bord dans la préparation de la réponse, ni d’une différence de comportement possible entre environnement local et production. J’ai pris le temps d’interroger la communauté Elixir sur ce comportement étrange, mais n’ai reçu aucune explication pour le moment.

Pour finir, un autre bug était difficile à résoudre. Après de fastidieuses recherches, j’ai fini par comprendre qu’un crash de mon application était causé par un bug d’une librairie Erlang. Celle-ci était utilisée par mon client HTTP Elixir, et le bug bien que déjà signalé, était toujours ouvert.

L’un des avantages lorsqu’on travaille avec des librairies open source est de pouvoir y contribuer et résoudre ses problèmes soi-même. Dans ce cas cela signifiait cependant contribuer sur un bug très spécifique, dans une librairie écrite dans un langage qui m’était complètement inconnu (Erlang).

Le jeu en vaut la chandelle

Même si perfectible, Elixir reste un excellent choix pour les Rubyistes faisant face à des problématiques de gestion de connexions concurrentes. L’apprentissage est grandement facilité par la connaissance de Ruby, et le gain de performance plus que conséquent.

La stratégie consistant à réécrire en Elixir, uniquement une petite partie de notre application est rétrospectivement un très bon compromis. Pour un investissement mineur, l’amélioration des performances globales du système a été considérable. Si vous choisissez cette route, il est possible que vous rencontriez des problèmes similaires à ceux exposés ici, mais soyez en sûrs, le jeu en vaut la chandelle.

Et vous, quel est votre point de vue ?

Echangeons
à lire
BACK
EVENTS
Scala.io v5 : retour de FABERNOVEL TECHNOLOGIES sur l’édition 2018

Cette année encore, notre équipe a participé à Scala.io, la conférence de l’année sur l’écosystème Scala. Découvrez ici nos retours sur ces trois jours inspirants !

API
BACK
DEVOPS
EVENTS
DevFest Nantes 2018 : retour sur la première édition pour FABERNOVEL TECHNOLOGIES

Découvrez ici nos retours sur l’événement incontournable de la région nantaise, DevFest, qui a eu lieu en octobre cette année.