Zurück zum Blog
Architektur
Experten-LevelFürBackend EngineersPlatform EngineersSRE
13 min

Skalierbarkeit: Der Engpass ist fast nie die CPU

Warum die CPU bei 30 Prozent liegt, während Ihr Dienst in Timeouts läuft: das Auslastungsknie, Connection Pools, ephemere Ports, Coordinated Omission im Lasttest und die Befehle, die den echten Engpass finden.

skalierbarkeitload-balancingverteilte-systemeconnection-poolbackpressurewarteschlangentheorie
Inhalt

Ihr Dashboard zeigt 28 Prozent CPU. Ihre p99-Latenz liegt bei vier Sekunden. Jemand schlägt vor, mehr Pods zu starten.

Das ist die häufigste Fehlabbiegung bei Skalierungsarbeit, und sie folgt aus einer nachvollziehbaren Annahme: dass ein langsames System ein ausgelastetes System sein müsse. Meistens ist es das nicht. Es ist ein System, in dem Anfragen auf etwas warten, das keine CPU ist — und zusätzliche Instanzen machen es oft schlimmer.

Hier steht, was Sie tatsächlich begrenzt, und die Rechnung, mit der Sie es finden.

Skalierbarkeits-Engpässe: warum die CPU untätig ist, während Anfragen anderswo warten.

Eine Formel, die fast alles dimensioniert

Littles Gesetz ist das Nützlichste in der Kapazitätsplanung und passt in eine Zeile:

Nebenläufigkeit = Ankunftsrate x Latenz
L = λ × W

Treffen 500 Anfragen pro Sekunde ein und verbringt jede 40 ms in der Datenbank, brauchen Sie zu jedem Zeitpunkt:

500 Anf./s × 0,04 s = 20 gleichzeitige Datenbankverbindungen

Zwanzig. Nicht zweihundert.

Jetzt rückwärts — dort verdient die Formel ihr Geld. Sie haben einen Pool mit 20 Verbindungen, jede Abfrage dauert 50 ms:

20 / 0,05 s = 400 Anfragen pro Sekunde

Das ist Ihre Decke. Kein weiches Limit, ein hartes. Sie können vierzig Pods betreiben; der Pool lässt weiterhin 400 Anfragen pro Sekunde durch, alles andere wartet. Die CPU bleibt niedrig, weil die CPU nichts tut. Sie wartet.

Jede Warteschlange in Ihrem System folgt dem. Thread-Pools, Connection-Pools, Worker-Zahlen, In-Flight-Limits. Kennen Sie zwei der drei Größen, kennen Sie die dritte.

Warum 80 Prozent Auslastung nicht zu 80 Prozent in Ordnung sind

Hier steht der Teil, der erklärt, warum Kapazitätsplanung unintuitiv wirkt.

Bei einer einfachen Warteschlange hängt die Antwortzeit so von der Auslastung ab:

Antwortzeit = Bedienzeit / (1 - Auslastung)
R = S / (1 - ρ)

Der Nenner ist die ganze Geschichte. Nähert sich die Auslastung der Eins, steigt die Antwortzeit nicht sanft an — sie geht gegen unendlich.

Warum 80 Prozent Auslastung nicht zu 80 Prozent in Ordnung sind
Auslastung Antwortzeit gegenüber Leerlauf
50%
80%
90% 10×
95% 20×
99% 100×

Der Schritt von 50 auf 80 Prozent Auslastung kostet Sie nicht 30 Prozent Ihres Latenzbudgets. Er kostet 150 Prozent — die Antwortzeit springt vom Doppelten auf das Fünffache der Bedienzeit.

Deshalb halten erfahrene Teams scheinbar verschwenderische Reserven vor. Und deshalb fällt ein System, das gestern bei 70 Prozent stabil lief, heute bei 85 Prozent nach einem moderaten Lastanstieg um. Geändert hat sich nichts außer Ihrer Position auf einer Kurve, die senkrecht wird.

