Engineering

So bauen deutsche Unternehmen resiliente Systeme: Lessons Learned aus Krisenzeiten

Erkenntnisse aus realen Krisensituationen: Wie Sie IT-Systeme bauen, die Ausfälle überstehen und unter Last stabil bleiben.

P
Philipp Joos
Autor
12. Oktober 2025
13 min Lesezeit

Die Krise als Lehrer: Was wir aus Ausfällen lernen

Jedes Unternehmen erlebt früher oder später eine IT-Krise. Die Frage ist nicht ob, sondern wann – und wie gut Sie vorbereitet sind.

Dieser Artikel teilt Erkenntnisse aus realen Krisensituationen und zeigt, wie Sie Systeme bauen, die auch unter extremen Bedingungen funktionieren.

Was ist Resilienz?

Resilienz ist mehr als Hochverfügbarkeit. Es ist die Fähigkeit eines Systems:

  1. Störungen zu absorbieren ohne vollständigen Ausfall
  2. Sich automatisch zu erholen nach Teillausfällen
  3. Unter Degradation zu funktionieren wenn nicht alles verfügbar ist
  4. Aus Fehlern zu lernen und robuster zu werden
flowchart TB
    subgraph Resilienz["Resilienz-Ebenen"]
        L4["4. Organisatorisch | Prozesse, Training, Postmortems"]
        L3["3. Architektur | Microservices, Loose Coupling"]
        L2["2. Infrastruktur | Redundanz, Multi-Zone, DR"]
        L1["1. Anwendung | Retry, Circuit Breaker, Fallback"]
    end
    L4 --> L3 --> L2 --> L1

Lessons Learned aus realen Krisen

Lesson 1: Cascading Failures kommen schneller als gedacht

Der Fall: Ein deutscher E-Commerce-Händler erlebte am Black Friday einen Domino-Effekt. Eine langsame Datenbankabfrage führte zu Thread-Pool-Erschöpfung, die führte zu Timeouts im API-Gateway, die führte zu Retry-Storms, die das gesamte System in die Knie zwang.

Die Ursache: Fehlende Circuit Breaker und unbegrenzte Retry-Logik.

Die Lösung:

// Circuit Breaker Pattern
class CircuitBreaker {
  private state: 'closed' | 'open' | 'half-open' = 'closed';
  private failureCount = 0;
  private lastFailure: Date | null = null;

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (this.shouldAttemptReset()) {
        this.state = 'half-open';
      } else {
        throw new CircuitOpenError();
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    this.state = 'closed';
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailure = new Date();
    if (this.failureCount >= 5) {
      this.state = 'open';
    }
  }
}

Lesson 2: Monitoring allein reicht nicht

Der Fall: Ein Logistik-Unternehmen hatte umfangreiches Monitoring – aber niemand schaute hin. Der Ausfall um 3 Uhr nachts wurde erst bemerkt, als die Frühschicht keine Daten hatte.

Die Ursache: Monitoring ohne Alerting, Alerting ohne Eskalation.

Die Lösung:

# Mehrstufige Alerting-Strategie
alerts:
  - name: api_error_rate_high
    condition: error_rate > 5%
    for: 5m
    severity: warning
    notify: slack-team-channel

  - name: api_error_rate_critical
    condition: error_rate > 20%
    for: 2m
    severity: critical
    notify: pagerduty-oncall

  - name: api_completely_down
    condition: successful_requests == 0
    for: 1m
    severity: page
    notify: pagerduty-oncall
    escalate_after: 10m
    escalate_to: engineering-leadership

Lesson 3: Backups, die nie getestet wurden, sind keine Backups

Der Fall: Nach einem Ransomware-Angriff stellte ein Mittelständler fest, dass die Backups seit 6 Monaten fehlerhaft waren – ohne dass es jemand bemerkt hatte.

Die Ursache: Backup-Jobs liefen, aber Restore wurde nie getestet.

Die Lösung:

# Automatisierte Backup-Verifizierung
backup_verification:
  schedule: weekly
  steps:
    - name: Restore to Test Environment
      action: restore_latest_backup
      target: disaster-recovery-cluster
      
    - name: Run Smoke Tests
      action: execute_test_suite
      suite: critical-business-functions
      
    - name: Verify Data Integrity
      action: compare_checksums
      threshold: 100%
      
    - name: Report Results
      action: send_report
      recipients: [ops-team, compliance]

