1 Punkte von GN⁺ 2025-04-13 | 1 Kommentare | Auf WhatsApp teilen
  • Der Schlüssel dazu, dass Erlang zuverlässige verteilte Systeme ermöglichen konnte, lag weniger in den leichtgewichtigen Prozessen selbst als in den behaviours, die wiederkehrende Muster für Nebenläufigkeit und Fehlerbehandlung verallgemeinern
  • behaviours stellen wie Interfaces Implementierungspunkte bereit, verbergen aber zugleich, wie bei gen_server, die Nebenläufigkeitsverarbeitung in gemeinsamen Komponenten und lassen Anwendungscode eher wie sequenzielle Logik wirken
  • Joe Armstrongs Dissertation behandelt, wie große Systeme wie AXD301 aus kleinen Bausteinen wie gen_server, gen_event, gen_fsm, supervisor, application und release aufgebaut wurden
  • supervisor startet fehlgeschlagene Prozesse mit Strategien wie one_for_one oder one_for_all neu; die Philosophie „Let it crash!“ verlagert die Verantwortung für Recovery in den Supervisor-Baum
  • behaviours in Form von Zustandsmaschinen erleichtern Simulationstests und formale Verifikation; Entwickler können sich auf die Semantik des Problems konzentrieren statt auf die Komplexität von Nebenläufigkeit und Simulatoren

Das Problem, das Erlang lösen sollte

  • Erlang begann als Werkzeug zum Bau zuverlässiger verteilter Systeme
    • Anfangs war es eine Prolog-Bibliothek zum Bau zuverlässiger verteilter Systeme; später wurde es über einen Prolog-Dialekt zu einer eigenständigen Sprache
    • Es wurde zur Programmierung von Telefonvermittlungen bei Ericsson eingesetzt und bewältigte in den 80er- und 90er-Jahren Traffic in der Größenordnung von Hunderten Millionen Menschen sowie strenge SLAs
  • Ericsson verbot 1998 die Nutzung von Erlang; das Entwicklungsteam argumentierte, wenn man es schon verbiete, solle man es als Open Source veröffentlichen, und Ericsson setzte dies um
  • Joe Armstrong war eine der Schlüsselfiguren bei Design und Implementierung von Erlang; 2002 begann er am SICS mit seiner Doktorarbeit und schloss 2003 Making reliable distributed systems in the presence of software errors ab
  • Die Dissertation konzentriert sich weniger auf Mathematik oder Theorie als auf die Ideen hinter Erlang und die Erfahrungen beim Bau zuverlässiger verteilter Systeme
  • Die große Idee von Erlang sind nicht leichtgewichtige Prozesse und Message Passing, sondern allgemeine Komponenten, die in Erlang behaviours genannt werden

behaviours: Verbindung aus Interface und Infrastruktur

  • Erlang-behaviours sind Bündel von Typsignaturen, die wie Interfaces in Java oder Go mehrere Implementierungen haben können
  • Wenn Programmierer eine Implementierung dieses Interfaces bereitstellen, können sie generische Funktionen verwenden, die gegen dieses Interface geschrieben wurden
  • Der zentrale Unterschied ist, dass behaviours über einfache Interfaces hinaus auch Infrastrukturcode mitliefern
    • Anwendungsprogrammierer schreiben die Semantik des Problems, also die Business-Logik
    • Infrastrukturcode wie Nebenläufigkeit wird automatisch vom behaviour bereitgestellt
    • behaviours enthalten Best Practices, die von erfahrenen Experten geschrieben wurden
    • Wenn das Gesamtsystem eine kleine Menge von behaviours wiederverwendet, kann das System ohne Codeänderungen profitieren, sobald die behaviour-Implementierung verbessert wird
    • Die Nutzung von behaviours erzwingt Struktur und erleichtert Tests sowie formale Verifikation

gen_server: Nebenläufigkeit hinter sequenziellem Code verborgen

  • Das Beispiel für gen_server ist ein Key-Value-Store: store speichert Schlüssel-Wert-Paare, lookup sucht den Wert eines Schlüssels
  • Der zentrale Callback handle_call aktualisiert bei store-Anfragen den Zustand Dict und sucht bei lookup-Anfragen den Schlüssel im Zustand
  • Wenn man diese Implementierung an gen_server übergibt, entsteht ein Server, der gleichzeitig eintreffende store- und lookup-Anfragen verarbeiten kann
  • Wichtig ist, dass handle_call selbst vollständig sequenziell ist
    • Nebenläufigkeit ist im generischen gen_server-Baustein verborgen
    • Anwendungscode nähert sich der Form an, einen Zustand und eine Eingabe zu nehmen und daraus einen neuen Zustand sowie eine Ausgabe zu erzeugen
  • Joe Armstrongs Dissertation erweitert diesen Ansatz auf gen_server, gen_event, gen_fsm, supervisor, application und release

