Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Lire en français

Abschnitt 9.1 von 9.8 · Mittelstufe · 28 Min. Lesezeit

Skalierungsstrategien: vertikal, horizontal und der Scale Cube

Videolektion

5 Min. · auf Englisch gesprochen, mit Untertiteln
Nur anhören (Audio)
Transkript lesen

Willkommen zu Abschnitt neun Punkt eins. Wenn mehr Menschen ein System nutzen, müssen Sie Kapazität hinzufügen. In dieser Lektion geht es um die praktischen Entscheidungen: welche Ressourcen, wo, wann und wie automatisch.

Denken Sie an ein gut besuchtes Café. Sie können mehr Gäste bedienen, indem Sie eine größere, schnellere Kaffeemaschine kaufen. Das ist Scale-up. Oder Sie öffnen mehr Theken mit mehr Personal. Das ist Scale-out. Es funktioniert nur, wenn jede Theke jeden Gast bedienen kann. Sie können die Arbeit auch aufteilen: eine Theke für Getränke und eine für Essen, oder ein Café pro Stadtviertel. Und weil zusätzliches Personal Zeit braucht, um anzukommen, plant eine vernünftige Leitung es vor dem Mittagsansturm ein.

Scale-up, also vertikale Skalierung, gibt einem Rechner mehr Leistung, und an der Software ändert sich nichts. Aber es gibt einen größten Rechner, die Preise steigen im oberen Bereich steil, und ein Rechner ist eine Fehlerdomäne. Scale-out, also horizontale Skalierung, betreibt mehr Instanzen hinter einem Load Balancer. Bei acht Instanzen kostet der Verlust einer Instanz ein Achtel der Kapazität, nicht die ganze. Der Preis: Jede Instanz muss jede Anfrage bearbeiten können, und die Arbeit muss teilbar sein. Skalieren Sie vor allem den aktuellen Engpass. Die Webschicht zu verdoppeln, wenn die Datenbank zu fünfundneunzig Prozent ausgelastet ist, bringt nur eine höhere Rechnung.

Martin Abbott und Michael Fisher beschreiben mit dem Scale Cube drei verschiedene Arten des Scale-out. Seine Ecke ist eine einzelne Instanz, die alles erledigt. Die X-Achse ist Klonen: identische Kopien hinter einem Load Balancer, jede erledigt die ganze Arbeit. Sie ist die günstigste Achse, aber jeder Klon arbeitet weiterhin mit denselben Daten. Die Y-Achse teilt nach Funktion: eigene Services für Speisekarten, Bestellungen, Disposition und Zahlungen, die jeweils getrennt skalieren und ausfallen. Das ist die Achse hinter Microservices. Die Z-Achse teilt nach Daten: derselbe Code, aber jede Kopie verwaltet einen Teil der Kunden, etwa eine Zelle oder ein Shard pro Stadt. Ein Fehler in einer Zelle trifft nur deren Kunden. Reale Systeme kombinieren alle drei. Wählen Sie die günstigste Achse, die das tatsächliche Problem löst.

Klonen funktioniert nur, wenn jeder Klon jede Anfrage bedienen kann; der Service muss also zustandslos sein. Zustand versteckt sich in Sessions im Arbeitsspeicher, Dateien auf der lokalen Festplatte, geplanten Jobs, prozessinternen Sperren und Zählern. Jedes davon wandert in einen gemeinsamen Speicher. Zustandslosigkeit lässt sich günstig von Anfang an einplanen und nur teuer nachrüsten.

Autoscaling schließt einen Regelkreis. Nutzer senden Traffic über einen Load Balancer an die Instanzen. Die Instanzen melden ihre Last mit Verzögerung an einen Metrik-Store. Der Autoscaler vergleicht die Metrik mit seinem Zielwert und wählt eine Anzahl von Instanzen. Der Orchestrator oder die Cloud-API startet oder stoppt dann Instanzen. Und jede neue Instanz muss booten, sich aufwärmen und ihre Readiness-Prüfung bestehen, bevor sie Traffic erhält. Vom Lastanstieg bis zur nutzbaren Kapazität vergehen oft zwei bis zehn Minuten. Reaktive Skalierung bewältigt also allmähliche Änderungen. Plötzliche Sprünge brauchen Kapazitätsreserve, Queues oder im Voraus eingeplante Kapazität.

Welches Signal sollte den Regelkreis steuern? Eine Bestell-API wartet meist auf die Datenbank und andere Services, daher kann ihre CPU bei fünfundzwanzig Prozent liegen, während jeder Thread belegt ist. Besser ist ein Signal, das an die ausgehende Ressource gekoppelt ist: laufende Anfragen pro Instanz oder, bei Queue-Workern, der Rückstand pro Worker. Die Latenz steigt aus Gründen, die mehr Instanzen nicht beheben können, etwa bei einer langsamen Datenbank. Nutzen Sie sie für Alerts, nicht zum Skalieren. Und gegen Flapping skalieren Sie schnell hoch und langsam herunter, mit einer Mindest- und einer Höchstzahl an Instanzen.