Lesson 4: Single Points of Failure verstecken sich überall

Der Fall: Ein Finanzdienstleister hatte redundante Server, redundante Datenbanken, redundantes Netzwerk – aber einen einzelnen DNS-Server. Als der ausfiel, war nichts mehr erreichbar.

Die Ursache: Fokus auf offensichtliche SPOF, Übersehen der weniger offensichtlichen.

Die Lösung: SPOF-Analyse

flowchart TB
    subgraph SPOF["SPOF-Checkliste"]
        subgraph Network["Netzwerk"]
            N1["DNS (Primary + Secondary?)"]
            N2["Load Balancer (Active/Passive oder Active/Active?)"]
            N3["Internet-Uplink (Multi-Provider?)"]
            N4["Firewall (HA-Cluster?)"]
        end
        subgraph Compute["Compute"]
            C1["Application Server (mind. 2 Instanzen?)"]
            C2["Verfügbarkeitszonen (mind. 2 AZs?)"]
            C3["Container Orchestration (Multi-Master?)"]
        end
        subgraph Data["Daten"]
            D1["Datenbank (Primary/Replica?)"]
            D2["Cache (Cluster oder Fallback-Strategie?)"]
            D3["Message Queue (Cluster oder Redundanz?)"]
            D4["Object Storage (Cross-Region Replikation?)"]
        end
        subgraph External["Externe Abhängigkeiten"]
            E1["Payment Provider (Fallback-Provider?)"]
            E2["Email Service (Alternativer Anbieter?)"]
            E3["CDN (Multi-CDN oder Origin-Fallback?)"]
        end
    end

Lesson 5: Graceful Degradation schlägt totalen Ausfall

Der Fall: Ein Buchungsportal konnte bei Datenbankproblemen keine Buchungen mehr anzeigen – obwohl die Suchfunktion und Neukundenanmeldung hätten funktionieren können.

Die Ursache: Alles-oder-nichts-Architektur ohne Fallback-Strategien.

Die Lösung:

// Graceful Degradation Strategie
class BookingService {
  async getBookings(userId: string): Promise<BookingResponse> {
    try {
      // Primäre Datenquelle
      return await this.primaryDb.getBookings(userId);
    } catch (error) {
      this.metrics.increment('booking.primary_fallback');
      
      try {
        // Cache als Fallback (evtl. stale data)
        const cached = await this.cache.getBookings(userId);
        return {
          ...cached,
          stale: true,
          message: 'Zeigt möglicherweise nicht aktuelle Daten'
        };
      } catch (cacheError) {
        // Minimale Funktionalität
        return {
          bookings: [],
          unavailable: true,
          message: 'Buchungen temporär nicht verfügbar. Bitte später erneut versuchen.'
        };
      }
    }
  }
}

Resilienz-Patterns in der Praxis

Pattern 1: Bulkhead (Schottmuster)

Isolieren Sie Komponenten, damit ein Ausfall begrenzt bleibt:

// Separate Thread-Pools für verschiedene Operationen
const pools = {
  critical: new ThreadPool({ maxSize: 50, queue: 100 }),
  standard: new ThreadPool({ maxSize: 20, queue: 50 }),
  background: new ThreadPool({ maxSize: 10, queue: 1000 }),
};

// Kritische Operationen beeinflussen nicht unkritische
async function handlePayment(request: PaymentRequest) {
  return pools.critical.execute(() => processPayment(request));
}

async function handleAnalytics(event: AnalyticsEvent) {
  return pools.background.execute(() => trackEvent(event));
}

Pattern 2: Retry mit Exponential Backoff

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  options: { maxRetries: number; baseDelay: number; maxDelay: number }
): Promise<T> {
  let lastError: Error;
  
  for (let attempt = 0; attempt < options.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      if (!isRetryable(error)) {
        throw error;
      }
      
      const delay = Math.min(
        options.baseDelay * Math.pow(2, attempt) + randomJitter(),
        options.maxDelay
      );
      
      await sleep(delay);
    }
  }
  
  throw lastError;
}

Pattern 3: Health Checks und Readiness

// Unterscheidung zwischen Liveness und Readiness
app.get('/health/live', (req, res) => {
  // Bin ich grundsätzlich am Leben?
  res.status(200).json({ status: 'alive' });
});