Der Fall AXD301 und die Größenordnung von behaviours

  • Joe Armstrong verwendet Ericssons Telefonvermittlung AXD301 als Fallbeispiel
  • Das AXD301-Projekt enthielt die folgenden Komponenten
    • 122 gen_server-Instanzen
    • 36 gen_event-Instanzen
    • 10 gen_fsm-Instanzen
    • 20 Supervisor
    • 6 Applications
    • Das Ganze wurde als ein Release paketiert
  • AXD301 war ein System mit mehr als einer Million Zeilen Erlang-Code
  • Dass ein System dieser Größenordnung aus einer kleinen Menge von behaviours zusammengesetzt wurde, ist ein zentraler Beleg für die Struktur von Erlang

Die Rolle anderer behaviours

  • gen_event ist ein generischer Event-Manager
    • Man registriert Event-Handler, und wenn der Event-Manager entsprechende Nachrichten empfängt, führt er die Handler aus
    • Joe Armstrong nennt Fehler-Logging als Beispiel
    • Der Beispiel-Logger kann die letzten fünf Fehlermeldungen aufzeichnen und berichten
  • gen_fsm ist ein Zustandsmaschinen-behaviour, das später in gen_statem umbenannt wurde
    • Es eignet sich besser für Protokollimplementierungen
    • Protokolle werden häufig als Zustandsmaschinen spezifiziert
    • Man kann davon ausgehen, dass jeder gen_server auch als gen_statem implementiert werden kann und umgekehrt
  • application besteht aus einem Supervisor-Baum und den übrigen Elementen, die für die Auslieferung der Anwendung nötig sind
  • release paketiert eine oder mehrere Applications
    • Es enthält auch Code zur Behandlung von Upgrades
    • Wenn ein Upgrade fehlschlägt, muss ein Rollback auf den vorherigen stabilen Zustand möglich sein

supervisor und „Let it crash!“

  • supervisor ist ein Prozess, der überprüft, ob andere Prozesse ordnungsgemäß funktionieren
  • Wenn ein überwachter Prozess fehlschlägt, kann der Supervisor ihn gemäß einer vordefinierten Strategie neu starten
  • Die Beispielstrategie {one_for_one, 5, 1000} bedeutet Folgendes
    • Wenn einer von packet_assembler, kv oder simple_logger fehlschlägt, wird nur der fehlgeschlagene Prozess neu gestartet
    • Wenn innerhalb von 1000 Sekunden mehr als fünf Neustarts nötig sind, schlägt der Supervisor selbst fehl
  • permanent, 500, worker bedeutet, dass der betreffende Prozess ein Worker ist, der immer laufen muss, und dass der Supervisor ihm beim Versuch eines Neustarts 500 ms für ein geordnetes Beenden gibt
  • Bei der Strategie one_for_all werden alle Kindprozesse neu gestartet, wenn ein Prozess fehlschlägt
  • Ein Supervisor kann andere Supervisor überwachen und muss nicht zwingend auf demselben Rechner laufen
  • Diese Struktur arbeitet nicht auf der Ebene von Docker-Containern, sondern auf der Ebene von Threads/leichtgewichtigen Prozessen, weshalb sie sich schwerlich als „einfach Kubernetes“ beschreiben lässt
  • „Let it crash!“ setzt voraus, dass ein fehlgeschlagener Prozess neu gestartet wird
    • Das Programm beschreibt nur den Normalpfad
    • Wenn im Normalpfad ein Problem auftritt, versucht es nicht selbst, sich zu erholen, sondern crasht
    • Ein anderes Programm weiter oben im Supervisor-Baum übernimmt die Behandlung
  • Langfristige Stabilitätsnachweise für AXD301 wurden nicht systematisch gesammelt, aber in Joe Armstrongs Dissertation steht, dass eine PowerPoint-Zahl eines großen Kunden den Betrieb eines 11-Knoten-Systems mit 99,9999999 % Zuverlässigkeit angab
    • Wie diese Zahl ermittelt wurde, ist nicht dokumentiert
    • Auch andere Zahlen aus Downtime-Berichten sind mit dem Hinweis versehen, dass die Berechnungsmethode unklar ist

Wenn man behaviours in anderen Sprachen implementieren wollte

  • Leichtgewichtige Prozesse und Message Passing allein machen Erlang nicht für zuverlässige Systeme geeignet
  • Die ehrlichere Erklärung ist die Struktur, die behaviours bereitstellen, und die Art, wie diese Struktur zu zuverlässiger Software führt
  • Die Interface-Signatur von gen_server lässt sich als folgende Form betrachten
    • Input -> State -> (State, Output)
    • Das bedeutet, dass Eingabe und aktueller Zustand genommen werden, um einen neuen Zustand und eine Ausgabe zu erzeugen
  • Wie diese sequenzielle Signatur in die Verarbeitung nebenläufiger Anfragen überführt werden kann, lässt sich etwa so denken
    • Ein HTTP-Server wandelt Requests in Input um und legt sie in eine Queue
    • Eine Event Loop nimmt Eingaben aus der Queue und übergibt sie an die sequenzielle Implementierung
    • Die Ausgabe wird als Antwort an den Client zurückgegeben
    • Um mehrere gen_server zu unterstützen, kann man jedem Server einen Namen geben und in Requests sowohl Namen als auch Eingabe enthalten
  • gen_event könnte implementiert werden, indem man das Registrieren von Callbacks für bestimmte Event-Typen in der Queue erlaubt
  • supervisor lässt sich vereinfacht als Mechanismus betrachten, der gen_server-Funktionsaufrufe in Exception-Handler einhüllt und bei einer Exception den Supervisor benachrichtigt
    • Wenn der Supervisor nicht auf demselben Rechner läuft, wird es komplexer
  • application und release sind wichtig, weil Konfiguration, Deployment und Upgrades schwierige Probleme sind, werden hier aber nicht vertieft behandelt