Die ehrliche Einschränkung: Diese Formel setzt einen einzelnen Server mit Poisson-Ankünften und exponentialverteilten Bedienzeiten voraus — eine M/M/1-Warteschlange. Reale Systeme haben mehrere Server und stößeren Verkehr, Ihre Konstanten werden also abweichen. Die Form aber ist universell: Wartezeit ist hyperbolisch in der Auslastung, nicht linear. Jede Ihrer Warteschlangen hat ein Knie, und es liegt weiter links, als die Intuition nahelegt.

Praktische Folge: hören Sie auf, Autoscaling-Ziele auf 80 Prozent CPU zu setzen. Dort sind Sie bereits tief in der Kurve, und die neue Instanz braucht Zeit, bis sie nützt.

Die Decken, in der Reihenfolge ihres Auftretens

1. Datenbankverbindungen

Das häufigste echte Limit — und es hat eine Zahl, die Sie nachschlagen können.

PostgreSQL erlaubt standardmäßig max_connections = 100, davon sind drei für Superuser reserviert. Für Ihre Anwendung bleiben also rund 97.

Zählen Sie nun, was Sie anfordern. Zwölf Pods mit je zehn Verbindungen sind 120 gegen ein Limit von 97. Der zwölfte Pod bekommt Verbindungsfehler, und der Fehler sieht aus wie ein Datenbankproblem statt wie ein Rechenfehler.

Zwei Dinge verschärfen das:

  • PostgreSQL forkt einen Prozess pro Verbindung, Verbindungen kosten also echten Speicher. Die Dokumentation sagt ausdrücklich, dass ein höheres max_connections auch die Shared-Memory-Zuteilung erhöht. Genau dafür gibt es PgBouncer.
  • Ein größerer Pool ist nicht mehr Durchsatz. Jenseits des Punktes, an dem die Datenbank Abfragen wirklich parallel ausführen kann, bringen zusätzliche Verbindungen Lock-Contention und Context-Switches. Der Durchsatz flacht ab und fällt dann.

MySQL nutzt einen Thread pro Verbindung und ist hier günstiger — einer der strukturellen Unterschiede zwischen Postgres und MySQL, den man vor der Wahl kennen sollte.

2. Dateideskriptoren

Jeder Socket, jede offene Datei, jede Verbindung ist ein Dateideskriptor. Das Limit pro Prozess liegt beim Soft-Limit oft bei 1024 — klingt großzügig, bis man merkt, dass es eingehende Verbindungen, ausgehende Verbindungen, Logdateien und Bibliotheks-Interna gleichzeitig abdeckt.

Terminal window
ulimit -n # Soft-Limit dieser Shell
cat /proc/<pid>/limits # was der laufende Prozess wirklich hat
ls /proc/<pid>/fd | wc -l # wie viele er gerade nutzt

Der Fehlermodus ist einprägsam: EMFILE: too many open files, meist genau unter der Last, bei der man am wenigsten Lust auf einen Stacktrace hat.

3. Ephemere Ports und TIME_WAIT

Dieser Punkt überrascht, weil nichts in der Anwendung ihn erwähnt.

Öffnet Ihr Dienst eine ausgehende Verbindung, wählt der Kernel einen Quellport aus dem ephemeren Bereich. Unter Linux:

Terminal window
cat /proc/sys/net/ipv4/ip_local_port_range
# typischerweise: 32768 60999

Das sind rund 28.000 Ports je eindeutiger Kombination aus Zieladresse und Zielport.

Anschließend hält die schließende Seite den Socket 60 Sekunden im TIME_WAIT — und unter Linux ist diese Dauer in den Kernel einkompiliert, kein sysctl, das Sie herunterdrehen können.

Rechnen Sie nach. 500 kurzlebige Verbindungen pro Sekunde zum selben nachgelagerten Dienst:

500 Verb./s × 60 s = 30.000 Sockets im TIME_WAIT