Dimensionieren wir die Bestell-API der Essenslieferplattform für die Mittagsspitze. Die geschäftigste Minute erreichte zweitausendfünfzig Anfragen pro Sekunde, bei erwarteten fünfzehn Prozent Wachstum. Planen Sie also mit zweitausendvierhundert. Jede Instanz hat zweiunddreißig Worker-Threads und braucht hundertzwanzig Millisekunden pro Anfrage. Nach Littles Gesetz sind das zur Spitzenzeit zweihundertachtundachtzig laufende Anfragen. Für dreißig Prozent Reserve betreiben Sie jede Instanz bei siebzig Prozent ihrer Threads: zweiundzwanzig Komma vier Anfragen. Das ergibt zwölf Komma neun, also dreizehn Instanzen. Aber der Service muss den Verlust einer seiner drei Zonen überstehen. Verteilt auf fünf, vier und vier lässt der Verlust von fünf acht Instanzen mit je sechsunddreißig Anfragen übrig, mehr als ihre zweiunddreißig Threads. Lässt man beim Verlust einer Zone fünfundachtzig Prozent zu, braucht man elf Instanzen in zwei Zonen: sechs pro Zone, insgesamt achtzehn. Den Verlust einer Zone zu überstehen, nicht die durchschnittliche Last, bestimmt die Flottengröße. Prüfen Sie zum Schluss die darunterliegende Schicht. Ein Maximum von dreißig Instanzen mit je zehn Verbindungen braucht dreihundert, aber die Datenbank erlaubt zweihundert. Also fügt das Team zuerst einen Connection Pooler hinzu.

Der vollständige Abschnitt behandelt die Skalierung der Datenschicht und bietet eine Entscheidungshilfe. Als Nächstes betrachten wir in Abschnitt neun Punkt zwei Load Balancing und Sessions. Bis gleich.

Was Sie wissen müssen

Was Skalieren praktisch bedeutet

Abschnitt 2.4 hat Skalierbarkeit als die Fähigkeit definiert, durch zusätzliche Ressourcen mehr Last zu bewältigen, und mit dem Amdahlschen Gesetz und dem Universal Scalability Law gezeigt, warum mehr Ressourcen nicht immer mehr Kapazität bringen. In diesem Abschnitt geht es um die praktischen Entscheidungen: welche Ressourcen hinzukommen, wo, wann und wie automatisch. Der Ausgangspunkt ist immer dieselbe Frage: Was ist der Lastparameter, und welche Ressource geht zuerst aus? Die Bestell-API einer Essenslieferplattform ist zur Mittagszeit durch die Anfragen pro Sekunde begrenzt, ihr Dispositionsservice durch die Zahl aktiver Kuriere, deren Positionen alle paar Sekunden abgeglichen werden müssen, ihre Datenbank durch Schreibtransaktionen pro Sekunde und durch Verbindungen. Jede braucht eine andere Antwort.

Ein System zu skalieren heißt eigentlich, seinen aktuellen Engpass zu skalieren und dann den nächsten zu finden. Die Webschicht eines Systems zu verdoppeln, dessen Datenbank zu 95 % ausgelastet ist, bringt nichts außer einer höheren Rechnung und einer Datenbank, die jetzt noch stärker belastet wird. Deshalb beginnt jede Skalierungsentscheidung mit Messung: Auslastung und Sättigung jeder Ressource (die USE-Methode von Brendan Gregg, behandelt in Observability) und ein Lasttest, der zeigt, wo die Latenz zu steigen beginnt (Kapazitätsplanung und Performancetests).

Vertikale Skalierung: ein größerer Rechner

Vertikale Skalierung (Scale-up) bedeutet, dem vorhandenen Rechner mehr CPU-Kerne, mehr Arbeitsspeicher, schnellere Festplatten oder ein schnelleres Netzwerk zu geben. Ihr großer Vorzug: An der Software ändert sich nichts. Eine einzelne relationale Datenbank auf einem größeren Server behält ihre Transaktionen, ihre Joins und ihre einfache Konsistenz; eine Anwendung behält ihre In-Memory-Caches und ihre lokalen Dateien. In der Cloud ist eine Größenänderung ein API-Aufruf mit anschließendem Neustart. Die größten virtuellen Maschinen in der Cloud bieten heute Hunderte virtueller CPUs und Terabytes an Arbeitsspeicher, weit mehr, als die meisten Geschäftssysteme je brauchen werden, und einige sehr stark besuchte Websites haben öffentlich beschrieben, wie sie enormen Traffic mit wenigen leistungsstarken Datenbankservern bedienen.