behaviours und Testbarkeit

  • Die Struktur von Erlang-behaviours führt zu Simulationstests und formaler Verifikation
  • Ein aktuelles Interessengebiet sind Simulationstests verteilter Systeme im Stil von FoundationDB
  • Bei Simulationstests wird das System in einer simulierten Welt ausgeführt, wobei die Simulation den Zeitpunkt der Zustellung von Netzwerknachrichten vollständig kontrolliert
  • FoundationDB entwickelte für Simulationstests einen C++-Dialekt mit Actors oder eine eigene Programmiersprache
  • Dieser Ansatz hätte vermutlich schon mit dem folgenden Zustandsmaschinentyp recht weit kommen können
    • Input -> State -> (State, [Output])
    • [Output] ist eine Sequenz von Ausgaben
  • Der Simulator verwaltet Nachrichten in einer nach Ankunftszeit sortierten Priority Queue
    • Er entnimmt eine Nachricht
    • Er stellt die Uhr auf die Ankunftszeit dieser Nachricht vor
    • Er übergibt die Nachricht an die empfangende Zustandsmaschine
    • Er erzeugt neue Ankunftszeiten für Ausgabenachrichten und legt sie wieder in die Queue
  • Wenn alles deterministisch ist und Ankunftszeiten aus einem Seed erzeugt werden, lassen sich verschiedene Interleavings erkunden und reproduzierbare Fehler erhalten
  • Diese Methode ist schneller als Jepsen, weil Nachrichten im Speicher verarbeitet werden und die Uhr auf die Ankunftszeit vorgestellt wird, statt auf Timeouts zu warten
  • Eine solche Zustandsmaschinenform wird als „network normal form“ bezeichnet und führt zu der Vermutung, dass sich jedes Programm, das über das Netzwerk empfängt und sendet, in diese Form refaktorisieren lässt
  • Dass gen_server und gen_statem im Grunde dieselbe Typstruktur haben, kann als Hinweis verstanden werden, dass diese Struktur nicht willkürlich ist

Formale Verifikation und Zustandsmaschinenstruktur

  • Joe Armstrong sagte einmal in einem Talk, dass es schwierig sei, verteilte Leader Election korrekt zu implementieren
  • Mit einem Simulator könnte dieses Problem deutlich vereinfacht werden
    • So wie Windkanäle beim Flugzeugbau helfen, könnten Extrembedingungen wie instabile Netzwerke oder Stromausfälle vor der Production getestet werden
  • Dieser Simulator könnte generisch oder parametrisiert für behaviours sein
    • Entwickler müssten keinen eigenen Simulator schreiben
    • Die Komplexität wäre wie der Nebenläufigkeitscode von gen_server hinter dem behaviour verborgen
  • Kyle „aphyr“ Kingsbury twitterte über die Tests von FoundationDB sinngemäß, dass sie deutlich strenger wirkten als seine eigenen Jepsen-Tests
  • Auch formale Verifikation wird einfacher, wenn Programme als Zustandsmaschinen geschrieben sind
    • Lamports Arbeit zum TLA+-Model-Checking setzt voraus, dass Spezifikationen Zustandsmaschinen sind
    • Kleppmann zeigt, wie man das Problem der Zustandsexplosion mit struktureller Induktion auf Basis der Zustandsmaschinenstruktur angehen kann
  • Die aus Erlang-behaviours gewonnene Struktur kann wiederverwendet werden, um die Probleme leichter zu lösen, die Joe Armstrong als schwierig bezeichnete

Verwandte Arbeiten und Referenzen

  • Verwandte Arbeiten, die Ideen aus Erlang aufgegriffen haben, sind unter anderem
    • Arbeiten, die Ideen von Martin Thompsons LMAX Disruptor und aeron übernehmen, um eine schnelle Event Loop zu bauen, in der behaviours ausgeführt werden
    • Arbeiten, die Zustandsmaschinentypen um async I/O erweitern
    • Arbeiten, die supervisors detaillierter implementieren
    • Hot Code Swapping für Zustandsmaschinen
  • Als Referenzen werden Joe Armstrongs Doktorarbeit, OTP design principles sowie die Dokumentation zu gen_server, gen_event, gen_statem, supervisor, application und release genannt
  • Zum Verhältnis zwischen Actor Model und Erlang wird zusammengefasst, dass die Erlang-Entwickler Erlang schufen, ohne das Actor Model zu kennen, und dass Carl Hewitts Paper die Unterschiede zwischen Erlang-Prozessen und dem Actor Model dokumentiert
  • Akka hat zwar „actors“ und Supervisor Trees, scheint aber nichts zu haben, was den anderen Erlang-behaviours entspricht; auch Akkas Begriff „behavior“ unterscheidet sich von Erlang-behaviours

