Alle Artikel
12. September 2026
7 mins

Der verteilte Monolith

Von Tim Rutte, Cloud & Software ArchitectThemaBackend & Plattformen

Fünf getrennte weiße Kisten, die ein einziges straff gespanntes blaues Seil aneinanderbindet.

Es gibt einen Zustand, der schlechter ist als der Monolith, aus dem man kam, und er wird regelmäßig erreicht, wenn man ihn verlassen wollte. Sechs Dienste, sechs Repositorys, sechs Auslieferungswege. Und ein Ausrollen, das nur funktioniert, wenn alle sechs in der richtigen Reihenfolge gehen.

Das ist ein verteilter Monolith: eine Architektur mit den Kosten der Verteilung und den Abhängigkeiten des Monolithen. Man zahlt für Netzwerk, Ausfallsicherheit, Betrieb und Nachvollziehbarkeit und bekommt dafür keine der Freiheiten, wegen derer man geteilt hat.

Dieser Artikel handelt davon, wie man den Zustand erkennt, warum er entsteht, und wie man ihn verlässt, ohne wieder alles neu zu bauen.

Fünf Sätze, und Sie wissen es

Die Diagnose braucht kein Werkzeug. Trifft mehr als einer dieser Sätze zu, ist die Antwort klar.

Zwei Dienste müssen zusammen ausgerollt werden, sonst geht etwas kaputt. Dann sind es keine zwei Dienste, sondern einer mit einer Netzwerkverbindung in der Mitte.

Zwei Dienste schreiben in dieselbe Tabelle. Damit ist das Schema eine gemeinsame Schnittstelle, die niemand als solche behandelt, und jede Spaltenänderung ist ein abgestimmtes Vorhaben.

Ein Aufruf löst eine Kette von vier weiteren aus, alle synchron. Die Antwortzeit ist dann die Summe, und die Verfügbarkeit das Produkt: Fünf Dienste mit je 99,9 Prozent ergeben 99,5.

Eine Testumgebung braucht alle Dienste, um einen einzigen zu prüfen. Das ist der teuerste Befund in der Liste, weil er täglich Zeit kostet.

Ein neues Feld braucht Änderungen in drei Repositorys. Wenn eine fachliche Änderung regelmäßig drei Dienste anfasst, verläuft die Grenze zwischen ihnen quer zur Fachlichkeit.

Warum die Schnitte so liegen

Keiner dieser Zustände ist das Ergebnis von Unfähigkeit. Sie entstehen aus zwei Entscheidungen, die zum Zeitpunkt der Entscheidung vernünftig aussahen.

Geschnitten wurde entlang der Substantive. Kunden-Dienst, Produkt-Dienst, Bestell-Dienst. Das ist die Einteilung, die im Datenmodell steht, und sie ist naheliegend. Nur läuft ein Geschäftsvorgang quer dazu: Eine Bestellung aufgeben berührt Kunde, Produkt, Lager, Preis und Zahlung. Wer nach Substantiven schneidet, macht aus jedem Vorgang einen verteilten Aufruf.

Geschnitten wurde entlang der Teams. Das ist nicht falsch, aber es beschreibt, wer arbeitet, nicht, was zusammengehört. Ändert sich die Aufstellung, bleiben die Grenzen stehen.

Der Schnitt, der trägt, folgt einer dritten Größe: der Grenze einer Transaktion. Was in einem Schritt gemeinsam gültig sein muss, gehört in einen Dienst. Was danach passieren darf, darf über die Grenze. Diese Frage ist unbequemer als die nach Substantiven, weil sie Fachwissen verlangt, und sie ist die einzige, deren Antwort Jahre hält.

Wie ein erster Schnitt aussieht, der diese Lage gar nicht erst erzeugt, steht in Welchen Service Sie zuerst aus dem Monolithen lösen.

Die gemeinsame Datenbank

Unter allen Symptomen ist eines das teuerste, und es wird am häufigsten übersehen, weil es nicht wehtut, solange nichts geändert wird.

Teilen sich zwei Dienste eine Tabelle, dann ist jede Annahme des einen über die Struktur eine Abhängigkeit vom anderen, die nirgends steht. Kein Vertrag, keine Version, kein Test. Die Änderung einer Spalte wird zu einem Vorgang mit Terminabstimmung, und genau das sollte die Teilung verhindern.

Der verbreitete Reparaturversuch ist, eine Schnittstelle davorzusetzen und den zweiten Dienst darüber lesen zu lassen. Das verschiebt das Problem: Aus einer unsichtbaren Kopplung an das Schema wird eine sichtbare an einen Aufruf, und die Antwortzeit liegt jetzt bei der Verfügbarkeit des anderen Dienstes.

Belastbar ist der dritte Weg, und er ist unbequemer: Der Dienst, der die Daten braucht, bekommt seine eigene Kopie der Teilmenge, die er tatsächlich nutzt, gefüllt aus Ereignissen des besitzenden Dienstes. Das ist mehr Arbeit, es bedeutet, mit leicht veralteten Daten zu leben, und es ist das Einzige, was die beiden Dienste wirklich trennt.