Die Grenzen sind allerdings real. Es gibt einen größten Rechner, und der Preis pro Kapazitätseinheit steigt im oberen Bereich steil an. Ein Rechner ist eine Fehlerdomäne: Fällt er aus oder wird er für eine Größenänderung oder ein Patch neu gestartet, steht alles darauf still; Scale-up allein tut also nichts für die Verfügbarkeit. Software kann einen größeren Rechner oft nicht voll nutzen: Ein Single-Thread-Prozess, eine globale Sperre oder eine Hot Row in der Datenbank begrenzt den Geschwindigkeitsgewinn genau so, wie das Amdahlsche Gesetz es vorhersagt. Und Kapazität kommt in großen, langsamen Stufen, sodass Sie rund um die Uhr für die Spitze bezahlen. Trotzdem ist vertikale Skalierung weit häufiger der richtige erste Schritt, als die Mode vermuten lässt, besonders bei Datenbanken, wo Scale-out viel Entwurfsaufwand kostet.

Horizontale Skalierung: mehr Rechner

Horizontale Skalierung (Scale-out) bedeutet, mehr Instanzen zu betreiben und die Arbeit auf sie zu verteilen, meist mit einem Load Balancer davor. Sie reicht viel weiter, in kleinen und günstigen Schritten, und dieselbe Redundanz, die Kapazität schafft, toleriert auch Ausfälle: Bei acht Instanzen kostet der Verlust einer Instanz ein Achtel der Kapazität statt der gesamten. Cloud-Plattformen machen das Hinzufügen einer Instanz zu einer Sache von Sekunden oder Minuten; darauf beruht das Autoscaling.

Der Preis ist, dass die Software dafür entworfen sein muss. Jede Instanz muss jede Anfrage bearbeiten können; Instanzen dürfen also nichts behalten, wovon eine spätere Anfrage abhängt (der nächste Abschnitt). Die Arbeit muss teilbar sein: Ein Job, der eine globale Sperre braucht, läuft auf zehn Rechnern nicht schneller. Die Daten müssen von jeder Instanz aus erreichbar sein, was den Druck auf die gemeinsame Datenschicht verlagert. Und das System wird verteilt, mit den Teilausfällen, Timeouts und Wiederholungen, die Abschnitt 6.1 beschreibt. Die meisten realen Systeme kombinieren beides: Zustandslose Anwendungsschichten skalieren horizontal, während Datenbanken so lange vertikal skalieren, wie es vernünftig möglich ist, und dann mit Replikaten und Partitionen horizontal.

Vertikale und horizontale Skalierung im Vergleich

AnliegenVertikal (Scale-up)Horizontal (Scale-out)
Änderungen an der SoftwareKeineInstanzen müssen zustandslos und die Arbeit teilbar sein
ObergrenzeDer größte verfügbare RechnerMeist die gemeinsame Datenschicht oder Koordinationskosten
KostenkurveSteigt bei den größten Größen steil anUngefähr linear, in kleinen Schritten
Ausfall (Failure)Eine Fehlerdomäne; eine Größenänderung erfordert einen NeustartDer Verlust einer Instanz kostet einen Bruchteil der Kapazität
ÄnderungsgeschwindigkeitMinuten plus ein NeustartSekunden bis Minuten, auch automatisch
Konsistenz und TransaktionenUnverändert und einfachUnverändert bei zustandslosen Schichten; schwierig bei Daten
Typische VerwendungDatenbanken, Altsysteme und zustandsbehaftete Software, erste Antwort auf WachstumWeb- und API-Schichten, Worker, alles Zustandslose

Der Scale Cube: drei verschiedene Arten des Scale-out

Martin Abbott und Michael Fisher, die die Technologie bei eBay und PayPal leiteten, beschreiben Scale-out mit dem Scale Cube in The Art of Scalability (erste Auflage 2009, zweite Auflage 2015). Er unterscheidet drei unabhängige Arten, ein System aufzuteilen, jede als eine Achse eines Würfels gezeichnet, dessen Ursprung eine einzelne Instanz ist, die alles erledigt.

Die X-Achse ist horizontale Vervielfältigung, also Klonen: Betreiben Sie N identische Kopien der ganzen Anwendung oder des Service und verteilen Sie die Anfragen auf sie. Sie ist die einfachste und günstigste Achse und eignet sich gut für zustandslose Anfrageverarbeitung und Lesetraffic. Für die Datenmenge oder das Schreibvolumen bringt sie nichts, weil jeder Klon weiterhin mit denselben Daten arbeitet, und ebenso wenig für die Größe der Codebasis oder die Zahl der Teams, die daran arbeiten.

Die Y-Achse teilt das System nach Funktion auf: verschiedene Services für Speisekarten, Bestellungen, Disposition und Zahlungen, die jeweils nur den Code und die Daten für ihre Fähigkeit ausführen. Jeder Service kann dann getrennt skalieren, ausfallen und deployt werden, und jeder ist kleiner und leichter zu verstehen. Das ist die Achse hinter Microservices (Abschnitt 6.3), mit all ihren Kosten. Die Z-Achse teilt nach Daten auf: Jede Kopie führt denselben Code aus, ist aber nur für einen Ausschnitt der Kunden, Mandanten, Regionen oder Datensätze zuständig, gewählt über einen Schlüssel wie Stadt oder Kundenkennung. Datenbank-Sharding ist die Z-Achse, angewandt auf die Speicherung (Abschnitt 7.4); eine zellbasierte Architektur wendet sie auf ganze Stacks an, sodass ein Fehler oder ein fehlerhaftes Deployment in einer Zelle nur die Kunden dieser Zelle trifft.