app.get('/health/ready', async (req, res) => {
  // Kann ich Traffic verarbeiten?
  const checks = await Promise.all([
    checkDatabase(),
    checkCache(),
    checkExternalDependencies(),
  ]);
  
  const allHealthy = checks.every(c => c.healthy);
  
  res.status(allHealthy ? 200 : 503).json({
    status: allHealthy ? 'ready' : 'not ready',
    checks,
  });
});

Chaos Engineering: Resilienz testen

Der Gedanke dahinter

Wenn Sie nicht wissen, wie Ihr System bei Ausfällen reagiert, wissen Sie es erst im Ernstfall – wenn es zu spät ist.

Chaos Engineering testet Resilienz proaktiv:

  1. Hypothese aufstellen: "Wenn Service X ausfällt, degradiert das System graceful"
  2. Experiment durchführen: Service X in kontrollierter Umgebung ausschalten
  3. Beobachten: Verhält sich das System wie erwartet?
  4. Lernen: Schwachstellen identifizieren und beheben

Einfacher Start

# Chaos Experiment: Pod-Kill
experiment:
  name: api-pod-failure
  hypothesis: "Das System bleibt verfügbar, wenn ein API-Pod ausfällt"
  
  steady_state:
    - probe: http
      url: https://api.example.com/health
      expected_status: 200
      
  method:
    - action: kill
      target: pod
      selector:
        app: api
      count: 1
      
  rollback:
    - action: scale
      target: deployment/api
      replicas: 3

Disaster Recovery: Der Ernstfall

RTO und RPO definieren

RTO (Recovery Time Objective): Wie schnell müssen Sie wieder online sein?
RPO (Recovery Point Objective): Wie viel Datenverlust ist akzeptabel?

KritikalitätRTORPOStrategie
Mission Critical< 1 Stunde0Active/Active Multi-Region
Business Critical< 4 Stunden< 1 StundeHot Standby
Important< 24 Stunden< 4 StundenWarm Standby
Normal< 72 Stunden< 24 StundenCold Standby mit Backups

DR-Runbook Template

# Disaster Recovery Runbook

## Trigger-Kriterien
- Primäres Datacenter nicht erreichbar > 15 Minuten
- Bestätigung durch Operations Lead

## Failover-Schritte
1. [ ] Incident Commander bestimmen
2. [ ] Stakeholder informieren
3. [ ] DR-Umgebung aktivieren
4. [ ] DNS-Failover initiieren
5. [ ] Smoke Tests durchführen
6. [ ] Erfolg bestätigen

## Failback-Schritte
(Nach Wiederherstellung des Primärsystems)
1. [ ] Daten-Synchronisation verifizieren
2. [ ] Schrittweiser Traffic-Umzug
3. [ ] Monitoring intensivieren
4. [ ] Vollständigen Failback bestätigen

## Kontakte
- Operations Lead: +49 XXX
- Engineering Lead: +49 XXX
- Management: +49 XXX

Fazit: Resilienz als Investition

Resiliente Systeme zu bauen kostet Zeit und Geld. Aber die Alternative – ungeplante Ausfälle – kostet mehr:

  • Direkte Kosten: Umsatzverlust, SLA-Strafen
  • Indirekte Kosten: Vertrauensverlust, Marken-Schaden
  • Opportunitätskosten: Firefighting statt Innovation

Die wichtigsten Takeaways:

  1. Resilienz ist Schicht für Schicht: Anwendung, Infrastruktur, Architektur, Organisation
  2. Testen Sie Ausfälle, bevor sie passieren: Chaos Engineering
  3. Graceful Degradation vor Total-Ausfall: Fallback-Strategien
  4. Lernen Sie aus jeder Krise: Blameless Postmortems

Sie möchten Ihre Systeme resilienter machen? Wir unterstützen Sie bei der Analyse, Konzeption und Implementierung – basierend auf Erfahrung aus zahlreichen kritischen Systemen.

Tags:
Resilienz
Hochverfügbarkeit
Disaster Recovery
Business Continuity
SRE
Chaos Engineering
P

Philipp Joos

Geschäftsführer und Gründer von CFTools Software GmbH. Leidenschaftlich in der Entwicklung skalierbarer Softwarelösungen und Cloud-Native-Architekturen.

Artikel nicht verfügbar

Dieser Artikel ist für Ihren Zugangstyp nicht verfügbar.

Alle Artikel anzeigen