Warum Twilio Segment von Microservices zu einer monolithischen Architektur zurückgekehrt ist
(twilio.com)- Twilio Segment betrieb Hunderte von Microservices, wechselte aber wegen der Komplexität und des hohen Wartungsaufwands zu einem Single-Service (Monolithen)
- Anfangs wurden die einzelnen Destination-APIs getrennt, um Fehler zu isolieren und Skalierbarkeit zu erreichen. Als die Zahl der Services jedoch auf über 140 anstieg, nahm der operative Overhead stark zu
- Die Verwaltung zahlreicher Repositories und Shared Libraries wurde schwierig, und bei Tests sowie Deployments traten Probleme auf, die alle Services betrafen
- Um das zu lösen, führte das Unternehmen das Centrifuge-System und eine Monorepo-Struktur ein und baute für die Testautomatisierung den Traffic Recorder
- Dadurch verbesserten sich Entwicklungsgeschwindigkeit und Stabilität deutlich; Twilio Segment setzt den Monolithen weiterhin aus Gründen der Produktivität und operativen Effizienz ein
Einführung von Microservices und ihre Grenzen
- Twilio Segment setzte für seine Customer-Data-Infrastruktur auf eine Microservices-Architektur und entwarf die Plattform so, dass Services für einzelne Zwecke Ereignisse unabhängig verarbeiten
- Daten werden an Hunderte von serverseitigen Destinations (z. B. Google Analytics, Optimizely usw.) weitergeleitet
- Anfangs wurde eine einzelne Queue verwendet, doch bei Ausfällen bestimmter Destinations trat Head-of-Line-Blocking auf, wodurch das gesamte System verzögert wurde
- Zur Lösung richtete das Unternehmen für jede Destination separate Services und Queues ein und erreichte so Fehlerisolation sowie unabhängige Skalierung
- Mit wachsender Zahl der Services stiegen jedoch operative Komplexität und Wartungskosten stark an, was die Entwicklung verlangsamte und die Fehlerquote erhöhte
Probleme mit einzelnen Repositories und Shared Libraries
- Jede Destination verwendet ein anderes API-Format, weshalb benutzerdefinierter Transformationscode nötig war
- Anfangs wurde alles in einem einzigen Repository verwaltet, doch fehlschlagende Tests wirkten sich auf alles aus, sodass eine Aufteilung der Repositories erfolgte
- Mit der späteren Ergänzung von mehr als 50 neuen Destinations entstanden über 50 Repositories
- Für gemeinsame Funktionen wurden Shared Libraries eingeführt, doch Versionsunterschiede und der Aufwand für Deployments nahmen zu
- Da sich die Lastmuster je Service unterschieden, waren Auto-Scaling-Einstellungen schwer abzustimmen; in manchen Fällen mussten Operatoren manuell eingreifen
Umstellung auf den Monolithen und Einführung von Centrifuge
- Es fiel die Entscheidung, mehr als 140 Services in einem einzigen Service zu konsolidieren
- Um die einzelnen Queues zu ersetzen, wurde das Centrifuge-System entwickelt, das alle Events an einen einzigen Service weiterleitet
- Centrifuge entwickelte sich später zur Connections-Backend-Infrastruktur von Twilio Segment weiter
- Mit der Umstellung auf einen Single-Service-Aufbau konnten der operative Aufwand reduziert und die Reaktion auf Störungen vereinfacht werden
Monorepo und Testautomatisierung
- Der gesamte Code für die Destinations wurde in ein einziges Repository zusammengeführt, und mehr als 120 Abhängigkeiten wurden auf eine einheitliche Version gebracht
- Das vereinfachte die Versionsverwaltung und verbesserte die Wartungseffizienz
- Für die Testautomatisierung wurde der Traffic Recorder eingeführt
- Er zeichnet echte HTTP-Requests und -Responses auf und spielt sie wieder ab, wodurch die Abhängigkeit von externen Netzwerken entfällt
- Die Testdauer sank von mehreren Minuten auf den Millisekundenbereich, bei deutlich höherer Stabilität
- Die Fehlerrate bei Tests sank, und die Produktivität der Entwickler stieg deutlich
Auswirkungen und Trade-offs der monolithischen Struktur
- Nach der Konsolidierung in einen Single Service verbesserten sich Deployment-Geschwindigkeit und Entwicklungseffizienz deutlich
- Innerhalb eines Jahres stieg die Zahl der Verbesserungen an Shared Libraries von 32 auf 46
- Ein einzelner Engineer kann ein Deployment innerhalb weniger Minuten durchführen
- Auch die operative Effizienz verbesserte sich, da Lastspitzen durch große Worker-Pools aufgefangen werden können
- Es gibt jedoch auch Nachteile, darunter schwierigere Fehlerisolation, geringere Cache-Effizienz und Risiken bei Dependency-Updates
- Ein Teil dieser Nachteile wird durch einfacheren Betrieb und höhere Produktivität ausgeglichen
Fazit
- Microservices lösten anfangs Leistungsprobleme, sind aber für großflächige Skalierung und gebündelte Updates weniger geeignet
- Durch die Umstellung auf einen Monolithen wurden Betriebsstabilität und Entwicklungsgeschwindigkeit gleichermaßen verbessert
- Für eine erfolgreiche Umstellung sind ein robustes Testsystem und die Akzeptanz von Trade-offs entscheidend
- Twilio Segment setzt zwar in Teilen seiner Infrastruktur weiterhin auf Microservices, bewertet jedoch für serverseitige Destinations einen Monolithen als die geeignetere Struktur
2 Kommentare
Es scheint riskant zu sein, alles bis ins kleinste Detail aufzuteilen und zu normalisieren.
Hacker-News-Kommentare
Als man den Code für alle Ziele in einem einzigen Repo zusammenführte, konnte man ihn zu einem einzelnen Service verschmelzen.
Das Ergebnis war eine deutlich höhere Entwicklungsgeschwindigkeit. Jetzt müssen nicht mehr über 140 Services deployt werden, nur weil eine gemeinsame Bibliothek geändert wurde.
Ein einzelner Engineer kann nun innerhalb weniger Minuten deployen.
Wenn wegen einer Bibliotheksänderung alle Services neu deployt werden müssen, dann ist das kein echter Service, sondern ein verteilter Monolith.
Schon die Vorstellung, gemeinsame Bibliotheken zwanghaft über alle Services hinweg zu synchronisieren, passt nicht zur Philosophie einer Service-Architektur.
Das ist weniger „bei jedem Bibliotheks-Update alles neu deployen“ als vielmehr ein Amazon-artiges gemeinsames Build- und Deployment-System.
Bibliotheken werden aus einer zentral verwalteten Quelle bezogen, und bei Versionsunterschieden müssen wegen Kompatibilitätsproblemen alle migriert werden.
Wenn wegen einer Sicherheitslücke eine bestimmte Version entfernt werden muss, ist ein kompletter Redeploy nötig, aber die Vorteile der zentralisierten Verwaltung sind viel größer.
Solche Systeme werden weiterhin als Mikroservices eingeordnet, verhalten sich aber in Bezug auf Kosten und operative Effizienz wie eine gemeinsame Umgebung.
Das als verteilten Monolithen zu bezeichnen, ist eine Überinterpretation.
Wenn man dem Mikroservice-Muster folgt, steigt das Deployment-Risiko, auch wenn man das anfangs nicht bemerkt.
Wenn man zum Beispiel einen Bug in einer Geld-Bibliothek behoben hat, steht man realistisch vor der Frage, ob nun alle Services neu deployt werden müssen.
Eine Bibliothek mit Sicherheitslücken muss unabhängig vom Systemdesign ohnehin überall ersetzt werden.
In solchen Fällen ist eine monolithische Struktur sogar leichter zu handhaben.
Echte Mikroservices sollten Nachrichten austauschen und JSON verwenden.
Man sollte nur die API kennen müssen, nicht den Code. Nur dann kann unabhängig deployt und skaliert werden.
Ist es nicht sinnvoller, gemeinsame Module zu verwenden?
In meiner vorherigen Firma lief alles als Mikroservices, die Firma davor setzte auf AWS Serverless.
In beiden Fällen war die Kommunikation zwischen den Services das größte Problem. Contracts ließen sich schwer synchronisieren, und auch Deployments waren kompliziert.
Anfangs ging es schnell voran, aber mit der Zeit explodierte die Komplexität. Es entstand fear-based development, und es gab viel zu viele Meetings.
Meine jetzige Firma hat eine monolithische Struktur, und sie ist viel einfacher zu handhaben. Typen sind klar, Refactorings einfach.
Es ist spannend zu sehen, wie auf unserer eigenen Plattform aufgebaute AI-Agenten sich innerhalb der Codebasis selbst verbessern.
Der einzige Nachteil sind längere Build-Zeiten, aber durch Fortschritte in der Toolchain erwarte ich bis 2026 10-mal schnellere Deployments.
Mein Fazit ist, dass wir dank der monolithischen Struktur deutlich schneller wachsen und skalieren konnten.
In monolithischen Strukturen wurde die Trennung von Zuständigkeiten ständig verletzt, und die Kopplung zwischen Teams war stark.
Echte Geschwindigkeit und Skalierung waren nur möglich, wenn die Teams voneinander getrennt waren.
Der Wechsel von ORM zu DTO hat 2 Jahre, 50 Teams und mehr als 150 Personen gebraucht.
Eine so komplexe Arbeit wäre ohne Mikroservices nicht möglich gewesen.
Beim Lesen dieses Artikels wirkt es so, als liege das Kernproblem nicht in der technischen Wahl Mikroservices vs. Monolith, sondern in der Qualität und Struktur der Engineering-Organisation.
Code-Repository und Teststruktur zeigen das Niveau der Organisation direkt.
Wenn niemand sagen kann „Das machen wir nicht“, explodiert die Komplexität.
Es braucht eine Führungsperson mit Autorität, damit das Team innehält und nachdenkt.
Wenn es API-Probleme gab, wurde die Ursache nicht analysiert, sondern nur die Daten angepasst und das Ticket geschlossen.
Selbst wenn dasselbe Problem wiederkam, wurde die eigentliche Ursache nicht behoben.
Schon im Interview kann man den Aufbau der Codebasis eines Unternehmens bis zu einem gewissen Grad vorhersagen.
Das ist weniger eine echte Rückkehr zum Monolithen als weiterhin eine SOA-Struktur.
Nur der Umfang der Services ist größer geworden.
Wenn ein Team 140 Services verwaltet, dann dient SOA der Skalierung des Teams, nicht der Skalierung der Services.
Wenn ein einzelnes Team alle gemeinsamen Bibliotheken verwaltet, entstehen Versionskonflikte und API-Verwirrung.
Am Ende bestimmt die Organisationsstruktur die Architektur. Ein Team hat integriert, um die Komplexität zu senken.
Das ist kein „Monolith“, sondern ein auf Team-Ebene passend zugeschnittener Service-Level.
Ich halte so eine Struktur für ideal. Wenn das Team größer wird, muss man wieder aufteilen.
Ich bin kein Mikroservice-Verfechter, aber die falsche Dichotomie Monorepo vs. Mikroservices fällt mir auf.
Zu viele Tools setzen stillschweigend ein 1:1-Verhältnis zwischen Service und Repo voraus.
Man kann aber trotzdem alles in einem Repo haben und unabhängig deployen.
Es wäre schön, wenn man bei GitHub oder ähnlichen Plattformen Ordner als eigenständige Services behandeln könnte.
Mit Bazel wurde der Abhängigkeitsbaum verwaltet, und über
bazel querywurden betroffene Targets gefunden, um Tests automatisch auszuführen.Zusammen mit GitHub Actions haben wir einen Workflow gebaut, der PRs blockiert.
Das funktionierte gut, aber der Aufbau hat mehrere Monate gedauert.
Das eigentliche Problem war mangelnder Betrieb und fehlende Tooling-Unterstützung — CI, Autoscaling und On-Call-Strukturen waren alle unzureichend.
Beide Ansätze können scheitern.
In Umgebungen wie Node.js oder Python gibt es Grenzen dafür, wie viel Code die Event Loop verarbeiten kann.
Ich habe schon erlebt, dass 6–8 Personen 200 Services verwaltet haben, und auch, dass 80 Personen einen einzigen Monolithen betreut haben.
Mikroservices sind bei kleinen Änderungen im Vorteil, aber bei systemweiten Änderungen schwierig,
monolithische Strukturen genau umgekehrt.
Am Ende sind nicht die Architektur, sondern Abstraktion, Tests und die Art der Entkopplung entscheidend.
Der Maßstab für „micro“ ist nicht die Technik, sondern die Business-Einheit.
Wenn man noch kleiner schneidet, werden daraus Nanoservices.
In Umgebungen wie Beam, JVM, Rust oder Go ist das längst gelöst.
Geht es um CPU-Cache-Probleme?
Ich dachte, dort nehme man eher Go, Java oder C#.
In den meisten Firmen waren Mikroservices eher die Ursache von 90 % der Probleme.
Wenn man keine riesige Organisation wie AWS, Google oder Netflix ist, passt das meist nicht.
Es ist schon schwer genug, ein System in sinnvoll kombinierbare Einheiten zu zerlegen; zusätzlich noch Netzwerkgrenzen einzuziehen, ist töricht.
Der nächste Trend wird sich wohl von React und SPAs weg und hin zu einer serverzentrierten Ausrichtung bewegen.
Dass man auf Mikroservices umgestiegen ist, „weil Tests oft kaputtgingen“, wirkt wie ein völlig verkehrter Ansatz.
Weil Tests fehlschlagen, die gesamte Struktur der Codebasis umzubauen, ist seltsam.
Durch getrennte VMs und CI/CD-Konfigurationen pro Team verschwanden die Testkonflikte.
Der Nachteil ist, dass Konflikte zwischen Features nicht sofort sichtbar werden, aber durch klare Code-Zuständigkeiten war das kein großes Problem.
Es gab die Bitte, dem Titel [2018] hinzuzufügen.
Es heißt, man habe das Repo getrennt, weil „bei kaputten Tests auch nicht verwandter Code geändert werden musste“,
aber es hätte wohl auch andere Lösungen gegeben, etwa die Art der Testausführung zu ändern oder manuelle Deployments zuzulassen.
Die Trennung des Repos war nicht die einzige mögliche Lösung.