Der Scale Cube

Der Scale Cube: Die X-Achse klont identische Instanzen, die Y-Achse teilt das System nach Funktion und die Z-Achse nach Daten, sodass jede Kopie einen Ausschnitt bedientUrsprung: eineeinzelne App-Instanzalle drei Achsen vereintX: KlonenY: nachFunktionZ: nachDatenX-Achse: KlonenN identische Kopien hintereinem Load Balancer, jede machtalles: 14 Bestell-API-KopienY-Achse: nach FunktionServices pro Fähigkeit:Speisekarte, Bestellung,Disposition, ZahlungZ-Achse: nach DatenGleicher Code; jede Kopiehat einen Teil der Kunden/Daten:eine Zelle/Shard je Stadt
Der Scale Cube von Abbott und Fisher: Jede Achse ist eine andere Art, ein System aufzuteilen, und reale Systeme kombinieren meist alle drei.

Die drei Achsen in der Essenslieferplattform

AchseWas aufgeteilt wirdBeispielLöstKosten und Grenzen
X: KlonenAnfragen, über identische Kopien14 Instanzen der Bestell-API hinter einem Load BalancerAnfragedurchsatz, Ausfälle von InstanzenErfordert Zustandslosigkeit; alle Klone teilen eine Datenbank
Y: FunktionCode und Daten, nach FähigkeitGetrennte Services für Speisekarte, Bestellungen, Disposition und ZahlungenUnterschiedliche Anforderungen an Skalierung und Ausfallverhalten; TeamgrößeNetzwerkaufrufe, verteilte Daten, Betriebsaufwand
Z: DatenKunden oder Datensätze, nach SchlüsselEine Zelle pro Städtegruppe oder Bestellungen nach Stadt geshardetDatenmenge, Schreibvolumen, Blast RadiusRouting-Schicht, partitionsübergreifende Abfragen, Rebalancing, Hot Keys

Die Achsen lassen sich kombinieren. Der Bestellservice der Plattform (eine Aufteilung auf der Y-Achse) läuft als viele Klone (X) in jeder regionalen Zelle (Z). Der praktische Rat von Abbott und Fisher lautet, die günstigste Achse zu wählen, die das tatsächliche Problem löst: X für das Anfragevolumen, Y, wenn Teile des Systems wirklich unterschiedliche Anforderungen an Skalierung, Ausfallverhalten oder Teams haben, und Z, wenn ein Datenbestand oder ein Blast Radius zu groß geworden ist. Wer Z wählt, weil es beeindruckend klingt, holt sich Routing, Rebalancing und partitionsübergreifende Abfragen lange bevor sie nötig sind.

Zustandslosigkeit: die Voraussetzung für das Klonen

Skalierung auf der X-Achse funktioniert nur, wenn jeder Klon jede Anfrage bedienen kann. Ein zustandsloser Service behält zwischen Anfragen nichts in seinem eigenen Arbeitsspeicher oder auf seiner eigenen Festplatte; alles, was eine spätere Anfrage braucht, liegt in einem gemeinsamen Speicher oder reist mit der Anfrage mit. Der Zustand, der sich typischerweise in einer Instanz versteckt, ist leicht aufzuzählen, sobald man hinsieht: im Arbeitsspeicher gehaltene Nutzer-Sessions, hochgeladene Dateien auf der lokalen Festplatte, In-Memory-Caches, die als maßgebliche Quelle behandelt werden, geplante Jobs, die auf genau einem Rechner laufen müssen, prozessinterne Sperren, die eine gemeinsame Ressource schützen, und Zähler für Rate Limits.

Für jeden gibt es ein Standardmittel: Sessions wandern in einen gemeinsamen Speicher oder in ein signiertes Token (Abschnitt 9.2); Dateien wandern in einen Object Storage; Caches werden zu einer Performanceoptimierung, die jederzeit verloren gehen darf, oder wandern in einen gemeinsamen Cache (Abschnitt 7.6); geplante Jobs nutzen einen Scheduler mit Leader Election oder eine Queue; Sperren wandern in die Datenbank oder einen Koordinationsdienst; Rate-Limit-Zähler wandern in einen gemeinsamen Speicher. Die Regel der Twelve-Factor App, dass Prozesse zustandslos sind und nichts teilen (Abschnitt 10.3), ist genau dieser Gedanke. Zustandslosigkeit lässt sich günstig von Anfang an einplanen und nur teuer nachrüsten, weil Zustand an vielen kleinen Stellen in den Code sickert.

Autoscaling: reaktiv, zeitgesteuert und prädiktiv

