Ausfallsicherheit beginnt
bei der Architektur.

Die meisten Ausfälle, die ich in bestehenden Projekten finde, sind keine Server-Probleme. Es sind Architektur-Probleme: ein einzelner langsamer Dienst legt eine ganze Anwendung lahm, weil niemand definiert hat, was passieren soll, wenn er nicht antwortet.

Modulgrenzen statt Microservices

Der Reflex bei wachsenden Projekten ist die Aufteilung in Microservices. In der Praxis entsteht daraus oft ein verteilter Monolith: dieselben engen Abhängigkeiten wie vorher, nur jetzt über das Netzwerk – mit Latenz, Timeouts und deutlich schwierigerem Debugging.

Der erste Schritt ist deshalb nicht die Aufteilung, sondern die Grenze. Jedes Modul bekommt eine schmale, klar dokumentierte Schnittstelle und greift nie direkt in die Interna eines anderen. Wenn diese Grenzen im Code sauber sitzen, lässt sich ein Modul später ohne Umbau herauslösen – oder es bleibt einfach dort, wo es ist.

  • Jedes Modul hat genau einen Einstiegspunkt – keine Zugriffe an ihm vorbei.
  • Datenbanktabellen gehören einem Modul, andere fragen über dessen Schnittstelle an.
  • Abhängigkeiten laufen in eine Richtung, damit keine Zyklen entstehen.

Circuit Breaker für externe Dienste

Sobald eine Anwendung fremde Dienste einbindet – Zahlungsanbieter, Versanddienstleister, LLM-APIs – gilt: Der Dienst wird ausfallen. Nicht ob, sondern wann. Ohne Schutz wartet jeder Request in die Timeout-Grenze, die Warteschlange läuft voll, und die komplette Seite steht, obwohl nur eine Randfunktion betroffen ist.

Ein Circuit Breaker zählt Fehlschläge und macht nach einer festgelegten Schwelle sofort dicht: Weitere Aufrufe scheitern unmittelbar, statt in den Timeout zu laufen. Nach einer Abkühlzeit lässt er einen einzelnen Testaufruf durch und öffnet erst dann wieder.

app/Support/CircuitBreaker.php
final class CircuitBreaker
{
    public function __construct(
        private string $service,
        private int $threshold = 5,
        private int $cooldown = 60,
    ) {}

    public function call(callable $request): mixed
    {
        if (Cache::get("cb:{$this->service}:open")) {
            throw new ServiceUnavailableException($this->service);
        }

        try {
            $response = $request();
            Cache::forget("cb:{$this->service}:failures");

            return $response;
        } catch (Throwable $e) {
            $failures = Cache::increment("cb:{$this->service}:failures");

            if ($failures >= $this->threshold) {
                Cache::put("cb:{$this->service}:open", true, $this->cooldown);
            }

            throw $e;
        }
    }
}

Genauso wichtig wie der Breaker ist der Plan für den offenen Zustand: eine zwischen- gespeicherte Antwort ausliefern, die Funktion sichtbar deaktivieren oder den Auftrag in eine Queue legen und später nachziehen. Ein stiller Fehler ist die schlechteste Variante.

Monitoring, das Ursachen zeigt

Klassisches Monitoring meldet, dass etwas nicht stimmt. Was fehlt, ist meist die Antwort auf das Warum. Der Unterschied liegt im Detailgrad der Daten: Wer neben Statuscodes auch Kunden-ID, Endpunkt, aufgerufenen Fremddienst und Dauer protokolliert, kann einen Zwischenfall im Nachhinein rekonstruieren, statt ihn zu erraten.

Hier ist KI inzwischen tatsächlich nützlich: Ein Sprachmodell über den Logstream erkennt ungewöhnliche Muster, fasst zusammen und schlägt eine Ursache vor – nicht als Ersatz für Alarme mit festen Schwellwerten, sondern als erste Einschätzung, die einem das Durchsuchen tausender Zeilen abnimmt.

Fazit

Resilienz ist keine Komponente, die man nachträglich einbaut, sondern eine Reihe kleiner Entscheidungen: klare Modulgrenzen, definiertes Verhalten bei Fehlern und Daten, die im Ernstfall etwas erklären. Zusammen sorgen sie dafür, dass ein Teilausfall ein Teilausfall bleibt.

Fragen zum Thema – oder ein Projekt, das genau daran hängt?

Ich schaue mir deinen Fall an und sage dir konkret, was sich verbessern lässt. Unverbindlich und ohne Verkaufsgespräch.