Exkurs: Backpressure — Wenn Systeme Nein sagen lernen

An deutschen Autobahnauffahrten gibt es seit einigen Jahren Zuflussregelungsanlagen — Ampeln, die den Zufluss auf die Autobahn dosieren. Wenn die Autobahn voll ist, schaltet die Ampel auf Rot und lässt nur alle paar Sekunden ein Auto durch. Die Autos auf der Auffahrt stehen. Die Autos auf der Autobahn fahren.

Nicht elegant. Für Leute, die diesen Exkurs nicht gelesen haben auch irritierend. Aber wirksam.

Das Prinzip dahinter ist Backpressure: Ein überlastetes System signalisiert stromaufwärts, dass es nicht mehr aufnehmen kann. Statt alle Anfragen zu akzeptieren und unter der Last zusammenzubrechen, lehnt es einen Teil ab — damit der Rest schnell bleibt.

Drei Spielarten

In Software-Systemen gibt es drei Varianten, dieses Prinzip umzusetzen:

Ablehnen. Der Service gibt HTTP 503 zurück: „Service Unavailable, versuch’s später.” Brutal, aber ehrlich. Der Client weiß sofort, woran er ist, und kann es nach einer kurzen Pause erneut versuchen. Besser als 30 Sekunden auf ein Timeout zu warten und dann trotzdem eine Fehlermeldung zu bekommen.

Verzögern. Requests landen in einer Queue mit begrenzter Kapazität. Solange die Queue nicht voll ist, werden Anfragen gepuffert und in der Reihenfolge abgearbeitet. Läuft die Queue über, greift wieder Variante eins. Der Vorteil: Kurze Lastspitzen werden geglättet. Der Nachteil: Die Queue selbst braucht Ressourcen, und die Wartezeit addiert sich zur Antwortzeit.

Drosseln. Rate Limiting begrenzt die Anzahl der Anfragen pro Zeiteinheit, typischerweise pro Client oder API-Key. Ein Token-Bucket-Algorithmus erlaubt kurze Bursts, erzwingt aber im Mittel ein festes Limit. Das schützt den Service vor einzelnen Clients, die unverhältnismäßig viel Last erzeugen — sei es durch Fehlkonfiguration, aggressive Crawler oder einen Bug in einer Retry-Logik.

Wann braucht man das?

Am Sättigungspunkt. Little’s Law zeigt, was passiert, wenn ein System seine Kapazitätsgrenze erreicht: Die Wartezeiten explodieren überproportional. Ohne Backpressure akzeptiert der Service weiter Requests, die er nicht mehr rechtzeitig bearbeiten kann. Threads blockieren auf Datenbankverbindungen, die Warteschlange wächst, die Antwortzeiten steigen für alle Anfragen — auch für die, die der Service eigentlich noch hätte bedienen können.

Mit Backpressure: Einige Requests werden abgelehnt. Aber die restlichen bekommen ihre Antwort in normaler Zeit. Das ist kein Kompromiss — das ist Triage.

Connection-Pools: Backpressure ohne Absicht

Der Connection-Pool ist ein Paradebeispiel für Backpressure, die niemand als solche geplant hat. Wenn alle Verbindungen zur Datenbank belegt sind, wartet der nächste Thread auf eine freie Connection — oder bekommt nach einem Timeout eine Exception. Der Pool begrenzt, wie viel Last an die Datenbank weitergegeben wird.

Das ist Backpressure. Nur eben nicht explizit entworfen, sondern als Nebeneffekt einer Ressourcenbegrenzung entstanden. Und genau deshalb funktioniert es oft schlecht: Der Timeout ist zu hoch (30 Sekunden Default bei manchen Frameworks), es gibt keine sinnvolle Fehlermeldung an den Client, und die blockierten Threads belegen ihren Platz im Thread-Pool, bis auch dieser erschöpft ist.

Wer Connection-Pool-Timeouts bewusst konfiguriert — kurz genug, um nicht den Thread-Pool zu blockieren, lang genug, um kurze Spitzen zu puffern — betreibt im Grunde Backpressure-Design. Die meisten Teams tun es, ohne es so zu nennen.

Die unangenehmen Fragen

Backpressure klingt in der Theorie sauber. In der Praxis wirft sie unbequeme Fragen auf.

Wer wird abgelehnt? Ein HTTP 503 trifft alle Requests gleich — den Nutzer, der gerade bezahlen will, genauso wie den Bot, der zum dritten Mal die Sitemap crawlt. Load Shedding — das gezielte Verwerfen niedrigpriorer Requests, um Kapazität für kritische Funktionen freizuhalten — ist die Antwort, aber auch deutlich komplexer. Was ist ein „wichtiger” Request? Wer entscheidet das — und wie schnell muss diese Entscheidung fallen?

Was passiert danach? Der abgewiesene Request verschwindet nicht. Entweder versucht der Client es erneut — und erzeugt damit zusätzlichen Traffic, genau in dem Moment, in dem das System überlastet ist — oder der Nutzer gibt auf. Retries ohne Backoff und Jitter sind eine zuverlässige Methode, aus einer kurzen Überlast eine lange zu machen.

Ist der Request wiederholbar? Retries funktionieren nur bei idempotenten Operationen. Eine Suchanfrage kann man gefahrlos wiederholen. Eine Bestellung? Kommt darauf an, ob das System Duplikaterkennung hat. Ohne Idempotenz ist jeder Retry ein Risiko.

Was alle drei Fragen gemeinsam haben: Backpressure verschiebt die Komplexität vom Server zum Client. Der Service wird einfacher — er sagt Nein und ist fertig. Aber der Client muss jetzt entscheiden: Wie lange warten? Wie oft wiederholen? Mit welchem Backoff? Was dem Nutzer anzeigen? Retry-Logik, Idempotenz, Timeout-Handling, Fallbacks — das alles landet beim Aufrufer. In einer Microservice-Architektur, wo jeder Service Client eines anderen ist, multipliziert sich diese Komplexität über die gesamte Aufrufkette. Backpressure ist ein enormer Komplexitätstreiber — und wer ihn einführt, ohne die Client-Seite mitzudenken, verlagert das Problem, statt es zu lösen.


Quellen

Kommentare