Autoscaling schließt einen Regelkreis: Last messen, mit einem Zielwert vergleichen, die Zahl der Instanzen ändern. Reaktives Autoscaling tut das fortlaufend anhand einer Metrik. Die übliche Form ist Target Tracking: die durchschnittliche CPU bei 60 % halten oder die laufenden Anfragen pro Instanz bei 30. Der Kubernetes Horizontal Pod Autoscaler berechnet desired = ceil(current replicas × current metric ÷ target): Aus sechs Pods mit durchschnittlich 45 laufenden Anfragen bei einem Zielwert von 30 werden ceil(6 × 45 ÷ 30) = 9. Standardmäßig wertet er alle 15 Sekunden neu aus und ignoriert Abweichungen von weniger als 10 % vom Zielwert.

Zeitgesteuertes Autoscaling ändert die Kapazität zu bekannten Zeiten: das Minimum an jedem Werktag um 11:15 Uhr vor der Mittagsspitze auf 18 Instanzen anheben und um 14:00 Uhr wieder senken. Das ist grob und bei vorhersehbaren Mustern sehr wirksam, und es ist der einzige Weg, Kapazität vor einer sprunghaften Änderung bereitzuhaben, etwa vor einem Ticket-Vorverkauf um Punkt 10:00 Uhr. Prädiktives Autoscaling, das die großen Cloud-Anbieter anbieten, prognostiziert die Last aus der Historie (Tages- und Wochenzyklen) und skaliert ihr voraus, mit reaktiver Skalierung als Sicherheitsnetz für Überraschungen.

Die entscheidende Eigenschaft jedes reaktiven Verfahrens ist die Verzögerung. Metriken werden über Dutzende Sekunden gesammelt und gemittelt; der Autoscaler handelt bei seiner nächsten Auswertung; eine neue virtuelle Maschine braucht eine Minute oder länger zum Booten, ein Container-Image muss geladen werden, ein JVM- oder Python-Service muss starten, seine Caches und Connection Pools aufwärmen und seine Readiness-Prüfung bestehen, bevor der Load Balancer ihm Traffic schickt. In einem Cluster müssen womöglich erst die Knoten selbst von einem zweiten, langsameren Autoscaler hinzugefügt werden. Vom Moment, in dem die Last steigt, bis zu dem Moment, in dem neue Kapazität nützt, vergehen üblicherweise zwei bis zehn Minuten. Reaktives Autoscaling bewältigt daher allmähliche Änderungen; plötzliche Änderungen müssen durch Kapazitätsreserve, durch Queues (Abschnitt 9.3) oder durch im Voraus eingeplante Kapazität aufgefangen werden.

Der Autoscaling-Regelkreis und woher die Verzögerung kommt

Der Autoscaling-Regelkreis: Instanzen melden Last, der Autoscaler vergleicht sie mit einem Zielwert, der Orchestrator startet Instanzen, und neue Instanzen kommen erst in den Load Balancer, wenn sie bereit sindNutzerLoad BalancerInstanzenbedienen LastMetrik-StoreAutoscalerOrchestratoroder Cloud-API1 Last meldenverzögert4 booten, wärmen,Readiness best.2 Metrik mit Zielwertvergleichen, Anzahl wählen3 Instanzenan/ausLastanstieg bis nutzbare Kapazität: oft 2 bis 10 Minuten
Jeder Schritt des Regelkreises fügt Verzögerung hinzu, daher folgt reaktives Autoscaling der Last mit Minuten Abstand; Kapazitätsreserve und eingeplante Kapazität überbrücken die Lücke.

Anhand welcher Metrik skalieren und warum die CPU oft falsch ist

Die CPU-Auslastung ist auf den meisten Plattformen das Standardsignal, weil sie immer verfügbar ist. Das richtige Signal ist sie nur, wenn der Service CPU-gebunden ist: Bildskalierung, Verschlüsselung, aufwendige Serialisierung. Die meisten Geschäftsservices sind es nicht. Eine Bestell-API verbringt den größten Teil jeder Anfrage mit Warten auf die Datenbank, den Zahlungsdienstleister und andere Services; ihre CPU liegt womöglich bei 25 %, während jeder Worker-Thread mit Warten beschäftigt ist und neue Anfragen sich stauen. Skalierung anhand der CPU tut dann nichts, bis es viel zu spät ist. Auch der umgekehrte Fehler kommt vor: Ein Garbage-Collection-Sturm oder ein JIT-Warm-up treibt die CPU hoch, ohne dass die Last steigt, und der Autoscaler fügt Instanzen hinzu, die nicht gebraucht werden.