Der Bereich ist aufgebraucht. Neue Verbindungen scheitern, und der Fehler ist ein Connect-Timeout, der aussieht, als sei der andere Dienst ausgefallen.

Die Lösung ist nicht, den Bereich zu vergrößern. Sie ist, die Verbindungen gar nicht erst zu öffnen: HTTP-Keep-Alive, Connection-Pooling, HTTP/2-Multiplexing. Wiederverwendung schlägt Bereitstellung.

4. Connection Tracking

Läuft Ihr Verkehr über ein NAT-Gateway, eine Firewall oder einen Kubernetes-Node mit iptables, führt der Kernel für jeden Flow einen conntrack-Eintrag. Diese Tabelle ist endlich, und wenn sie voll ist, werden Pakete stillschweigend verworfen — kein Fehler, keine Logzeile in Ihrer Anwendung, nur Latenz und Retries.

Terminal window
sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_count

Der Abstand zwischen diesen beiden Zahlen ist eine Alarmschwelle wert — und fast niemand exportiert sie.

5. Threads und Event Loops

Ein Server mit einem Thread pro Anfrage ist ein Pool unter anderem Namen, Littles Gesetz gilt unverändert. Ein Event-Loop-Server hat dieses Limit nicht — was seine eigene Gefahr ist, denn er nimmt bereitwillig zehntausend gleichzeitige Anfragen an und bedient alle langsam, statt einige schnell abzulehnen.

Ein einziger blockierender Aufruf im Event Loop legt jede Anfrage auf diesem Loop lahm. Der CPU-Graph bleibt dabei ruhig.

Horizontale Skalierung und der gemeinsame Nenner

Zusätzliche Instanzen vervielfachen die Kapazität für den unabhängigen Teil einer Anfrage und ändern nichts am geteilten Teil.

Amdahls Gesetz deckelt den Gewinn aus Parallelisierung bei allem, was einen seriellen Anteil hat. Das Universal Scalability Law ergänzt den Teil, den die meisten übersehen — und es lohnt sich, ihn auszuschreiben, denn der zweite Term ist der gefährliche:

C(N) = N / (1 + σ(N-1) + κN(N-1))
N Anzahl der Worker
σ Contention — serieller Anteil, Warten auf geteilte Ressourcen
κ Coherency — Kosten, die Worker untereinander konsistent zu halten

Mit κ = 0 wird daraus Amdahls Gesetz: Der Durchsatz flacht ab, fällt aber nie. Mit κ > 0 passiert etwas Schlimmeres — es gibt ein Maximum, und dahinter sinkt der Durchsatz:

Maximum bei N* = sqrt((1 - σ) / κ)

Der Coherency-Term erklärt, warum zusätzliche Kapazität ein System verlangsamen kann. Er umfasst Cache-Line-Bouncing zwischen Kernen, Lock-Übergaben, Cluster-Gossip, verteilten Konsens und regionsübergreifende Replikation — alles, dessen Kosten mit dem Quadrat der Teilnehmerzahl wachsen, weil jeder sich mit jedem abstimmen muss.

Konkret: Wenn jeder neue Pod weitere Verbindungen zur selben Primärdatenbank öffnet, skalieren Sie nicht. Sie verwandeln ein Verbindungslimit in eine Warteschlange und danach in einen Timeout. Wenn jeder neue Knoten mit jedem anderen gossipen muss, haben Sie sich einen κ-Term gekauft.

Fragen Sie vor jeder zusätzlichen Instanz:

  1. Was berührt eine Anfrage, das geteilt ist?
  2. Wie hoch ist das aktuelle Nebenläufigkeitslimit dieser geteilten Sache?
  3. Erhöht eine weitere Instanz dieses Limit oder konkurriert sie darum?

Lautet die Antwort auf 3 „konkurriert”, macht Skalieren es schlimmer.

Load Balancing: was die Diagramme weglassen

Der Kasten mit der Aufschrift „LB” verbirgt drei Entscheidungen, die darüber bestimmen, ob Skalierung funktioniert.

