L’essence d’Erlang, ce ne sont pas les processus légers ni le passage de messages (2023)
(stevana.github.io)- Le point clé qui a permis à Erlang de construire des systèmes distribués fiables n’est pas tant les processus légers eux-mêmes que les behaviours, qui généralisent des motifs récurrents de concurrence et de gestion des pannes
- Les behaviours fournissent des points d’implémentation comme des interfaces, tout en masquant la gestion de la concurrence dans des composants communs comme
gen_server, ce qui rapproche le code applicatif d’une logique séquentielle - La thèse de Joe Armstrong présente comment de grands systèmes comme AXD301 ont été construits à partir de petits composants tels que
gen_server,gen_event,gen_fsm,supervisor,applicationetrelease supervisorredémarre les processus en échec selon des stratégies commeone_for_oneetone_for_all, et la philosophie « Let it crash! » déplace la responsabilité de la reprise vers l’arbre de superviseurs- Les behaviours sous forme de machines à états facilitent les tests par simulation et la vérification formelle, permettant aux développeurs de se concentrer sur la sémantique du problème plutôt que sur la complexité de la concurrence ou du simulateur
Le problème qu’Erlang cherchait à résoudre
- Erlang a commencé comme un outil destiné à construire des systèmes distribués fiables
- Au départ, c’était une bibliothèque Prolog pour créer des systèmes distribués fiables, avant de passer par un dialecte de Prolog puis de devenir un langage indépendant
- Il a été utilisé pour programmer les commutateurs téléphoniques d’Ericsson, avec dans les années 80 et 90 des volumes de trafic à l’échelle de centaines de millions d’utilisateurs et des SLA très stricts
- Ericsson a interdit l’usage d’Erlang en 1998, et l’équipe de développement a soutenu que, s’il fallait l’interdire, il valait mieux le publier en open source ; Ericsson l’a effectivement fait
- Joe Armstrong a été l’une des figures centrales de la conception et de l’implémentation d’Erlang, et il a commencé en 2002 au SICS une thèse achevée en 2003 sous le titre Making reliable distributed systems in the presence of software errors
- Cette thèse se concentre moins sur les mathématiques ou la théorie que sur les idées derrière Erlang et l’expérience acquise dans la construction de systèmes distribués fiables
- La grande idée d’Erlang n’est pas celle des processus légers et du passage de messages, mais celle des composants génériques qu’Erlang appelle behaviours
Behaviours : le couplage entre interface et infrastructure
- Les behaviours d’Erlang sont des ensembles de signatures de types pouvant avoir plusieurs implémentations, un peu comme les interfaces en Java ou en Go
- Si le programmeur fournit l’implémentation de cette interface, il peut utiliser des fonctions génériques écrites contre cette interface
- La différence essentielle est que les behaviours vont au-delà d’une simple interface en fournissant aussi du code d’infrastructure
- Le programmeur applicatif écrit la sémantique du problème, autrement dit la logique métier
- Le code d’infrastructure, comme la concurrence, est fourni automatiquement par le behaviour
- Les behaviours encapsulent des bonnes pratiques écrites par des experts expérimentés
- Si tout le système réutilise un petit ensemble de behaviours, toute amélioration de leur implémentation peut améliorer le système sans modifier le code applicatif
- L’usage des behaviours impose une structure qui facilite les tests et la vérification formelle
gen_server : la concurrence cachée derrière du code séquentiel
- L’exemple de
gen_serverest un magasin clé-valeur :storeenregistre une paire clé-valeur etlookupretrouve la valeur associée à une clé - Le callback central,
handle_call, met à jour l’étatDictpour une requêtestore, et interroge cet état pour une requêtelookup - En fournissant cette implémentation à
gen_server, on obtient un serveur capable de traiter des requêtesstoreetlookuparrivant en parallèle - Le point important est que
handle_calllui-même est entièrement séquentiel- La concurrence est cachée à l’intérieur du composant générique
gen_server - Le code applicatif se rapproche ainsi d’une forme qui prend un état et une entrée pour produire un nouvel état et une sortie
- La concurrence est cachée à l’intérieur du composant générique
- La thèse de Joe Armstrong étend cette approche à
gen_server,gen_event,gen_fsm,supervisor,applicationetrelease
Le cas AXD301 et l’échelle des behaviours
- Joe Armstrong prend comme étude de cas le commutateur téléphonique AXD301 d’Ericsson
- Le projet AXD301 comprenait les éléments suivants
- 122 instances de
gen_server - 36 instances de
gen_event - 10 instances de
gen_fsm - 20 superviseurs
- 6 applications
- Le tout empaqueté dans une seule release
- 122 instances de
- AXD301 est un système de plus d’un million de lignes de code Erlang
- Le fait qu’un système de cette taille ait été construit à partir d’un petit ensemble de behaviours constitue un argument central en faveur de la structure d’Erlang
Le rôle des autres behaviours
gen_eventest un gestionnaire d’événements générique- Il permet d’enregistrer des gestionnaires d’événements, puis exécute ces gestionnaires quand le gestionnaire d’événements reçoit les messages correspondants
- Joe Armstrong cite par exemple la journalisation d’erreurs
- Le logger d’exemple peut enregistrer et signaler les cinq messages d’erreur les plus récents
gen_fsm, renommé plus tardgen_statem, est un behaviour de machine à états- Il est mieux adapté à l’implémentation de protocoles
- Les protocoles sont souvent spécifiés comme des machines à états
- On peut considérer que n’importe quel
gen_serverpeut être implémenté avecgen_statem, et inversement
applicationse compose d’un arbre de superviseurs et des autres éléments nécessaires à la livraison de l’applicationreleaseempaquette une ou plusieurs applications- Elle inclut aussi le code de gestion des mises à niveau
- En cas d’échec d’une mise à niveau, il doit être possible de revenir au précédent état stable
supervisor et « Let it crash! »
supervisorest un processus qui vérifie que d’autres processus fonctionnent correctement- Si un processus surveillé échoue, le superviseur peut le redémarrer selon une stratégie prédéfinie
- Dans l’exemple, la stratégie
{one_for_one, 5, 1000}signifie ceci- Si
packet_assembler,kvousimple_loggeréchoue, seul le processus en échec est redémarré - S’il faut redémarrer plus de 5 fois en 1000 secondes, le superviseur lui-même échoue
- Si
permanent, 500, workersignifie que ce processus est un worker qui doit toujours rester actif, et que le superviseur lui laisse 500 ms pour s’arrêter proprement lorsqu’il tente de le redémarrer- Avec la stratégie
one_for_all, l’échec d’un processus provoque le redémarrage de tous les processus enfants - Un superviseur peut surveiller d’autres superviseurs, et n’a pas nécessairement besoin de s’exécuter sur la même machine
- Cette structure fonctionne au niveau des threads/processus légers, et non au niveau des conteneurs Docker ; il est donc difficile de la réduire à « juste Kubernetes »
- « Let it crash! » suppose qu’un processus en échec sera redémarré
- Le programme n’exprime que le chemin nominal
- Si quelque chose tourne mal sur ce chemin nominal, il ne cherche pas à récupérer lui-même et plante
- Un autre programme, plus haut dans l’arbre des superviseurs, prend alors en charge le traitement
- Il n’existe pas de preuve systématiquement collectée de la stabilité à long terme d’AXD301, mais la thèse de Joe Armstrong mentionne une diapositive PowerPoint d’un grand client affirmant qu’un système à 11 nœuds était exploité avec une fiabilité de 99.9999999 %
- La manière dont ce chiffre a été obtenu n’est pas documentée
- D’autres chiffres de rapports sur les temps d’arrêt sont également accompagnés de la réserve que leur méthode de calcul n’est pas claire
Implémenter des behaviours dans d’autres langages
- Les processus légers et le passage de messages ne suffisent pas à eux seuls à faire d’Erlang un bon choix pour les systèmes fiables
- Une explication plus honnête met l’accent sur la structure fournie par les behaviours et sur la manière dont cette structure conduit à du logiciel fiable
- La signature d’interface de
gen_serverpeut être vue sous la forme suivanteInput -> State -> (State, Output)- Autrement dit, on prend une entrée et l’état courant pour produire un nouvel état et une sortie
- On peut imaginer la transformation de cette signature séquentielle en traitement de requêtes concurrentes de la façon suivante
- Un serveur HTTP transforme les requêtes en
Inputet les place dans une file - Une boucle d’événements retire les entrées de la file et les transmet à l’implémentation séquentielle
- La sortie est réécrite comme réponse au client
- Pour prendre en charge plusieurs
gen_server, on peut donner un nom à chaque serveur et inclure à la fois ce nom et l’entrée dans la requête
- Un serveur HTTP transforme les requêtes en
gen_eventpeut être implémenté en autorisant l’enregistrement de callbacks pour certains types d’événements dans la filesupervisorpeut être simplifié comme un encapsulage des appels de fonctionsgen_serverdans un gestionnaire d’exceptions qui notifie le superviseur en cas d’exception- Cela devient plus complexe si le superviseur ne s’exécute pas sur la même machine
applicationetreleasesont importants car la configuration, le déploiement et les mises à niveau sont des problèmes difficiles, mais ils ne sont pas traités en profondeur ici
Behaviours et testabilité
- La structure des behaviours d’Erlang mène naturellement aux tests par simulation et à la vérification formelle
- Un sujet d’intérêt récent est le test par simulation de systèmes distribués dans le style de FoundationDB
- Le test par simulation consiste à exécuter le système dans un monde simulé où la simulation contrôle entièrement le moment d’envoi des messages réseau
- Pour cela, FoundationDB a créé un dialecte de C++ avec des acteurs, ou bien son propre langage de programmation
- Cette approche semble aller déjà très loin avec le seul type de machine à états suivant
Input -> State -> (State, [Output])[Output]représente une séquence de sorties
- Le simulateur gère les messages avec une file de priorité ordonnée par temps d’arrivée
- Il retire un message
- Il avance l’horloge jusqu’au temps d’arrivée de ce message
- Il transmet le message à la machine à états réceptrice
- Il génère de nouveaux temps d’arrivée pour les messages de sortie avant de les réinsérer dans la file
- Si tout est déterministe et que les temps d’arrivée sont générés à partir d’une seed, il devient possible d’explorer de nombreux interleavings et d’obtenir des échecs reproductibles
- Cette approche est plus rapide que Jepsen, car les messages sont traités en mémoire et l’horloge avance directement jusqu’au temps d’arrivée au lieu d’attendre des timeouts
- Cette forme de machine à états est appelée « network normal form », ce qui conduit à l’hypothèse que tout programme recevant et envoyant sur le réseau pourrait être refactoré sous cette forme
- Le fait que
gen_serveretgen_statempartagent en pratique la même structure de type suggère que cette structure n’a rien d’arbitraire
Vérification formelle et structure en machine à états
- Joe Armstrong a déjà expliqué dans une conférence qu’il est difficile d’implémenter correctement une élection de leader distribuée
- Avec un simulateur, ce problème pourrait être considérablement simplifié
- De la même manière qu’une soufflerie aide à concevoir des avions, on pourrait tester avant la production des conditions extrêmes comme un réseau instable ou une coupure de courant
- Ce simulateur pourrait être générique ou paramétré pour les behaviours
- Les développeurs n’auraient pas besoin d’écrire le simulateur séparément
- La complexité serait cachée derrière le behaviour, comme le code de concurrence dans
gen_server
- À propos des tests de FoundationDB, Kyle « aphyr » Kingsbury a tweeté que les tests de FoundationDB semblaient bien plus stricts que ses propres tests Jepsen
- La vérification formelle devient elle aussi plus simple quand le programme est écrit comme une machine à états
- Le model checking TLA+ de Lamport suppose que la spécification est une machine à états
- Kleppmann montre comment traiter le problème d’explosion de l’espace d’états via une induction structurelle fondée sur la structure en machine à états
- La structure obtenue grâce aux behaviours d’Erlang peut donc être réutilisée pour rendre plus abordables les problèmes que Joe Armstrong jugeait difficiles
Travaux connexes et ressources
- Parmi les travaux connexes qui reprennent des idées d’Erlang, on trouve
- Des travaux inspirés du LMAX Disruptor de Martin Thompson et de aeron pour construire une boucle d’événements rapide sur laquelle exécuter des behaviours
- Des travaux qui ajoutent l’I/O asynchrone au type de machine à états
- Des travaux qui implémentent plus en détail les supervisors
- Le swapping à chaud du code des machines à états
- Comme ressources, sont cités la thèse de doctorat de Joe Armstrong, les OTP design principles, ainsi que la documentation de
gen_server,gen_event,gen_statem,supervisor,applicationet release - Sur la relation entre le modèle acteur et Erlang, il est indiqué que les développeurs d’Erlang ont créé Erlang sans connaître le modèle acteur, et qu’un article de Carl Hewitt documente les différences entre les processus Erlang et ce modèle
- Akka dispose d’« actors » et d’arbres de superviseurs, mais ne semble pas avoir d’équivalent aux autres behaviours d’Erlang, et sa notion de « behavior » diffère elle aussi des behaviours d’Erlang
1 commentaires
Commentaires sur Hacker News
Ce qui est remarquable avec Erlang et BEAM, c’est la profondeur des fonctionnalités. Dans l’article d’origine, le point marquant était Behaviour/Interface, mais pour moi, ce qui compte davantage, c’est qu’il permet de construire des systèmes complexes avec beaucoup moins de ressources de développement que d’autres langages
OTP lui-même contient énormément de choses. J’ai travaillé à compiler Elixir pour le faire tourner sur des appareils iOS, et j’ai pu non seulement utiliser le processus de release d’Erlang, mais aussi la bibliothèque ei pour compiler un nœud en C et le faire communiquer avec d’autres nœuds Erlang — Erlang, Elixir, Gleam, etc. — comme dans un réseau distribué classique
J’ai aussi pu appeler des fonctions d’une application Elixir depuis C via la bibliothèque rpc d’Erlang. Il y a un surcoût d’encodage/décodage, et une FFI serait plus rapide, mais cela tenait largement dans notre budget de latence, et j’ai pu monter en quelques jours une fonctionnalité dont je n’avais même jamais entendu parler auparavant
Le point plus général, c’est qu’Erlang a déjà résolu il y a des décennies, en termes de passage à l’échelle et de coût d’implémentation, beaucoup de problèmes avec lesquels les stacks modernes se débattent encore. HN affiche parfois une affection un peu putaclic pour Erlang/Elixir sans que cela se traduise vraiment par des adoptions concrètes, et certaines entreprises brûlent de l’argent à réimplémenter ce que la stack Erlang fournit gratuitement par défaut
Le projet sur lequel j’ai travaillé était un pipeline de données backend, avec un volume de données pas particulièrement élevé, et pourtant il était incroyablement difficile d’isoler précisément les bugs critiques
Au fil du processus, j’ai découvert plusieurs caractéristiques de Node.js, et en les comparant à Elixir/Erlang/OTP, j’en suis arrivé à la conclusion que Node.js est, par conception, moins fiable
J’ai aussi beaucoup fait de Ruby et touché à Python, mais de nombreuses plateformes de langage de la génération actuelle ont du mal à construire des systèmes distribués fiables. La VM BEAM et la plateforme OTP ont déjà résolu cette partie
Par exemple, dans la plupart des systèmes BEAM, le GenServer qui joue le rôle de worker se résume au bout du compte à appeler plusieurs fonctions avec des paramètres simples
On peut donc tester en appelant directement ces fonctions, en passant les paramètres à la main, puis en vérifiant seulement la sortie. Pas besoin de mettre en place un système de test complexe pour gérer du code asynchrone, ni de bloquer les tests en attendant la fin d’un travail
C’est un point que les juniors ratent souvent, mais une fois qu’on le comprend, c’est assez libérateur
J’ai vu plusieurs personnes, surtout des managers, vouloir écrire un livre à partir de notre expérience. Ce qui m’a toujours frustré, c’est que nous ne voyions pas les mêmes choses comme la raison essentielle de notre réussite. Ce que je considérais indispensable, ils avaient tendance à le réduire à quelque chose de simplement appréciable
Ici aussi, quelqu’un dit que les processus légers et le passage de messages ne sont pas la sauce secrète, tout en passant à côté du fait que voir Erlang comme des processus séquentiels communicants (CSP) est indissociable de ces propriétés. Et pourtant, il mentionne à plusieurs reprises CSP comme faisant partie de cette sauce secrète
Par exemple, le programmeur applicatif écrit du code séquentiel et la concurrence est cachée dans les behaviours ; ou la logique métier est séquentielle, ce qui permet à un nouveau membre de l’équipe de démarrer facilement ; ou encore les superviseurs et la philosophie « let it crash » permettent de construire des systèmes fiables
Les behaviours sont intéressants, étaient courants dans les années 80 et résolvent un problème sur lequel certains travaillaient encore dans les années 2000, mais dans Erlang ils sont à la fois une fin et un moyen. C’est la manière dont ces autres propriétés ont été implémentées, même si je ne sais pas si Erlang devait nécessairement fonctionner ainsi pour rester vraiment Erlang
CSP est plutôt la lignée qui, via occam et d’autres langages, a inspiré les channels de Go. La différence la plus nette est la synchronisation sur des channels non bufferisés, mais il y a aussi des différences comme le pattern matching sur les boîtes aux lettres dans le modèle d’acteurs
Le débat CSP contre modèle d’acteurs est assez intéressant, parce qu’ils se ressemblent en surface mais ont en réalité des implications très différentes
Je trouve que le modèle d’acteurs est plus pertinent, mais cela peut beaucoup varier selon les personnes
Je suis venu ici parce que je voulais en savoir plus sur les raisons pour lesquelles Ericsson a cessé d’utiliser Erlang, et pourquoi Joe a été licencié
En résumé, Ericsson a basculé ses nouveaux projets vers Java, ce qui semble avoir marginalisé Erlang. Ensuite, Joe et ses collègues ont créé Bluetail en 1998, qui a été rachetée par Nortel
Nortel était un géant des télécoms qui représentait environ un tiers de la valeur de la Bourse de Toronto. En 2000, son action a atteint 125 dollars, mais en 2002 elle était tombée sous 1 dollar. Cela faisait partie de l’éclatement de la bulle Internet, et avec la chute brutale des dépenses télécoms, Nortel a été particulièrement touchée
Il semble plus prudent de voir le licenciement de Joe comme le fait que « son département a été le premier à prendre l’eau sur un navire en train de couler ». Nortel a licencié 60 000 personnes, soit plus des deux tiers de ses effectifs. Ce licenciement n’était pas le signe que Joe ne faisait pas son travail, ni la preuve d’une inefficacité de son unité, mais une mesure massive et désespérée
« fired » porte un jugement de valeur fort et suggère un licenciement pour faute. Même si cela avait été le cas, l’auteur de l’article d’origine ne pouvait pas le savoir, et cela ne le regardait pas
C’est à cause des processus légers et du passage de messages que j’ai commencé à m’intéresser de nouveau à Erlang, et jusqu’ici les behaviours étaient secondaires
Le projet consiste à apporter la programmation visuelle basée sur les flux (FBP) à Erlang. La FBP semble avoir été faite pour Erlang, et j’ai été surpris de ne pas voir d’implémentation existante
L’outil que j’utilisais surtout pour la FBP était Node-RED ; l’idée de base est donc de connecter le frontend Node-RED à un backend Erlang et de faire de chaque nœud un processus. Le frontend Node-RED se prête bien à la modélisation du passage de messages entre nœuds, ce qui permet un mapping 1:1 très simple avec les processus et les messages Erlang
J’ai implémenté une partie des fonctionnalités de base, et commencé à transformer les flux en tests unitaires afin d’ajouter progressivement des fonctionnalités. Ce serait bien d’être compatible à 100 % avec Node-RED, le backend Node.js. Plus de détails dans le dépôt GitHub → https://github.com/gorenje/erlang-red
Dans l’ensemble, Erlang convient étonnamment bien à ce cas, et je suis surpris que personne n’ait fait quelque chose de similaire. Peut-être que cela existait déjà ?
[1] = https://jpaulm.github.io/fbp/index.html
Pour moi, la force d’Erlang/Elixir ne réside pas tant dans l’implémentation du modèle d’acteurs, le matching venu de Prolog, l’immutabilité ou les behaviours eux-mêmes, que dans la volonté de Joe de montrer qu’on peut faire plus avec moins
C’est un système de calcul bien conçu et éprouvé, avec une cohérence rare dans les autres langages comme dans le monde du « web ». Il n’est pas parfait, mais il est assez impressionnant
Malheureusement, dans le monde du logiciel, je trouve qu’on reconnaît et adopte très peu ce que la simplicité rend possible. La complexité permet aux gens de devenir experts, aux managers de justifier de grandes équipes et de nombreuses réunions, et aux experts de rester experts
Erlang a été développé à une époque où les entreprises cherchaient à mettre en œuvre des solutions logicielles avec moins de personnes et des performances limitées. Puis, au fil des décennies, l’argent a afflué dans ce domaine, et la valeur selon laquelle « moins signifie plus, d’une manière bénéfique pour tous » est devenue moins attrayante
Le concept le plus intéressant dans Erlang/BEAM est que la récupération partielle est intégrée dès le départ
Lorsqu’on rencontre un état inattendu, au lieu de tuer tout le processus ou de continuer en acceptant les dégâts, on revient à un état sain connu au niveau le plus fin possible
Cette idée a été étudiée il y a longtemps sous le nom de « microreboots » et était aussi liée au « crash-only software », mais seul Erlang/BEAM en a fait un concept de premier ordre du système d’exécution
Erlang fournit un bon modèle pour traiter ce genre de problèmes, mais cela ne veut pas dire qu’on peut complètement arrêter de réfléchir. Si l’on part du principe qu’il suffit de laisser redémarrer et que tout ira bien, on peut se brûler
Erlang, OTP et BEAM offrent davantage que les behaviours. La VM ressemble plutôt à un noyau virtuel doté de superviseurs, de processus isolés et d’un mode distribué qui traite plusieurs machines physiques/virtuelles comme un seul pool de ressources
OTP fournit aussi plusieurs modes utiles, comme la base de données Mnesia, des compteurs atomiques pour le cache et des tables ETS. Le runtime prend également en charge le rechargement à chaud du bytecode, ce qui permet d’appliquer des correctifs sans interruption du système. La syntaxe n’est pas très compatible avec les lecteurs d’écran, mais elle reste lisible
La seule plateforme qui me vient à l’esprit comme comparable à BEAM dans sa capacité à traiter les ressources de plusieurs machines comme un pool unique est à peu près Apache Mesos[1]
Il y a un peu plus d’un an, ma société de conseil personnelle a adopté Erlang comme langage backend. Depuis, nous avons commencé à explorer l’intérieur de BEAM, par exemple en remplaçant une stack TCP par QUIC ou en intégrant des correctifs Rust
C’est un excellent choix pour des systèmes légers et à haut débit qui ne tombent pas en panne sauf en cas de kernel panic ou de perte d’alimentation. Nous développons actuellement des logiciels très sollicités et fortement concurrents, comme un outil de suivi de production pour films/jeux et un gestionnaire de pipeline, et nous préparons aussi de la R&D pour un service de gestion de cliniques privées
[1]: https://mesos.apache.org/
Il est très probable que nous devions maintenir nous-mêmes des bindings ou des implémentations internes pour les choses qui ne sont pas maintenues côté Elixir
Il y a aussi beaucoup de sucre syntaxique, et les utilisateurs semblent avoir une étrange obsession pour l’abstraction des choses sous forme d’interfaces DSL
À la question « pourquoi les concepteurs de langages et de bibliothèques ne copient-ils que les idées de processus légers et de passage de messages, sans voler la structure derrière les behaviours d’Erlang ? », il y a une raison.
Les signatures de fonctions des behaviours Erlang sont fortement liées à d’autres caractéristiques d’Erlang, en particulier à sa manière inhabituelle d’utiliser l’immuabilité. C’est aussi pour cela qu’un serveur a besoin d’un appel
initséparé, et que la gestion de l’état doit prendre une forme très nette pour fonctionner de la même manière.Mais, dans d’autres langages, atteindre le même objectif implique presque toujours de ne pas copier tel quel ce que fait Erlang. Si quelqu’un dit avoir « porté gen_server dans un autre langage » et montre exactement la même interface qu’Erlang, j’y vois un portage qui ne comprend pas en profondeur ce que fait Erlang.
Quand j’ai transposé l’idée de supervisor tree en Go[1], je l’ai fait d’une manière idiomatique pour Go. En Go moderne, la bonne interface pour quelque chose de « supervisable » n’est pas une signature à la Erlang, mais simplement ceci :
type Service interface {
Serve(context.Context)
}
En Go, c’est tout, et c’est à peu près tout ce qu’il faut utiliser. Dans d’autres langages, ce sera différent. Go a des canaux, donc on n’a pas besoin de
handle_event/2, et il faut les utiliser. Non pas parce que les canaux seraient meilleurs ou moins bons, mais parce que c’est ainsi que ce langage fonctionne.Dans d’autres langages, on peut utiliser autre chose, et dans d’autres infrastructures, au lieu « d’appeler
handle_event/2», on peut aussi envoyer vers Kafka ou vers un bus d’événements cloud. L’essentiel est de construire un système orienté événements, pas de copier l’implémentation exacte d’Erlang.La communauté Erlang a un problème : elle est excessivement convaincue qu’il y a quelque chose d’extraordinairement spécial dans la manière exacte dont Erlang fait les choses, et que toute autre approche est forcément mauvaise et peu fiable. C’était peut-être vrai en 2005, mais plus en 2025.
À une époque, Erlang était quasiment la seule réponse qui avait du sens, mais le problème en 2025 est de naviguer dans une profusion de réponses. Je recommande vivement d’apprendre d’Erlang pour construire des logiciels fiables, mais je déconseille fortement de porter aveuglément dans d’autres langages la manière exacte dont Erlang y est parvenu. Dans presque tous les autres contextes de langage, c’est la mauvaise réponse. Même d’autres langages immuables ont des structures suffisamment différentes pour qu’on ne puisse pas simplement copier tel quel.
[1]: https://jerf.org/iri/post/2930/
En tant que personne qui utilise Elixir professionnellement, c’est pour moi la question la plus intéressante à propos d’Erlang.
Quand on voit le grand succès de k8s, Kafka et des produits de systèmes distribués d’AWS, il est clair qu’il existe une énorme demande pour des outils aidant à concevoir des systèmes concurrents fiables. Alors pourquoi Erlang/Elixir n’ont-ils pas capté cette part ?
J’en discute souvent avec des amis, mais je n’ai pas la réponse.
handle_event/2» me laisse dubitatif. Quel type de canal ? Plus important encore, les canaux sont locaux au processus ; pour les rendre réseau, il faut du code de liaison.Je pense qu’Erlang a probablement abstrait le traitement des messages réseau. En plus, j’ai vu trois ou quatre variantes du pattern proposé pour des choses comme des serveurs longue durée.
Je suis entièrement d’accord qu’un portage doit utiliser les briques idiomatiques du langage. Mais un langage peut avoir des mécanismes cachés qui font perdre, pendant le portage, l’essence même qui avait de la valeur. Une sorte d’anti-relativisme des langages de programmation, en somme.
« Des canaux ? Il suffit de les envelopper avec X » peut aussi être bien plus nuisible à l’interopérabilité que ça n’en a l’air. Par exemple, si l’on regarde
http.Handleren Go, c’est simple, mais le fait qu’il soit dans la bibliothèque standard a de grandes implications pratiques. Cela a créé un écosystème de middleware globalement compatible, même sans coordination préalable.io.Readeret ses proches jouent un rôle similaire. On peut considérer que ces interfaces extrêmement simples valent plus que les implémentations elles-mêmes.Ce n’est qu’une supposition, mais si Erlang a bien défini de nombreuses interfaces pour les systèmes distribués fiables, c’est peut-être précisément ce qui a rendu l’ensemble possible.
Je ne suis pas d’accord. Une interface est un concept trivial que l’on peut ajouter à n’importe quel langage. Même dans les langages sans syntaxe officielle d’interface, on peut l’imiter dans l’espace du programme.
Si BEAM a réussi, c’est parce qu’il permet de faire tourner un million de processus sur un seul nœud, d’exprimer facilement des machines à états distribuées complexes, et de redémarrer des parties du système sans interruption. Et il y a beaucoup d’autres raisons.
Je ne pense vraiment pas que les behaviours/interfaces soient la pièce la plus centrale.
Pour des tas de processus isolés, on peut utiliser des processus OS ; pour les arbres de supervision, Kubernetes ; pour le passage de messages, deux threads Java et une file partagée ; le chargement de code à chaud est aussi possible en Java ; pour le traitement à faible latence, un LMAX Disruptor bien réglé peut même battre Erlang, etc.
Mais l’essentiel est de rassembler tout cela dans une seule plateforme ou bibliothèque. Les processus OS sont lourds, et il n’est pas facile d’en faire tourner deux millions sur un serveur. Si l’on utilise des green threads ou des promises, on perd les tas isolés.
Kubernetes peut aussi convenir dans une certaine mesure, mais il ne gère pas très bien les arbres de supervision imbriqués. Ce serait possible, mais en plus du code, on se retrouve avec des pods, des contrôleurs, des volumes et toutes sortes d’autres éléments.
On peut faire du passage de messages avec des bibliothèques d’acteurs dans plusieurs langages, mais elles n’intègrent pas de façon transparente le pattern matching dans
receiveni l’envoi vers un autre thread sur un autre nœud.Le chargement de code à chaud est possible aussi, mais la question est de savoir comment traiter les structures de données et l’état à l’exécution. Erlang a été conçu autour de cela, et comme l’état de
gen_serverest immuable et explicite, il dispose d’un callback pour mettre à niveau non seulement le code, mais aussi l’état lui-même.Je ne dirais pas que BEAM supprime ce problème, mais il semble réduire la pente de la courbe. Des idiomes et fonctionnalités cohérents entre eux, incluant le déploiement, l’auto-réparation et l’équilibrage de charge, réduisent les frictions entre modules.
Cela permet de créer des systèmes où 12 ingénieurs peuvent gérer facilement 30 endpoints, et où la surface peut encore suivre une loi de puissance.
Je ne suis pas d’accord avec le contenu de cet article. Les behaviours sont rendus possibles par l’architecture sous-jacente du système
Un behaviour n’est pas une interface, il est plus proche d’un objet abstrait dans un langage comme Java. Il implémente une fonctionnalité fondamentale et autonome, cachée derrière une interface de collaboration, mais il ne peut pas faire grand-chose sans l’infrastructure de base qui garantit que chaque processus est totalement isolé des autres, que tous les processus peuvent être arrêtés en toute sécurité sans fuite de mémoire ni de ressources, et qu’il est impossible de partager des pointeurs dangereux entre deux processus
Ce que Joe a montré dans son article, c’est comment construire un système fiable, voire dans une certaine mesure un système distribué fiable, à partir d’un ensemble donné de briques Lego
Pour implémenter correctement quelque chose comme ça, il faut la VM Erlang, et ce n’est pas entièrement implémentable sur d’autres VM. Sans la plomberie de bas niveau, les arbres de supervision fuient. En Java, on ne peut pas tuer un thread qui détient des ressources et espérer que tout se passe toujours bien, et il manque aussi des moyens de surveiller des processus distincts