Besser ist ein Signal, das direkt an die Ressource gekoppelt ist, die ausgeht. Bei Request-Response-Services ist das meist die Nebenläufigkeit: die Zahl der laufenden Anfragen pro Instanz, verglichen mit der Zahl ihrer Worker-Threads oder Verbindungen. Littles Gesetz stellt den Zusammenhang exakt her (laufende Anfragen = Ankunftsrate × Antwortzeit), und das Signal reagiert sowohl auf mehr Traffic als auch auf langsamere Antworten. Bei Queue-Workern ist es der Rückstand pro Worker oder das Alter der ältesten Nachricht, was ausdrückt, wie lange der Abbau der Queue dauern wird. Die Latenz ist als Skalierungssignal verlockend, aber gefährlich: Sie steigt aus Gründen, die mehr Instanzen nicht beheben können, etwa bei einer langsamen Datenbank, und wer einen Service horizontal skaliert, dessen Abhängigkeit schon kämpft, schickt dieser Abhängigkeit noch mehr Last. Nutzen Sie die Latenz für Alerts und skalieren Sie anhand eines Maßes für Arbeit.

Skalierungssignale und wann sie in die Irre führen

SignalPasst fürFührt in die Irre, wenn
CPU-AuslastungCPU-gebundene Arbeit: Bildverarbeitung, Verschlüsselung, aufwendige SerialisierungArbeit wartet auf I/O, Threads oder Verbindungen; Garbage Collection oder Warm-up treibt die CPU hoch
Anfragen pro Sekunde und InstanzServices, deren Anfragen ungefähr gleich viel kostenDer Anfragemix ändert sich, zum Beispiel mehr Suchen und weniger Speisekarten-Abrufe
Laufende Anfragen pro InstanzDie meisten Request-Response-Services; entspricht Thread- und VerbindungslimitsEine nachgelagerte Abhängigkeit wird langsamer; Scale-out belastet sie dann zusätzlich, also das Maximum begrenzen
Queue-Rückstand pro Worker oder Alter der ältesten NachrichtAsynchrone WorkerWorker sind durch ein nachgelagertes Rate Limit begrenzt statt durch ihre Anzahl
LatenzAlerts und SLOs (Abschnitt 9.5)Die Ursache ist eine gemeinsame Abhängigkeit, die mehr Instanzen nicht beheben können
Geschäftsmetrik (Bestellungen pro Minute, aktive Sessions)Last mit einem stabilen, gemessenen Verhältnis zur KapazitätDas Verhältnis verschiebt sich, wenn sich das Produkt ändert; mit Lasttests neu kalibrieren

Cooldowns, Stabilisierung und Flapping

Ein Autoscaler, der auf jedes Schwanken reagiert, gerät ins Flapping: Er fügt Instanzen hinzu, wenn ein Lastschub kommt, entfernt sie eine Minute später, wenn der Schub vorbei ist, und fügt sie dann wieder hinzu, zahlt jedes Mal die Startkosten und stört Caches und Connection Pools. Er kann sich auch selbst im Kreis jagen: Neue Instanzen sind kalt und langsam, daher steigen Latenz oder CPU pro Anfrage nach dem Scale-out kurzzeitig an, was wie mehr Last aussieht.

Die üblichen Gegenmaßnahmen sind asymmetrisch. Skalieren Sie schnell hoch, weil zu wenig Kapazität den Nutzern schadet; skalieren Sie langsam herunter, weil zu viel Kapazität nur Geld kostet. Ein Cooldown oder Stabilisierungsfenster lässt den Autoscaler nach einer Aktion warten oder vor dem Herunterskalieren die höchste Empfehlung eines zurückliegenden Zeitfensters nehmen: Der Kubernetes-Autoscaler nutzt standardmäßig ein Fenster von fünf Minuten (300 Sekunden) für das Herunterskalieren und keines für das Hochskalieren. Ein Toleranzband ignoriert kleine Abweichungen. Mindest- und Höchstzahlen für Instanzen begrenzen den Regelkreis: Das Minimum hält genug Kapazität vor, um den Verlust einer Zone und die ersten Minuten eines Ansturms zu überstehen, und das Maximum schützt die Datenbank, nachgelagerte Services und das Budget vor einem Autoscaler, der ein Problem fälschlich als Last deutet. Auch das Herunterskalieren muss kontrolliert ablaufen: Eine zum Entfernen ausgewählte Instanz darf keine neuen Anfragen mehr erhalten und muss die laufenden abschließen (Connection Draining, Abschnitt 9.2).

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: die Bestell-API für die Mittagsspitze dimensionieren

Die Bestell-API der Essenslieferplattform muss das Mittagsgeschäft an Werktagen bewältigen. In den letzten acht Wochen erreichte die geschäftigste Minute gegen 12:30 Uhr 2050 Anfragen pro Sekunde (rps); das Marketing erwartet in diesem Quartal 15 % Wachstum. Ein Lasttest einer Instanz (4 vCPU, 32 Worker-Threads) ergab eine mittlere Antwortzeit von 120 ms und einen Latenzanstieg ab etwa 260 rps. Der Service läuft in drei Availability Zones und muss den Verlust einer Zone zur Spitzenzeit überstehen.