Health Checks, die lügen. Eine Prüfung, die 200 zurückgibt, sobald der Prozess lebt, leitet Verkehr weiter in eine Instanz, deren Datenbank-Pool erschöpft und deren Warteschlange dreißig Sekunden tief ist. Eine nützliche Prüfung verifiziert die Abhängigkeiten, die eine Anfrage wirklich braucht — und trennt Liveness (starte mich neu) von Readiness (schick mir keinen Verkehr). Beides zu vermischen erzeugt Neustartschleifen unter Last, also genau dann, wenn Sie Stabilität brauchen.

L4 gegen L7 unter HTTP/2. L4 verteilt Verbindungen. Bei HTTP/2 oder gRPC trägt eine langlebige Verbindung viele gemultiplexte Anfragen — Verteilung auf Verbindungsebene kann einem Backend also den Großteil der Arbeit überlassen, während die übrigen nichts tun. Der Graph zeigt gleichmäßige Verbindungszahlen und völlig ungleiche CPU. Für HTTP/2 und gRPC wollen Sie Verteilung auf Anfrageebene.

Sticky Sessions. Einen Nutzer an eine Instanz zu binden hebelt die Verteilung absichtlich aus. Außerdem verliert ein Neustart den Zustand dieser Nutzer, und ein heißer Nutzer wird zu einer heißen Instanz, die Sie nicht verteilen können.

Dazu die Retry-Falle: Ist ein System bereits gesättigt, vervielfachen automatische Wiederholungen genau die Last, die es gesättigt hat. Retries brauchen Rate Limiting und ein Budget sowie Jitter — sonst wird aus einem Brown-out ein Ausfall.

Warteschlangen puffern Spitzen, nicht Überlast

Eine Warteschlange vor einem langsamen Konsumenten verschafft Ihnen Zeit über eine Spitze hinweg. Gegen eine dauerhaft über der Bedienrate liegende Ankunftsrate hilft sie nicht — sie verschiebt nur, wo das Scheitern sichtbar wird.

Übersteigen die Ankünfte die Bedienkapazität auf Dauer, wächst die Warteschlange unbegrenzt und die Latenz mit ihr. Aufrufer laufen in Timeouts. Sie wiederholen. Die Ankünfte steigen. Das ist eine Rückkopplung mit falschem Vorzeichen.

Backpressure ist die Antwort, und sie bedeutet, Nein sagen zu können:

  • Begrenzte Warteschlangen. Eine unbegrenzte Warteschlange ist ein Out-of-Memory mit Zwischenschritten.
  • Schnell ablehnen. Ein 429 mit Retry-After nach 5 ms ist deutlich freundlicher als ein Timeout nach 30 Sekunden — für den Aufrufer und für Sie.
  • Nach Wert verwerfen. Unter Druck kippen Sie den Batch-Export, bevor Sie den Checkout kippen.
  • In-Flight-Arbeit deckeln. Nebenläufigkeitslimits je Abhängigkeit verhindern, dass ein langsamer Downstream alle Worker verbraucht.

Der Reflex sagt, Anfragen abzulehnen sei Scheitern. Die Alternative ist, Anfragen anzunehmen, die Sie nicht bedienen können — das ist ebenfalls Scheitern, nur später und für alle gleichzeitig.

Was Multi-Region leistet und was nicht

Multi-Region kauft Ihnen Verfügbarkeit beim Ausfall einer Region und niedrigere Latenz für Nutzer in der Nähe einer Region.

Es kauft Ihnen keinen Durchsatz für irgendetwas, das konsistent bleiben muss. Ein global konsistenter Schreibvorgang kostet mindestens einen regionsübergreifenden Roundtrip, und die Lichtgeschwindigkeit ist kein Konfigurationsparameter — grob 5 ms je 1.000 km in Glasfaser, bevor irgendein Gerät das Paket anfasst. Frankfurt nach Virginia hat eine physikalische Untergrenze von mehreren Dutzend Millisekunden pro Roundtrip.