1 Kommentare

 
GN⁺ 2025-04-13
Kommentare auf Hacker News
  • Das Erstaunliche an Erlang und BEAM ist die Tiefe der Funktionalität. Im ursprünglichen Artikel waren Behaviour/Interface der zentrale Eindruck, aber für mich wiegt stärker, dass man damit komplexe Systeme mit deutlich weniger Entwicklungsressourcen bauen kann als in anderen Sprachen.
    Auch OTP selbst enthält sehr viel. Ich habe einmal daran gearbeitet, Elixir so zu kompilieren, dass es auf iOS-Geräten läuft. Dabei konnte ich nicht nur den Release-Prozess von Erlang nutzen, sondern auch mit der ei-Bibliothek einen Node in C kompilieren, der mit anderen Erlang-Nodes wie Erlang, Elixir, Gleam usw. wie in einem normalen verteilten Netzwerk kommunizieren konnte.
    Außerdem konnte ich über die rpc-Bibliothek von Erlang sogar aus C heraus Funktionen einer Elixir-Anwendung aufrufen. Es gibt Overhead beim Encoding/Decoding, und FFI wäre schneller, aber es passte locker in unser Latenzbudget, und wir konnten eine Funktionalität, von der ich zuvor noch nie gehört hatte, innerhalb weniger Tage aufbauen.
    Der größere Punkt ist: Erlang hat viele Probleme, mit denen moderne Tech-Stacks ringen, schon vor Jahrzehnten gelöst – sowohl in Bezug auf Skalierung als auch Implementierungskosten. HN zeigt zwar eine gewisse clickbaitartige Zuneigung zu Erlang/Elixir, aber das führt nicht wirklich zu Adoption; zugleich gibt es Unternehmen, die Geld verbrennen, um Dinge zu implementieren, die man im Erlang-Stack standardmäßig gratis bekommt.

    • Ich bin von einem Unternehmen mit Elixir-Backend zu einem gewechselt, das Node.js nutzt. Ursprünglich hatte ich kaum Erfahrung mit Node.js und war neutral eingestellt.
      Das Projekt, an dem ich arbeitete, war eine Backend-Datenpipeline, und es wurden nicht einmal besonders viele Daten verarbeitet. Trotzdem war es unglaublich schwierig, den entscheidenden Bug sauber einzugrenzen.
      Dabei lernte ich mehrere Eigenschaften von Node.js kennen, und im Vergleich zu Elixir/Erlang/OTP kam ich zu dem Schluss, dass Node.js konstruktionsbedingt weniger zuverlässig ist.
      Ich habe auch viel Ruby gemacht und etwas Python genutzt, aber viele Sprachplattformen der aktuellen Generation tun sich schwer damit, zuverlässige verteilte Systeme zu bauen. Die BEAM-VM und die OTP-Plattform haben diesen Teil bereits gelöst.
    • Die Grundbausteine, die Erlang und seine Nachfolger bereitstellen, sind leicht zu handhaben und deshalb auch leicht zu testen.
      Ein GenServer, der in den meisten BEAM-Systemen die Rolle eines Workers übernimmt, läuft am Ende zum Beispiel darauf hinaus, mehrere Funktionen mit einfachen Parametern aufzurufen.
      Daher kann man diese Funktionen direkt aufrufen, die Parameter manuell übergeben und nur die Ausgabe prüfen. Man muss kein komplexes Testsystem für asynchronen Code aufsetzen oder in Tests blockieren, während man auf den Abschluss einer Arbeit wartet.
      Juniors übersehen das oft, aber wenn man es einmal verstanden hat, fühlt es sich ziemlich befreiend an.
    • C-Nodes werden unterschätzt. Wir verwenden Cgo-basierte C-Nodes für die Kommunikation zwischen Go- und Elixir-Services, die im selben Kubernetes-Pod laufen. Auch die Dokumentation zu Erlang und den C-Bibliotheken ist ziemlich gut.
    • Ich frage mich, wie die Community erklärt, dass HN Erlang/Elixir offenbar mag, daraus aber trotzdem keine Adoption entsteht.
  • Ich habe erlebt, dass einige Leute, meist Manager, auf Basis unserer Erfahrungen ein Buch schreiben wollten. Es war immer frustrierend, dass sie jeweils andere Dinge als entscheidend dafür ansahen, warum unsere Arbeit erfolgreich war. Was ich für essenziell hielt, stuften sie oft zu etwas herab, das nur „nice to have“ sei.
    Auch hier sagt jemand, leichtgewichtige Prozesse und Message Passing seien nicht die geheime Zutat, übersieht aber, dass die Sicht auf Erlang als kommunizierende sequenzielle Prozesse (CSP) nicht von diesen Eigenschaften zu trennen ist. Gleichzeitig wird CSP wiederholt als Teil der geheimen Zutat erwähnt.
    Etwa wenn Anwendungsprogrammierer sequenziellen Code schreiben und Nebenläufigkeit in Behaviours verborgen wird, wenn Business-Logik sequenziell ist und neue Teammitglieder leichter einsteigen können, oder wenn Supervisoren und die „let it crash“-Philosophie zuverlässige Systeme ermöglichen.
    Behaviours sind interessant, waren in den 80ern verbreitet und lösen ein Problem, das manche auch in den 2000ern noch bearbeitet haben. In Erlang sind sie aber zugleich Zweck und Mittel. Sie sind die Art, wie diese anderen Eigenschaften implementiert wurden; ob es zwingend genau so sein musste, damit Erlang weiterhin Erlang bleibt, weiß ich nicht.

    • Erlang ist kein CSP, sondern ein Actor Model. https://en.wikipedia.org/wiki/Actor_model
      CSP ist die Linie, die über occam und andere Sprachen die Go-Channels inspiriert hat. Die Synchronisation über ungepufferte Channels ist der deutlichste Unterschied, daneben gibt es Unterschiede wie Pattern Matching auf Mailboxen im Actor Model.
      Die Debatte CSP vs. Actor Model ist ziemlich interessant, weil sie oberflächlich ähnlich wirken, die tatsächlichen Implikationen aber stark auseinandergehen.
    • Betrachtet man Erlang als CSP? Ich habe das nie so gesehen, sondern als eigenes Modell, in dem Actors mit Identifikatoren direkt miteinander kommunizieren. CSP habe ich eher als anonyme Prozesse verstanden, die Channel-Messaging verwenden.
      Ich finde, das Actor Model passt besser, aber das kann je nach Person stark variieren.
    • Manager bauen sich je nach Stimmung ihre eigene Erzählung.
  • Ich bin hierhergekommen, weil ich mehr darüber wissen wollte, warum Ericsson aufgehört hat, Erlang zu verwenden, und warum Joe entlassen wurde.
    Kurz gesagt scheint Ericsson neue Projekte auf Java umgestellt zu haben, wodurch Erlang marginalisiert wurde. Danach gründeten Joe und Kollegen 1998 Bluetail, das von Nortel übernommen wurde.
    Nortel war ein Telekommunikationsriese, der etwa ein Drittel des Werts der Börse von Toronto ausmachte. Im Jahr 2000 stieg die Aktie bis auf 125 Dollar pro Anteil, fiel 2002 aber unter 1 Dollar. Das war Teil des Dotcom-Crashs, und zusammen mit dem starken Rückgang der Telekom-Ausgaben traf es Nortel besonders hart.
    Joes Entlassung sollte man wohl am besten als Fall betrachten, in dem „seine Abteilung auf einem sinkenden Schiff zuerst unter Wasser geriet“. Nortel entließ 60.000 Menschen, also mehr als zwei Drittel der gesamten Belegschaft. Diese Entlassung war kein Signal dafür, dass Joe seine Arbeit nicht machte, und auch kein Beleg für Ineffizienz seiner Geschäftseinheit, sondern eine verzweifelte groß angelegte Maßnahme.

    • Es ist in diesem Kontext sehr seltsam, das Wort „fired“ zu sehen. „laid off“ wäre passender.
      „fired“ enthält eine starke Wertung und legt nahe, dass jemand wegen Fehlverhaltens oder aus einem bestimmten Grund gekündigt wurde. Selbst wenn es tatsächlich so gewesen wäre, könnte der Autor des ursprünglichen Artikels das nicht wissen – und es geht ihn auch nichts an.
  • Wegen der leichtgewichtigen Prozesse und Message Passing habe ich angefangen, mir Erlang wieder anzusehen; bis jetzt waren Behaviours für mich eher zweitrangig.
    Das Projekt besteht darin, visuelle Flow-Based Programming (FBP) nach Erlang zu bringen. FBP wirkt, als wäre es für Erlang gemacht, daher war ich überrascht, dass ich keine bestehende Implementierung finden konnte.
    Das Tool, das ich für FBP hauptsächlich verwendet habe, war Node-RED. Die Grundidee ist also, das Node-RED-Frontend an ein Erlang-Backend anzubinden und jeden Node zu einem Prozess zu machen. Das Node-RED-Frontend eignet sich gut, um Message Passing zwischen Nodes zu modellieren, sodass eine sehr einfache 1:1-Abbildung auf Erlang-Prozesse und -Messages möglich ist.
    Einige Grundfunktionen habe ich implementiert und begonnen, Flows als Unit Tests aufzubauen, um die Funktionalität schrittweise zu erweitern. Schön wäre eine 100%ige Kompatibilität mit Node-RED, dem Node.js-Backend. Details stehen im GitHub-Repository → https://github.com/gorenje/erlang-red
    Insgesamt passt Erlang erstaunlich gut dazu, und ich wundere mich, warum niemand etwas Ähnliches gemacht hat. Gab es das vielleicht schon?
    [1] = https://jpaulm.github.io/fbp/index.html

    • Ich dachte schon immer, dass eine visuelle Programmiersprache auf der BEAM interessant wäre.
    • Aus einer höheren Perspektive frage ich mich, ob man FBP als ähnlich zu Erlang-Prozessen ansehen kann, bei denen der Message Flow in eine Richtung läuft.
  • Für mich liegt die Stärke von Erlang/Elixir weniger in der Actor-Model-Implementierung, dem Matching aus Prolog, Immutability oder den Behaviours selbst, sondern in Joes Willen zu zeigen, dass man mit weniger mehr erreichen kann.
    Es ist ein gut entworfenes und erprobtes Rechensystem mit einer Konsistenz, die man in anderen Sprachen und erst recht in der „Web“-Welt selten sieht. Perfekt ist es nicht, aber ziemlich beeindruckend.
    Leider gibt es in der Softwarewelt meiner Ansicht nach deutlich zu wenig Anerkennung und Übernahme dessen, was Einfachheit ermöglicht. Komplexität macht Menschen zu Experten, erlaubt Managern, große Teams und viele Meetings zu rechtfertigen, und sorgt dafür, dass Experten Experten bleiben.
    Erlang wurde in einer Zeit entwickelt, in der Unternehmen Softwarelösungen mit weniger Personal und begrenzter Performance umsetzen wollten. Nachdem in den folgenden Jahrzehnten sehr viel Geld in diesen Bereich floss, wirkte der Wert „weniger bedeutet mehr, und zwar auf eine Weise, die allen nützt“ weniger attraktiv.

    • Alan Kay hat einmal gesagt, Einfachheit entstehe dadurch, dass man etwas komplexere Bausteine wählt. Erlang scheint genau das zu tun.
    • Ich muss an Rich Hickeys Vortrag Simple VS Easy denken.
    • Diese Aussage hat mich dazu gebracht, mehr Zeit in Erlang investieren zu wollen. Ich habe es ein wenig ausprobiert, und zumindest oberflächlich gefällt mir die Erlang-Syntax besser als die von Elixir.
  • Das interessanteste Konzept in Erlang/BEAM ist, dass partielle Wiederherstellung von Anfang an eingebaut ist.
    Wenn ein unerwarteter Zustand auftritt, wird nicht der gesamte Prozess beendet oder einfach weitergemacht und Beschädigung in Kauf genommen, sondern auf der feinsten möglichen Ebene zu einem bekannten guten Zustand zurückgekehrt.
    Diese Idee wurde schon vor langer Zeit unter dem Namen „Microreboots“ erforscht und steht auch mit „crash-only software“ in Zusammenhang, aber nur Erlang/BEAM hat sie zu einem First-Class-Konzept des Betriebssystems gemacht.

    • Bei Supervision Trees und dem Neustart von Teilen eines Baums muss man dennoch vorsichtig sein. Wenn zum Beispiel der gesamte Erlang-Betriebssystemprozess plötzlich stirbt und neu gestartet wird, kann das System korrekt weiterlaufen; wenn aber nur ein Teil des Erlang-Prozessbaums neu gestartet wird, können Daten beschädigt werden.
      Erlang bietet ein gutes Modell, um mit solchen Problemen umzugehen, aber das heißt nicht, dass man das Denken komplett abschalten kann. Wer mit der Haltung herangeht, man könne es einfach neu starten lassen und alles werde schon gut, kann sich die Finger verbrennen.
    • Ich frage mich, worin sich diese Beschreibung von Exception Handling unterscheidet.
  • Erlang, OTP und BEAM bieten mehr als Behaviours. Die VM ist eher ein virtueller Kernel mit Supervisors, isolierten Prozessen und einem verteilten Modus, der mehrere physische/virtuelle Maschinen wie einen einzigen Ressourcenpool behandelt.
    OTP stellt außerdem mehrere nützliche Modi bereit, darunter die Datenbank Mnesia sowie atomare Counter und ETS-Tabellen fürs Caching. Die Runtime unterstützt auch Hot Reloading von Bytecode, sodass Patches ohne Systemunterbrechung eingespielt werden können. Die Syntax ist nicht gerade Screenreader-freundlich, aber lesbar.
    Als Plattform, die mir in ihrer Fähigkeit, Ressourcen mehrerer Maschinen wie einen einzigen Pool zu behandeln, ähnlich wie BEAM erscheint, fällt mir höchstens Apache Mesos[1] ein.
    Vor gut einem Jahr hat meine eigene Beratungsfirma Erlang als Backend-Sprache eingeführt. Seitdem haben wir begonnen, das Innere der BEAM zu erkunden, etwa indem wir einen TCP-basierten Stack auf QUIC umstellen oder Rust-Patches integrieren.
    Für leichtgewichtige Systeme mit hohem Durchsatz, die außer bei Kernel Panic oder Stromausfall nicht ausfallen, ist es wirklich eine hervorragende Wahl. Derzeit bauen wir sehr geschäftige, hochgradig nebenläufige Software wie Tracker und Pipeline-Manager für Film-/Game-Produktionen und bereiten außerdem F&E für einen Verwaltungsdienst für Privatkliniken vor.
    [1]: https://mesos.apache.org/

    • Ich habe nicht vor, Elixir oder Gleam vollständig zu übernehmen. Ihr Ökosystem ist nämlich stark um das Phoenix-Framework und externe Services/Datenbanken herum aufgebaut.
      Es ist gut möglich, dass wir für Dinge, die auf Elixir-Seite nicht gepflegt werden, eigene interne Bindings oder Implementierungen warten müssten.
      Außerdem gibt es viel syntaktischen Zucker, und die Nutzer zeigen eine eigenartige Besessenheit davon, Dinge hinter DSL-Interfaces zu abstrahieren.
  • Auf die Frage „Warum kopieren Sprach- und Bibliotheksdesigner nur die Ideen von leichtgewichtigen Prozessen und Message Passing, stehlen aber nicht die Struktur hinter Erlang behaviours?“ gibt es einen Grund.
    Die Funktionssignaturen von Erlang behaviours sind stark mit anderen Eigenschaften von Erlang verknüpft, insbesondere mit seiner ungewöhnlichen Art, Immutability zu nutzen. Deshalb braucht ein Server auch einen separaten init-Aufruf, und auch die Zustandsverwaltung braucht eine sehr ausgeprägte Form, wenn sie auf dieselbe Weise funktionieren soll.
    In anderen Sprachen gilt aber fast immer: Um dasselbe Ziel zu erreichen, sollte man nicht einfach exakt kopieren, was Erlang tut. Wenn jemand sagt, er habe „gen_server in eine andere Sprache portiert“, und dann exakt dieselbe Schnittstelle wie Erlang zeigt, sehe ich das als Portierung, die nicht wirklich tief verstanden hat, was Erlang tut.
    Als ich die Idee des supervisor tree nach Go übertragen habe[1], habe ich es ebenfalls auf Go-typische Weise getan. In modernem Go ist die richtige Schnittstelle für etwas „Überwachbares“ nicht dieselbe Signatur wie in Erlang, sondern nur diese:
    type Service interface {
    Serve(context.Context)
    }
    In Go ist das alles, und man sollte nicht mehr als das verwenden. In anderen Sprachen kann es anders sein. Go hat Channels, daher braucht man kein „handle_event/2“, sondern sollte Channels verwenden. Nicht weil Channels besser oder schlechter wären, sondern weil die Sprache so funktioniert.
    In anderen Sprachen kann man andere Dinge verwenden, und in anderer Infrastruktur kann man Ereignisse statt „handle_event/2 aufzurufen“ auch an Kafka oder einen Cloud Event Bus schicken. Der Kern ist, ein ereignisgetriebenes System zu bauen, nicht Erlangs exakte Implementierung zu kopieren.
    In der Erlang-Community gibt es das Problem, dass sie übermäßig davon überzeugt ist, an Erlangs exakter Vorgehensweise sei etwas enorm Besonderes, und jede Abweichung sei sofort falsch und unzuverlässig. 2005 mag das gestimmt haben, 2025 nicht mehr.
    Eine Zeit lang war Erlang fast die einzige sinnvolle Antwort, aber 2025 besteht das Problem darin, sich durch eine Flut von Antworten zu bewegen. Für zuverlässige Software von Erlang zu lernen, empfehle ich ausdrücklich; Erlangs genaue Art, die Dinge zu erreichen, blind in andere Sprachen zu portieren, lehne ich jedoch entschieden ab. In fast jedem anderen Sprachkontext ist das die falsche Antwort. Selbst andere immutable Sprachen sind strukturell verschieden genug, dass man es nicht einfach kopieren kann.
    [1]: https://jerf.org/iri/post/2930/

    • Die naheliegende Anschlussfrage lautet meiner Meinung nach: „Warum hatten andere Sprachen und Systeme mit solchen Ansätzen und Ideen Erfolg bei der Marktakzeptanz, Erlang/Elixir aber nicht?“
      Als jemand, der Elixir beruflich nutzt, ist das für mich die interessanteste Frage zu Erlang.
      Wenn man sieht, wie erfolgreich k8s, Kafka und die verteilten Systemprodukte von AWS geworden sind, ist die Nachfrage nach Werkzeugen, die beim Entwurf zuverlässiger nebenläufiger Systeme helfen, eindeutig enorm. Warum haben Erlang/Elixir sich davon also keinen Anteil gesichert?
      Ich diskutiere das oft mit Freunden, kenne die Antwort aber nicht.
    • Ich mag Go am liebsten, aber bei der Aussage „Go hat Channels, also braucht es handle_event/2 nicht“ habe ich Zweifel. Was für eine Art Channel? Und wichtiger: Channels sind prozesslokal; um sie netzwerkfähig zu machen, braucht es Glue Code.
      Ich vermute, Erlang hat die Verarbeitung von Netzwerknachrichten abstrahiert. Außerdem habe ich für Dinge wie langlebige Server drei oder vier Varianten des vorgeschlagenen Musters gesehen.
      Ich stimme völlig zu, dass man beim Portieren idiomatische Bausteine verwenden sollte. Aber Sprachen können verborgene Mechanismen haben, durch die beim Portieren der wertvolle Kern verloren geht. Eine Art Antirelativismus der Programmiersprachen.
      „Channels? Einfach in X wrappen“ kann für Interoperabilität deutlich schädlicher sein, als es klingt. Nimmt man zum Beispiel Go-http.Handler: Er ist einfach, aber die praktische Konsequenz, dass er in der Standardbibliothek steckt, ist groß. Dadurch entsteht auch ohne vorherige Abstimmung ein Middleware-Ökosystem, das weitgehend kompatibel ist.
      io.Reader und seine Verwandten sind ähnlich. Solche extrem einfachen Schnittstellen können wertvoller sein als die Implementierung.
      Nur eine Vermutung, aber wenn Erlang viele Schnittstellen für zuverlässige verteilte Systeme richtig getroffen hat, könnte genau das der Faktor sein, der das Ganze ermöglicht hat.
  • Ich stimme nicht zu. Interfaces sind ein triviales Konzept, das man an jede Sprache anhängen kann. Selbst in Sprachen ohne offizielle Interface-Syntax kann man sie im Programmraum nachbilden.
    BEAM war erfolgreich, weil es auf einem einzelnen Node eine Million Prozesse ausführen kann, komplexe verteilte Zustandsmaschinen leicht ausdrückbar macht und Teile des Systems ohne Downtime neu starten kann. Daneben gibt es viele weitere Gründe.
    Ich glaube wirklich nicht, dass behaviour/interface das zentrale Puzzlestück ist.

    • Bis zu einem gewissen Grad stimme ich zu. Wenn man Erlang zum ersten Mal sieht, scheint es für jede Erlang-Funktion auch in anderen Systemen etwas Ähnliches zu geben.
      Isolierte Prozess-Heaps kann man mit OS-Prozessen haben, für supervisor trees kann man Kubernetes verwenden, Message Passing lässt sich in Java mit zwei Threads und einer gemeinsamen Queue bauen, Hot Code Loading kann Java auch, und bei niedriger Latenz kann ein getunter LMAX Disruptor Erlang vielleicht sogar schlagen.
      Entscheidend ist aber, all das in einer Plattform oder Bibliothek zusammenzubringen. OS-Prozesse sind schwergewichtig, und es ist nicht leicht, zwei Millionen davon auf einem Server laufen zu lassen. Nutzt man Green Threads oder Promises, verliert man die isolierten Heaps.
      Kubernetes kann man bis zu einem gewissen Grad ebenfalls nutzen, aber verschachtelte supervisor trees behandelt es nicht gut. Möglich wäre es wohl, aber zusätzlich zum Code bekommt man dann alle möglichen Dinge wie Pods, Controller und Volumes.
      Mit Actor-Bibliotheken in verschiedenen Sprachen kann man Message Passing umsetzen, aber sie integrieren nicht transparent Pattern Matching in receive oder das Senden an andere Threads auf anderen Nodes.
      Hot Code Loading ist ebenfalls möglich, aber die Frage ist, wie man Laufzeitdatenstrukturen und Zustand behandelt. Erlang wurde darum herum gebaut, und gen_server hat, weil der Zustand immutable und explizit ist, Callbacks, um nicht nur den Code, sondern auch den Zustand selbst zu aktualisieren.
    • Ich habe es nicht ausgiebig genug genutzt, aber BEAM scheint manche der Probleme zu vermeiden, die Ian Cooper in „Where Did It All Go Wrong?“ wiederentdeckt hat: nämlich dass Microservices die Reibung innerhalb von Modulen und zwischen Modulen eines Systems nicht optimieren.
      Ich würde nicht sagen, dass BEAM dieses Problem beseitigt, aber es scheint die Steigung der Kurve zu verringern. Seine in sich konsistenten Idiome und Funktionen, einschließlich Deployment, automatischer Wiederherstellung und Lastverteilung, reduzieren die Reibung zwischen Modulen.
      Es ermöglicht Systeme, in denen zwölf Engineers 30 Endpunkte leicht verwalten können und die Oberfläche trotzdem weiterhin einem Potenzgesetz folgen kann.
  • Ich stimme dem Inhalt dieses Beitrags nicht zu. Behaviours werden durch die zugrunde liegende Architektur des Systems ermöglicht.
    Ein Behaviour ist keine Schnittstelle, sondern eher ein abstraktes Objekt wie in Sprachen à la Java. Es implementiert grundlegende, in sich geschlossene Funktionalität, die hinter einer Kollaborationsschnittstelle verborgen ist. Ohne die zugrunde liegende Infrastruktur, die garantiert, dass jeder Prozess vollständig von anderen Prozessen getrennt ist, dass alle Prozesse ohne Speicher- oder Ressourcenlecks sicher beendet werden können und dass zwischen zwei Prozessen keine gefährlichen Pointer geteilt werden können, lässt sich damit aber nicht viel anfangen.
    Was Joe in seiner Arbeit gezeigt hat, war, wie man mit einer gegebenen Menge von Lego-Bausteinen zuverlässige Systeme und darüber hinaus bis zu einem gewissen Grad zuverlässige verteilte Systeme bauen kann.
    Um so etwas korrekt umzusetzen, braucht man die Erlang-VM; auf anderen VMs lässt es sich nicht vollständig implementieren. Ohne die darunterliegende Verkabelung wird der Supervision Tree undicht. In Java kann man keinen Thread, der Ressourcen festhält, einfach killen und erwarten, dass immer alles gutgeht, und es fehlen auch die Möglichkeiten, unterschiedliche Prozesse zu überwachen.