Prognostizierte Spitze λ2050 × 1,15 ≈ 2360, aufgerundet auf 2400 rps
Littles Gesetz für die FlotteL = λ × W = 2400 × 0,12 s = 288 laufende Anfragen zur Spitzenzeit
Kapazität einer Instanz32 Threads ÷ 0,12 s ≈ 267 rps, was zum Knick passt, den der Lasttest fand (etwa 260 rps)
Zielauslastung70 % von 32 Threads = 22,4 laufende Anfragen pro Instanz (etwa 187 rps), bleiben 30 % Reserve für Lastschübe
Instanzen für die Spitze288 ÷ 22,4 = 12,9, also 13 Instanzen (2400 ÷ 13 ≈ 185 rps je Instanz)
Verlust einer Zone überstehen13 verteilt auf 5/4/4: Der Verlust von 5 lässt 8 Instanzen mit je 288 ÷ 8 = 36 laufenden Anfragen, mehr als 32 Threads: Überlast. Wer beim Verlust einer Zone bis zu 85 % (27,2 laufende Anfragen) zulässt, braucht 288 ÷ 27,2 = 10,6, also 11 in zwei Zonen: 6 pro Zone, insgesamt 18
Hochlauf und Scale-out-VerzögerungDie Nachfrage steigt von 600 rps um 11:30 Uhr auf 2400 um 12:15 Uhr: 40 rps pro Minute. Reaktives Scale-out dauert etwa 5 Minuten (1 Minute zum Erkennen, 4 zum Booten und Aufwärmen), in denen die Nachfrage um 200 rps steigt; die freie Kapazität bei 70 % beträgt etwa 80 rps pro Instanz, reaktive Skalierung allein könnte mit diesem allmählichen Hochlauf also Schritt halten; was sie nicht bieten kann, ist Schutz gegen den Verlust einer Zone
EntscheidungZeitgesteuerte Skalierung hebt das Minimum um 11:15 Uhr auf 18 (6 pro Zone) und senkt es um 14:00 Uhr auf 6 (2 pro Zone); reaktive Skalierung anhand der laufenden Anfragen pro Instanz (Zielwert 22) bleibt als Sicherheitsnetz, mit einem Maximum von 30
Datenbankprüfung18 Instanzen × ein Pool von 10 Verbindungen = 180; beim Maximum von 30 Instanzen 300. Die Datenbank erlaubt 200, daher fügt das Team einen Connection Pooler hinzu, bevor es das Maximum anhebt

Littles Gesetz macht aus einer prognostizierten Rate und einer gemessenen Antwortzeit eine Nebenläufigkeitszahl, die sich direkt mit Thread- und Verbindungslimits vergleichen lässt.

Die Anforderung, den Verlust einer Zone zu überstehen, nicht die durchschnittliche Last, bestimmt die Flottengröße zur Spitzenzeit: 18 Instanzen statt 13. Reaktives Autoscaling kann diesen Schutz nicht bieten, weil das Ersetzen einer verlorenen Zone Minuten dauert.

Das Scale-out der Anwendungsschicht hat die Last auf die Verbindungen der gemeinsamen Datenbank vervielfacht, den nächsten Engpass. Jede Dimensionierung sollte mit einer Prüfung der darunterliegenden Schicht enden.

Die Datenschicht skalieren

Zustandslose Anwendungsschichten lassen sich leicht skalieren; genau deshalb macht man sie zustandslos. Der Druck landet dann auf den Datenbanken, Caches und Brokern, die sie gemeinsam nutzen; deshalb ist die Datenschicht meist die eigentliche Grenze eines Systems. Die Werkzeuge wurden in Kapitel 7 behandelt und sind hier in der Reihenfolge zusammengefasst, in der Fachleute meist zu ihnen greifen. Erstens: die Arbeit verringern, mit Indizes, besseren Abfragen, weniger Roundtrips pro Anfrage. Zweitens: Lesezugriffe mit Caching (Abschnitt 7.6) und Lesereplikaten auffangen und Replikationsverzögerung hinnehmen, wo der Anwendungsfall es erlaubt. Drittens: Verbindungen schützen. Jede Anwendungsinstanz hält einen Pool, die Gesamtzahl wächst also mit der Flotte, und ein Connection Pooler wie PgBouncer lässt Tausende Client-Verbindungen sich einige Hundert Datenbankverbindungen teilen (der Standardwert von max_connections in PostgreSQL ist nur 100). Viertens: den Primary vertikal skalieren, was oft günstiger ist als jeder Umbau. Erst dann Schreibzugriffe entlang der Z-Achse nach einem Schlüssel wie Stadt oder Kunde partitionieren (Abschnitt 7.4) oder auf eine Distributed-SQL-Datenbank wechseln.

Zwei Warnungen gelten. Erstens kann Autoscaling der Anwendungsschicht der Datenschicht schaden: Jede neue Instanz öffnet Verbindungen und wärmt ihren Cache aus der Datenbank auf, sodass ein großes Scale-out-Ereignis die Datenbank selbst überlasten kann. Zweitens sind manche Grenzen logisch, nicht physisch: Eine einzige Hot Row (der Bestandszähler für die letzten 500 Tickets eines Konzerts oder eine globale Sequenznummer) serialisiert Schreibzugriffe, egal wie groß der Server ist. Solche Grenzen beseitigt man durch Design, etwa indem man den Bestand auf mehrere Zähler aufteilt oder Plätze in Blöcken reserviert, nicht durch Hardware.