Kreuzt Ihr Schreibpfad Regionen, haben Sie Schreibvorgänge nicht skaliert. Sie haben Latenz und einen Partitionsmodus zum Testen hinzugefügt.

Den echten Engpass finden

Brendan Greggs USE-Methode ist der schnellste Weg. Messen Sie für jede Ressource drei Dinge:

Den echten Engpass finden
Was es bedeutet Warum es zählt
Utilisation Anteil der Zeit, in der die Ressource beschäftigt ist Für sich genommen irreführend
Saturation Wie viel Arbeit wartet Meist das eigentliche Signal
Errors Ablehnungen, Timeouts, Verwürfe Sagt, was bereits kaputt ist

Die meisten Teams exportieren Auslastung und hören dort auf. Aber ein Pool mit 100 Prozent Auslastung und leerer Warteschlange ist kerngesund, während ein Pool mit 60 Prozent und einer dreißig Einträge tiefen Warteschlange Ihr Ausfall ist. Die Sättigung benennt den Engpass — und genau sie zeichnet fast niemand auf.

Praktisch: Exportieren Sie für jeden Pool im System — Datenbankverbindungen, HTTP-Clients, Thread-Pools, Worker-Queues — Warteschlangentiefe und Wartezeit, nicht nur die Zahl der belegten Slots.

Ihr Lasttest lügt Sie an

Dieser Punkt verdient einen eigenen Abschnitt, denn er entwertet genau die Messung, der die meisten Teams am meisten vertrauen.

Die meisten Lastgeneratoren arbeiten in einer geschlossenen Schleife: Anfrage senden, auf die Antwort warten, nächste senden. Klingt vernünftig. Liefert Latenzzahlen, die still und systematisch falsch sind.

Stellen Sie sich einen Generator mit 100 virtuellen Nutzern vor, der einen Dienst belastet, welcher zwei Sekunden hängt. Während dieses Hängers ist jeder Nutzer blockiert. Sie senden nichts. Löst sich der Stau, machen alle weiter und verbuchen eine normal aussehende Latenz.

Das Ergebnis: Die schlimmsten zwei Sekunden Ihres Tests haben fast keine Messpunkte beigesteuert. Ihr p99 wird über eine Menge berechnet, die genau den Zeitraum ausschließt, der Sie interessiert.

Das ist Coordinated Omission, benannt von Gil Tene. Der Lastgenerator hat sich unfreiwillig mit dem Testsystem abgestimmt und misst es nur dann, wenn es gesund ist.

Echte Nutzer verhalten sich nicht so. Sie kommen nach eigenem Fahrplan. Wer während Ihres Zwei-Sekunden-Hängers klickt, wartet die vollen zwei Sekunden — plus die Zeit, die die Warteschlange davor zum Abbau braucht.

Woran Sie es erkennen: Liegt Ihr p99 unter Last verdächtig nah am Median, während Nutzer von Einfrierern berichten, die Sie nicht reproduzieren können, haben Sie es mit ziemlicher Sicherheit.

Die Gegenmittel:

  • Gegen den geplanten Fahrplan messen, nicht gegen den tatsächlichen. Wollten Sie bei t=0 senden und kamen blockiert erst bei t=1,8 s dazu, beginnt die Latenz dieser Anfrage bei t=0.
  • Ein korrigiertes Werkzeug nutzen. wrk2 wurde genau dafür geschrieben. HdrHistogram bietet recordValueWithExpectedInterval, um die fehlenden Messpunkte nachzutragen.
  • Offenes Modell bevorzugen. Last mit fester Ankunftsrate erzeugen, unabhängig davon, ob vorherige Antworten zurückkamen — genau so verhält sich echter Verkehr.

Bis Sie das beheben, ist jede unter Sättigung erhobene Latenzzahl eine Untertreibung — und je schlimmer der Hänger, desto größer die Lüge.

In der Praxis finden

Genug Theorie. Hier die tatsächliche Reihenfolge, wenn die Latenz oben ist und die CPU nicht.

