Amazons neuer Modulith – Services neu zuschneiden macht noch keinen Monolithen

Ein Blogpost des Amazon-Prime-Video-Entwicklerteams stiftet Verwirrung: Kehrt AWS Microservices den Rücken zu? Kris Köhntopp klärt das Missverständnis auf.

In Pocket speichern vorlesen Druckansicht 26 Kommentare lesen
Stonehenge

(Bild: Vlukas/Fotolia)

Lesezeit: 8 Min.
Von
  • Kristian Köhntopp
Inhaltsverzeichnis

"Computer sind einfach", erzähle ich gerne den Menschen, die ich unterrichte. "Es gibt nur Nullen und Einsen, und viel komplizierter wird es nicht mehr." – "Aber ich finde das alles schwierig", bekomme ich dann oft zu hören.

Eine Analyse von Kristian Köhntopp

Kristian Köhntopp hat 1983 angefangen zu programmieren und ist seit 1988 online. Seitdem hat er verschiedene Dinge mit Computern aller Art getan, denkt angesichts der Lage aber über eine Karriere als Landschaftsgärtner nach.

Das stimmt. Komplexität kommt bei Computersystemen fast nie aus den einzelnen Schichten, sondern aus der Höhe und Breite des Stacks, in dem wir die Komponenten auf- oder nebeneinander stapeln. Wer beim Blockschaltbild einer CPU anfängt und dann die Schichten des Stacks aufeinanderstapelt, um bei einer typischen lokalen Einprozessanwendung in einer objektorientierten Sprache mit GUI aufzuhören, kommt vorsichtig geschätzt auf zwei bis drei Dutzend Abstraktionsebenen und "Dinge, von denen man die Interna kennen könnte", die miteinander interagieren. Und das ist nur lokal, noch ohne Netzwerkstacks, Kommunikations-Protokolle, Proxies und andere Intermediäre – ohne die Komplexitäten verteilter Systeme, die dieselbe Übung noch einmal aufmachen, nur quer statt längs.

Es spricht für die Informatik als Wissenschaft, dass überhaupt etwas funktioniert. All die Interfaces, Kapselungen und Abstraktionen sind offensichtlich zu etwas gut – solange sie dicht halten. Wenn sie es nicht tun, dann werden Dinge jedoch oft schnell komisch: Im Stack eine kleine Änderung vorgenommen, etwa ein Epsilon, und irgendwo anders verhält sich etwas anderes unerwartet nicht-linear, macht ein gigantisches Delta und alle sind plötzlich sehr traurig.

Wenn Entwickler also gerne abgeschlossene Büros, kein Telefon und schalldichte Kopfhörer haben wollen, dann deswegen, weil das Arbeiten an solchen Systemen im Wesentlichen darin besteht, den Stack im Kopf aufzuklappen und beim Schreiben einer Zeile in einer Abstraktionsschicht die Effekte im Kopf zu antizipieren, die dies auf anderen Schichten haben könnte, oder auf anderen Systemen anderswo in einem verteilten System.

Vor diesem Hintergrund lässt sich die Ankündigung des Amazon-Streaming-Teams lesen, die Kosten für das Monitoring von Audio und Video beim Streamingdienst Prime Video um 90 Prozent gesenkt zu haben. Amazon setzt damit eine Funktionsweise um, die Frames aus zum Kunden gesendeten Videostreams abzweigt und analysiert. Bestimmte, die Qualität beeinflussende Artefakte erkennt das System und meldet sie, damit sie korrigiert werden können. Amazontypisch setzten die Entwickler dies als einen Haufen Microservices um, die mit Lambdas und Step Functions hantieren, um Daten aus einem S3 Bucket in einen anderen Bucket zu schaufeln.

Meldungen über Probleme werden auf einen SNS Message Bus gelegt. Anders ausgedrückt, ohne Amazon-Worte zu verwenden: Sie packten Code in Container, wo er auf eine spezifische, für die Programmiersprache vorbereitete Umgebung trifft, und diesen anschließend in einen Container-Cluster gestellt, wo er bei Bedarf durch Ereignisse aktiviert und ausgeführt wird. Der Code wird aktiviert, wenn in einem über HTTPS erreichbaren Speicherbereich neue Daten vorliegen, und schreibt seine Ergebnisse in einen ebensolchen Bereich weg, um dann über einen Eventbus eine Meldung darüber abzusetzen, dass neue Ergebnisse vorliegen.

Das hat funktioniert wie erwartet: Das Projekt war schnell vollendet und konnte zeigen, dass es tut, was man von ihm fordert. Beim Hochskalieren stellte das Team dann fest, dass der Zuschnitt der verteilten Komponenten ungünstig war: Lambdas und Step Functions sind keine guten Elemente, um im Batch die Frames von Videostreams an ML Detectors zu verfüttern, denn dafür waren sie primär niemals gedacht – es handelt sich um Komponenten zum Umsetzen von Webanwendungen.

Selbstverständlich ist S3 auch kein guter Speicher für schnelle temporäre Interprozesskommunikation, denn als Webspeicher mit niedrigen Kosten ist er für niedrige Transaktionsraten gedacht und für Dienste, die hohe Latenzen tolerieren können. Die im Artikel des Amazon-Teams vorgestellte Lösung war dann, all diese Dinge in einem einzigen Container zusammenzuziehen, und ihn in die Amazon Elastic Container Services (ECS) zu werfen.