Entscheidungshilfe

Entscheidungshilfe: wie Sie einer Komponente Kapazität hinzufügen

Gehen Sie die Fragen der Reihe nach für die Komponente durch, die das System heute begrenzt.

  1. Wurde der Engpass gemessen (Auslastung, Sättigung und Warteschlangen bei CPU, Arbeitsspeicher, Verbindungen, Sperren und nachgelagerten Aufrufen) statt geschätzt?
    JaMachen Sie mit der Komponente weiter, die zuerst gesättigt ist.
    NeinZuerst einen Lasttest durchführen und messen. Wer die falsche Schicht skaliert, erhöht die Kosten ohne Kapazitätsgewinn und kann den echten Engpass noch stärker belasten.
  2. Lässt sich die Arbeit selbst verbilligen (ein Index, ein Cache, weniger Roundtrips, kleinere Payloads, weniger Logging)?
    JaTun Sie das zuerst. Es senkt die Kosten auf jeder Größenordnung und macht Skalierung oft ganz überflüssig.
    NeinWeiter: Es muss Kapazität hinzukommen.
  3. Ist die Komponente zustandslos, oder lässt sich ihr Zustand (Sessions, Dateien, In-Memory-Sperren, Zeitpläne) in gemeinsame Speicher verlagern?
    JaSkalieren Sie auf der X-Achse hinter einem Load Balancer, mit Autoscaling anhand eines Lastsignals und genug Kapazitätsreserve für die Scale-out-Verzögerung.
    NeinSkalieren Sie vorerst vertikal und planen Sie die Arbeit, den Zustand auszulagern; eine zustandsbehaftete Einzelinstanz ist zudem ein Single Point of Failure.
  4. Liegt die Grenze in der Datenschicht: Schreibrate, Speichergröße oder Verbindungszahl einer Datenbank?
    JaNutzen Sie Caching und Lesereplikate für Lesezugriffe, einen Connection Pooler für Verbindungen und Partitionierung auf der Z-Achse (Shards oder Zellen) für Schreibzugriffe und Speicherung.
    NeinWeiter.
  5. Haben Teile der Komponente sehr unterschiedliche Anforderungen an Last, Skalierung oder Ausfallverhalten?
    JaErwägen Sie eine Aufteilung auf der Y-Achse, sodass jeder Teil getrennt skaliert und ausfällt, und nehmen Sie die in Kapitel 6 beschriebenen Kosten der Verteilung in Kauf.
    NeinBleiben Sie beim Klonen auf der X-Achse: Es ist die am günstigsten zu betreibende Achse.

Prüfen Sie Ihr Verständnis

Die CPU einer Bestell-API liegt bei 30 %, aber die p99-Latenz hat sich verdreifacht, und alle Worker-Threads sind belegt. Warum hat der CPU-basierte Autoscaler nicht reagiert, und welches Signal hätte reagiert?

Der Service wartet auf I/O, daher sind die Threads erschöpft, während die CPU untätig ist. Die laufenden Anfragen pro Instanz, verglichen mit der Thread-Zahl, wären mit der Last gestiegen.

Welche Achse des Scale Cube hilft bei einer Datenbank, die der Schreibkapazität eines Servers entwachsen ist?

Die Z-Achse: die Daten nach einem Schlüssel partitionieren, sodass jeder Shard oder jede Zelle einen Teil der Schreibzugriffe übernimmt.

Warum skalieren Autoscaler meist schnell hoch, aber langsam herunter?

Zu wenig Kapazität schadet den Nutzern sofort, zu viel kostet nur Geld, und zu eifriges Herunterskalieren verursacht Flapping, wenn die Last zurückkehrt.

Warum kann reaktives Autoscaling einen Ticket-Vorverkauf, der um Punkt 10:00 Uhr beginnt, nicht absichern?

Die Nachfrage springt innerhalb von Sekunden, aber das Erkennen des Anstiegs, das Starten der Instanzen und ihr Aufwärmen dauern Minuten; die Kapazität muss also vor Verkaufsbeginn eingeplant werden.

Weiter · 9.2Load BalancingLoad Balancer auf Layer 4 und Layer 7, die wichtigsten Algorithmen einschließlich der Power of Two Random Choices, Liveness- und Readiness-Checks, Connection Draining, die Probleme mit Sticky Sessions, ausgelagerter Session-Zustand und globales Load Balancing.Weiter →

Quellen und weiterführende Literatur

Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.

Fragen: „Unsere Bestell-API skaliert automatisch anhand der CPU, bricht aber mittags trotzdem zusammen. Anhand welcher Metrik sollten wir stattdessen skalieren, und wie viele Instanzen brauchen wir wirklich?“