Bei der Datenbank anfangen, denn dort liegt es meistens.

-- Worauf wartet gerade wer?
SELECT state, wait_event_type, wait_event, count(*)
FROM pg_stat_activity
GROUP BY 1, 2, 3
ORDER BY 4 DESC;

Viele Zeilen mit Client / ClientRead bedeuten, dass die Datenbank untätig ist und auf Sie wartet — der Engpass liegt woanders. Viele Lock oder LWLock bedeuten Contention innerhalb der Datenbank. Viele idle in transaction bedeuten, dass Ihre Anwendung Verbindungen offen hält, ohne sie zu nutzen — das entspricht einem kleineren Pool, als Sie denken.

-- Wie viel Verbindungsreserve ist übrig?
SELECT count(*) AS used,
current_setting('max_connections')::int AS max_conn
FROM pg_stat_activity;

Dann die Sockets.

Terminal window
ss -s # Summen, inklusive TIME_WAIT
ss -tan state time-wait | wc -l # wie tief ist das TIME_WAIT-Loch
ss -tan state established | wc -l # was wirklich offen ist

Liegt TIME_WAIT im Zehntausenderbereich, verwenden Sie Verbindungen nicht wieder. Das ist ein Client-Konfigurationsproblem, kein Kapazitätsproblem.

Dann die Kernel-Tabellen.

Terminal window
cat /proc/sys/net/ipv4/ip_local_port_range
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

Dann die Prozesslimits.

Terminal window
cat /proc/<pid>/limits | grep 'open files'
ls /proc/<pid>/fd | wc -l

Die ganze Sequenz dauert etwa drei Minuten und benennt die bindende Randbedingung deutlich häufiger als ein weiteres Dashboard.

Die Checkliste

Die Checkliste
Symptom Wahrscheinliche Ursache Was zuerst prüfen
Niedrige CPU, hohe Latenz Warten an einem Pool Wartezeit im Pool, Warteschlangentiefe
Fehler bei N Pods, nicht bei N-1 max_connections überschritten Pods × Poolgröße gegen DB-Limit
too many open files FD-Limit ulimit -n, /proc/<pid>/limits
Connect-Timeouts zu einem Downstream Ephemere Ports / TIME_WAIT Wiederverwendung, Keep-Alive
Stiller Paketverlust unter Last conntrack-Tabelle voll nf_conntrack_count gegen max
Ungleiche Backend-CPU, gleiche Verbindungen L4 verteilt HTTP/2 Auf Anfrageebene verteilen
Latenz wächst und erholt sich nie Keine Backpressure Warteschlange begrenzen, Last abwerfen
Mehr Pods machen es langsamer Contention um geteilte Ressourcen Worum konkurrieren alle Pods?

Das Fazit

CPU ist am leichtesten zu graphen — deshalb schaut man zuerst dorthin, und deshalb ist es meist das Falsche.

Anfragen warten auf Verbindungen, Deskriptoren, Ports, Locks und Threads, und jede dieser Warteschlangen folgt derselben Rechnung. Nebenläufigkeit gleich Ankunftsrate mal Latenz. Kennen Sie zwei Größen, kennen Sie die dritte — und können Ihre Decke ausrechnen, statt sie während eines Incidents zu entdecken.

Beantworten Sie vor jeder zusätzlichen Kapazität eine Frage: Worauf wartet die Anfrage? Können Sie es nicht benennen, behebt eine weitere Instanz nichts — sie verschafft derselben Warteschlange nur mehr Kundschaft.

Häufig gestellte Fragen

Warum ist die CPU niedrig, der Dienst aber langsam?

Weil CPU nur eine von mehreren Ressourcen ist, die eine Anfrage braucht, und selten die knappste. Typischerweise wartet eine Anfrage auf eine Datenbankverbindung aus dem Pool, einen Dateideskriptor, einen ephemeren Port, einen Thread oder ein Lock. Ist eine davon erschöpft, stauen sich Anfragen, während die CPU untätig auf I/O wartet. Niedrige Auslastung neben hoher Latenz ist die Signatur einer gesättigten Warteschlange an anderer Stelle — deshalb verlangt die USE-Methode, Sättigung getrennt von Auslastung zu messen.