Da die für eine Teilaufgabe notwendigen Komponenten sich nebeneinander im selben Container, und damit auf derselben Maschine, befinden, können sie über Speicher miteinander kommunizieren, statt Frames mit https durch S3 zu drücken. Ebenso fallen die Lambdas und Step Functions irgendwo weit draußen im AWS-internen Netz weg und werden durch lokale Container für denselben Code ersetzt oder gar zu lokalen Funktionsaufrufen.

Wie gewünscht entfernt dies viel Kommunikationslatenz und die damit einhergehenden Kosten, weil ein ausgesprochen schnell drehender Teil eines verteilten Systems in ein lokal kommunizierendes System umgewandelt worden ist. Daten müssen nun nicht mehr über das Netz geschickt werden, sondern stehen lokal in gemeinsamen Speicher zur Verfügung. Weil niemand mehr auf das Netz warten muss, geht alles viel schneller und günstiger.

Das war ein unerwartet schmerzloser Prozess: "Das Konzept der High-Level-Architektur ist gleich geblieben. Wir haben immer noch genau die gleichen Komponenten wie im ursprünglichen Entwurf (Medienkonvertierung, Detektoren oder Orchestrierung). Dadurch konnten wir eine Menge Code wiederverwenden und schnell zu einer neuen Architektur migrieren", heißt es in dem Artikel des Amazon-Teams.

Und das ist vielleicht die Erfahrung, die man aus diesem Bericht mitnehmen kann: In einem sauber konzipierten und gekapselten System mit einer guten Software-Architektur kann man die Komponenten ohne große Codeänderungen rekonfigurieren und den Zuschnitt des Systems ändern – ohne, dass man bei null neu anfangen muss. Geändert wurde nicht so sehr die Architektur, sondern ihre Verteilung auf die ausführenden Komponenten, also das Deployment.

Der Untertitel des Blogposts von Amazon macht es Lesern leider schwer, die wesentliche Erkenntnis aus dem Text mitzunehmen, denn er setzt falsche Erwartungen: "Durch den Wechsel von einer verteilten Microservices-Architektur zu einer monolithischen Anwendung konnten eine höhere Skalierbarkeit und Ausfallsicherheit erreicht und die Kosten gesenkt werden", ist im ursprünglichen Blogartikel bei Amazon zu lesen.

Was das AWS-Prime-Video-Team da gebaut hat, ist am ehesten noch ein Modulith, nur dass man bei Amazon Prime Video den umgekehrten Weg gegangen ist. Martin Fowler propagiert in einem Blogeintrag von 2015 unter dem Titel "Monolith first" ein System als Einprozesssystem zu planen und prototypisch zu entwickeln. Zum Skalieren wird man dann Teile abspalten. Das Team hat stattdessen in der AWS-Infrastruktur vorhandene Komponenten für verteilte, eventbasierte Systeme verwendet, um einen Prototyp zu realisieren.

Dann hat es das Deployment geändert, um stark gekoppelte Komponenten dicht beieinander auszuführen, ohne dabei ihre modulare Struktur aufzugeben. Das ist keine so ungewöhnliche Idee: Schon Fowler selbst verweist in seinem Monolith-First-Artikel auf einen einschlägigen Artikel über Microservices von Sam Newman und dessen daraus entstandenes Buch über die erfolgreiche Entwicklung von Microservices.

Noch dazu ist das Resultat kein klassischer Single-Instance-Monolith, sondern ein Zusammenlegen eng gekoppelter Komponenten in einem Container, der dann in ECS selbstverständlich mit der für die Skalierung notwendigen Parallelität instanziiert wird. Und der "Modulith" ist selbst Bestandteil eines noch größeren, mit Lambdas und Messages verteilt kommunizierenden Systems.

Der Blogeintrag von Amazon ist ein Beispiel dafür, wie man Code ohne viel Änderungsaufwand in unterschiedlichen Formen auf unterschiedlichen Substraten zum Laufen bringen kann. Dazu braucht es insbesondere eine sauber strukturierte und in gut isolierte Komponenten unterteilte Anwendung mit sauberen Interfaces. Zudem gute Observability, damit sich die Stellen identifizieren lassen, an denen die verteilte Anwendung ungünstig geschnitten ist.

Wie hier vorgeführt wurde, ist es auf diese Weise leicht möglich, auf wechselnde Skalierungsanforderungen oder sich ändernde Geschäftsaufgaben schnell und kosteneffektiv zu reagieren. Oder mit anderen Worten: Leute, die verstehen, was wirklich vorgeht, haben wenig Schwierigkeiten, ihre Anwendungen auf variable Anforderungen anzupassen. Wenn die Komplexität in der Informatik aus der Höhe und Breite des Stacks kommt und den Stellen, an denen unsere Kapselungen und Abstraktionen versagen, dann ist es gut, das System so zu bauen, dass man es nach Bedarf anders zuschneiden und verteilen kann, ohne es dabei neu schreiben zu müssen.

(sih)