Der Umbau der Tabelle selbst läuft dabei nach dem bekannten Muster: Expand und Contract, erst neue Struktur daneben, dann Umstellung der Leser, dann Wegnahme der alten. Ein Schnitt in einem Schritt scheitert an genau dem Dienst, den man vergessen hat.

Was hilft, bevor irgendetwas umgebaut wird

Der Umbau dauert Monate. Die schlimmste Eigenschaft eines verteilten Monolithen lässt sich in einer Woche entschärfen, und diese Woche ist die beste Investition in der ganzen Liste.

Diese Eigenschaft ist: Ein einzelner langsamer Dienst reißt alles mit. Aufrufe ohne Zeitgrenze stapeln sich, Verbindungen laufen voll, und ein Problem in einem Randdienst wird in vier Minuten zu einem Ausfall der Startseite.

Dagegen helfen drei Dinge, und keines davon setzt eine Architekturentscheidung voraus:

// 1. Eine Zeitgrenze an JEDEN ausgehenden Aufruf. Der Standardwert
//    vieler Clients ist "unendlich", und genau das ist der Fehler.
klient := &http.Client{Timeout: 800 * time.Millisecond}

// 2. Ein Schalter, der nach genug Fehlern aufhoert zu fragen.
//    Ein ausgefallener Dienst wird durch Warten nicht schneller.
var schalter = gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name: "lager",
    ReadyToTrip: func(z gobreaker.Counts) bool {
        return z.ConsecutiveFailures > 5
    },
    Timeout: 30 * time.Second,
})

func Bestand(ctx context.Context, artikel string) (int, error) {
    wert, err := schalter.Execute(func() (any, error) {
        return holeBestand(ctx, klient, artikel)
    })
    if err != nil {
        // 3. Ein definierter Rueckfall statt einer Fehlerseite.
        //    "Verfuegbarkeit unbekannt" ist eine Antwort,
        //    ein Zeitueberlauf nach 30 Sekunden ist keine.
        return 0, ErrBestandUnbekannt
    }
    return wert.(int), nil
}

Der dritte Punkt ist der, der am seltensten gemacht wird und am meisten bringt. Für fast jeden Aufruf gibt es eine sinnvolle Antwort ohne den anderen Dienst: der letzte bekannte Wert, ein Hinweis statt einer Zahl, ein Weiterkommen ohne diese Information. Wer die für jeden Aufruf einmal festlegt, hat aus einer Kette von Abhängigkeiten eine Reihe von Verschlechterungen gemacht.

Eine Verbindung nach der anderen lösen

Der Weg heraus geht nicht über einen Plan für alle sechs Dienste, sondern über die eine Verbindung, die am meisten schmerzt. Meistens ist es die, die in der Kette am weitesten hinten steht und trotzdem synchron auf die Antwort wartet.

Der Schnitt dort ist eine Frage, die die Fachseite beantworten kann: Muss das jetzt passieren, oder muss es passieren? Die Rechnung muss nicht existieren, bevor die Bestellung bestätigt wird. Die Benachrichtigung auch nicht. Die Bestandsprüfung schon.

Für alles, was nur passieren muss, wird der Aufruf zu einem Ereignis. Damit verschwindet die Abhängigkeit in der Antwortzeit und in der Verfügbarkeit gleichzeitig, und der empfangende Dienst darf für eine Stunde weg sein, ohne dass es jemand merkt.

Die Bedingung dafür steht im Kleingedruckten und wird gern übersehen: Der Empfänger muss ein Ereignis zweimal verarbeiten können, ohne dass es zweimal wirkt. Ohne Idempotenz tauscht man eine synchrone Abhängigkeit gegen doppelte Rechnungen, und das ist kein Fortschritt.

Wie aus einem synchronen Aufruf ein Auftrag wird, steht in Von Cronjobs zu Queues.

Zusammenlegen ist auch eine Lösung

Zum Schluss die Möglichkeit, die in solchen Diskussionen selten ausgesprochen wird, obwohl sie oft die richtige ist.

Wenn zwei Dienste immer zusammen ausgerollt werden, immer zusammen geändert werden und dieselben Daten brauchen, dann sind sie einer. Sie wieder zusammenzulegen ist kein Rückschritt, sondern die Korrektur eines Schnitts, der sich als falsch erwiesen hat. Ein Dienst weniger heißt: ein Auslieferungsweg weniger, eine Netzwerkgrenze weniger, eine Fehlerquelle weniger.

Der brauchbare Zielzustand für die meisten gewachsenen Systeme ist deshalb nicht „möglichst viele kleine Dienste", sondern ein modularer Monolith mit zwei oder drei echten Diensten daneben: dort, wo eine andere Sprache, eine andere Last oder ein anderer Auslieferungsrhythmus das rechtfertigt. Alles dazwischen ist der Zustand, von dem dieser Artikel handelt.

Wie ich Dienste neben einem Bestandssystem schneide, steht auf einer eigenen Seite.

Dieser Artikel gehört zu einer Reihe über Systeme, die es schon gibt. Der Rückblick ordnet alle Artikel der Reihe nach Anlass.