Wie dimensioniere ich einen Datenbank-Connection-Pool?

Mit Littles Gesetz: Nebenläufigkeit gleich Ankunftsrate mal Bedienzeit. Bei 500 Anfragen pro Sekunde und 40 ms in der Datenbank brauchen Sie 500 mal 0,04, also 20 gleichzeitige Verbindungen. Prüfen Sie anschließend die Summe über alle Instanzen: zwanzig Pods mit je 20 Verbindungen verlangen 400, und PostgreSQL erlaubt standardmäßig 100. Ein größerer Pool ist nicht die Lösung — jenseits des Punktes, an dem die Datenbank Abfragen wirklich parallel ausführen kann, bringen zusätzliche Verbindungen Contention statt Durchsatz.

Was sind ephemere Ports und warum begrenzen sie den Durchsatz?

Öffnet Ihr Dienst eine ausgehende Verbindung, vergibt der Kernel einen Quellport aus dem ephemeren Bereich, unter Linux typischerweise 32768 bis 60999 — also rund 28.000 Ports je Kombination aus Zieladresse und Zielport. Nach dem Schließen bleibt der Socket unter Linux 60 Sekunden im TIME_WAIT, und diese Dauer ist einkompiliert und nicht per sysctl änderbar. Wer mehr als ein paar hundert kurzlebige Verbindungen pro Sekunde zum selben Ziel öffnet, erschöpft den Bereich. Die Lösung ist Wiederverwendung über Keep-Alive und Pooling, nicht das Anheben von Limits.

Hilft horizontale Skalierung immer?

Nur bei wirklich unabhängiger Arbeit. Zusätzliche Instanzen vervielfachen die Kapazität für den zustandslosen Teil einer Anfrage und lassen alles Geteilte unverändert — die geteilte Komponente wird damit zur Decke. Schlimmer noch: Das Universal Scalability Law zeigt, dass der Durchsatz ab einem Punkt sogar sinken kann, weil die Koordination der zusätzlichen Worker mehr kostet als sie beiträgt. Wenn jeder neue Pod weitere Verbindungen zur selben Primärdatenbank öffnet, können Sie sich durchaus in ein langsameres System hineinskalieren.

Was ist Backpressure und warum ist sie wichtig?

Backpressure bedeutet, eingehende Arbeit abzulehnen oder zu verlangsamen, statt sie in eine Warteschlange aufzunehmen. Ohne sie nimmt ein überlastetes System Anfragen an, die es nie rechtzeitig bedienen wird — die Latenz wächst, bis Aufrufer in Timeouts laufen, es erneut versuchen und die Last weiter erhöhen. Eine begrenzte Warteschlange, die schnell mit 429 und Retry-After ablehnt, hält das System berechenbar. Unbegrenzte Warteschlangen puffern keine Überlast, sie verbergen sie, bis der Speicher ausgeht.

Warum explodiert die Latenz nahe 100 Prozent Auslastung?

Weil Wartezeit hyperbolisch in der Auslastung ist, nicht linear. Bei einer einfachen Warteschlange entspricht die Antwortzeit der Bedienzeit geteilt durch eins minus Auslastung, der Nenner schrumpft also gegen null, je näher Sie der Volllast kommen. Bei 50 Prozent sehen Sie etwa die doppelte Bedienzeit, bei 80 Prozent das Fünffache, bei 90 das Zehnfache und bei 95 das Zwanzigfache. Deshalb kostet der Schritt von 50 auf 80 Prozent weit mehr Latenz, als die zusätzlichen 30 Prozent Last vermuten lassen, und deshalb liegen Autoscaling-Ziele bei 80 Prozent CPU bereits hinter dem nützlichen Teil der Kurve. Die genauen Faktoren setzen eine M/M/1-Warteschlange voraus, die hyperbolische Form gilt jedoch für jede Warteschlange.

Was ist Coordinated Omission im Lasttest?

Ein systematischer Messfehler in Lastgeneratoren mit geschlossener Schleife, benannt von Gil Tene. Ein solcher Generator sendet eine Anfrage, wartet auf die Antwort und sendet dann die nächste. Hängt das System, sind alle virtuellen Nutzer blockiert, es werden kaum Anfragen gesendet und kaum Messpunkte im schlimmsten Zeitraum erfasst. Der resultierende p99 wird über Messungen berechnet, die genau den relevanten Hänger ausschließen. Echte Nutzer kommen nach eigenem Fahrplan und erleben die volle Verzögerung. Beheben lässt es sich, indem man gegen den geplanten Sendefahrplan misst, ein korrigiertes Werkzeug wie wrk2 oder HdrHistograms recordValueWithExpectedInterval nutzt, oder Last mit fester Ankunftsrate erzeugt.

Kann mehr Server ein System langsamer machen?

Ja, und das Universal Scalability Law beschreibt genau wann. Seine Formel enthält einen Contention-Term für das Warten auf geteilte Ressourcen und einen Coherency-Term für die Kosten, Worker untereinander konsistent zu halten. Die Coherency-Kosten wachsen mit dem Quadrat der Teilnehmerzahl, weil jeder sich mit jedem abstimmen muss — jenseits eines Maximums bei der Wurzel aus eins minus Contention geteilt durch Coherency sinkt der Durchsatz also tatsächlich. In der Praxis sind das Cache-Line-Bouncing, Lock-Übergaben, Cluster-Gossip, Konsensrunden und regionsübergreifende Replikation.

Wie finde ich den Engpass schnell?

Von der Datenbank nach außen arbeiten. Fragen Sie pg_stat_activity gruppiert nach wait_event_type ab, um zu sehen, worauf Sessions warten: viele Zeilen mit ClientRead bedeuten, dass die Datenbank untätig auf Ihre Anwendung wartet, Lock- oder LWLock-Wartezeiten bedeuten Contention in der Datenbank, und idle in transaction bedeutet, dass Ihr Code Verbindungen hält, ohne sie zu nutzen. Prüfen Sie danach Sockets mit ss -s und zählen Sie TIME_WAIT-Einträge, kontrollieren Sie den ephemeren Portbereich und die conntrack-Zähler und zuletzt das Dateideskriptor-Limit gegen die aktuelle Nutzung.

Was ist die USE-Methode?

Eine Checkliste von Brendan Gregg zum schnellen Auffinden von Engpässen: für jede Ressource Utilisation, Saturation und Errors messen. Auslastung allein täuscht, denn ein Pool kann zu 100 Prozent ausgelastet und völlig gesund sein oder zu 60 Prozent ausgelastet mit einer langen Warteschlange dahinter. Die Sättigung — die Menge wartender Arbeit — ist meist das Signal, das den Engpass benennt, und genau die Kennzahl exportieren die wenigsten Teams.

L4- oder L7-Load-Balancing?

L4 verteilt TCP-Verbindungen, ist günstig und protokollunabhängig. Bei HTTP/2 oder gRPC trägt eine Verbindung jedoch viele gemultiplexte Anfragen, sodass das Verteilen von Verbindungen einem Backend den Großteil der tatsächlichen Arbeit überlassen kann. L7 versteht Anfragen, verteilt sie einzeln, kann idempotente Aufrufe wiederholen und nach Pfad oder Header routen — zum Preis des Terminierens und Neuaufbaus von Verbindungen. Für HTTP/2- und gRPC-Verkehr wollen Sie in der Regel Verteilung auf Anfrageebene.

Aus der Community

Diskussion im Fediverse

Antworten von Mastodon und Bluesky — direkt aus dem offenen Netz, ohne Tracking.

Antworten werden geladen …

ENDE