Skip to content

Staging Ingest Operations Runbook

Dieser Block beschreibt nur den operativen Ablauf fuer den separaten Kafka/Ingest-Worker im Staging. Die API bleibt dabei weiter online, weil der Worker als eigener Compose-Service laeuft.

Wann wir den Worker einschalten

  • STAGING_INGEST_ENABLED=true startet den dedizierten ingest-Worker beim naechsten Staging-Deploy.
  • STAGING_INGEST_ENABLED=false stoppt den Worker wieder und entfernt alte Container aus frueheren Deploys.
  • Aendere den Wert in den GitHub Actions Variablen und starte den Workflow Deploy Staging erneut auf dem development-Branch.

Replicas hoch- und runterfahren

  • STAGING_INGEST_REPLICAS steuert die Anzahl der Worker-Instanzen im Staging-Deploy.
  • Starte normal mit 1.
  • Erhoehe den Wert, wenn sich CDC-Backlog oder Catch-up sichtbar aufbauen.
  • Reduziere den Wert wieder, wenn die Last sinkt oder wir nur eine einzelne Instanz brauchen.
  • Mehrere Replicas sind unkritisch, weil sie sich den gleichen Kafka Consumer Group State teilen.

Verifikation nach dem Deploy

Pruefe nach jedem Deploy diese Punkte:

docker compose ps backend ingest
docker compose logs --tail 200 ingest
curl -sS http://127.0.0.1:8080/healthz | jq '.components.ingest'
curl -sS http://127.0.0.1:8080/api/admin/diagnostics/summary | jq '.ingest'

Erwartung: - status unter components.ingest ist up - workerState ist running - lastErrorMessage ist leer - heartbeatAt und lastProcessedAt aktualisieren sich - catchUpState ist je nach Lage live oder kontrolliert catching_up - lagSampledAt wird frisch geschrieben und lagErrorMessage bleibt leer - totalLag, maxTopicLag und lagTopics zeigen, ob echter Rueckstau vorliegt oder nur normaler Live-Betrieb - der Admin-Bereich zeigt den Ingest-Status nicht mehr als pauschalen Fehler, sondern als echte Worker-Info

Wenn der Worker laeuft, aber die Daten nicht weiter vorankommen, dann zuerst Logs und Health pruefen, danach die Kafka-/Debezium-Strecke getrennt betrachten.

Wichtig fuer Releases mit Datenbankaenderungen:

  • Deploy Staging fuehrt vor dem Restart automatisch npm run prisma:migrate:deploy im Backend-Image aus.
  • Dadurch werden neue Prisma-Migrationen zusammen mit dem Release auf das Staging-Schema angewendet.
  • Wenn dieser Schritt scheitert, bleibt das Deploy bewusst stehen, bevor die neuen Container live gehen.

Backfill und Reprocessing

Fuer gezielte Rueckverarbeitung nutzen wir den Backfill-Command im Backend:

cd targetshot/backend
npm run ingest:backfill -- 1234:5678 1234:6789

Nutzliche Optionen:

  • --group=<id> verwendet eine stabile Consumer Group fuer Lag-Beobachtung
  • --new-group startet einen frischen Replay-Lauf ab Offset 0

Empfohlener Ablauf fuer groessere Reprocessing-Faelle:

  1. Live-Worker bei Bedarf kurz deaktivieren oder auf 1 Replicas lassen.
  2. Backfill mit einer klar benannten Gruppe starten.
  3. Ergebnis in Mirror-DB, Kafka-Topic und Admin-Diagnose pruefen.
  4. Den normalen Worker wieder aktivieren bzw. die Replicas hochziehen.

Fuer die historische Rueckverarbeitung der Schuetzen-Stammdaten steht zusaetzlich der manuelle GitHub-Workflow Replay Shooter Master Data zur Verfuegung.

Empfohlene Eingaben:

  • club_ids: zum Beispiel 413067
  • group_id: Standard targetshot-shooter-backfill
  • fresh_group=true: erzwingt einen frischen Replay-Lauf ab Offset 0

Der Workflow startet einen einmaligen Backend-Container mit node dist/ingest/backfillShooters.js ... und nutzt dabei die bestehende Staging-.env.

Incident-Matrix fuer Source-Drift und Rejects

Die Ingest-Health unterscheidet jetzt bewusst zwischen normalem Betrieb, frischen Vertragsabweichungen und echter Quarantaene:

  • workerState=running + catchUpState=live: normaler Live-Betrieb
  • admissionState=rejecting: frische, wiederholte Contract-Rejects; einzelne fehlerhafte Events werden verworfen, aber der Worker verarbeitet weiter
  • sourceDriftState=suspected: es gibt ein frisches Drift-Signal, bevor bereits ein voller Incident offen ist
  • admissionState=quarantined: derselbe fehlerhafte Pfad wurde mehrfach in kurzer Zeit abgelehnt; der Vorfall ist jetzt operativ ein Incident
  • incidentState=recovered: der vormals fehlerhafte Pfad wurde spaeter wieder mit passender Herkunft akzeptiert

Diese Signale erscheinen an drei Stellen:

  • GET /healthz unter components.ingest
  • admin-portal in Connectors beim Shared-Ingest-Block des Host-Dialogs
  • admin-portal in Diagnostics und in den Dashboard-Warnungen

Wichtige Felder fuer die Einordnung:

  • lastContractRejectReason
  • lastContractRejectTopic
  • lastContractRejectEntity
  • sourceDriftDetail
  • rejectBurstCount
  • quarantineSince
  • quarantineTopic
  • quarantineClubId
  • quarantineEntity
  • quarantineDetail

Vorgehen bei rejecting oder sourceDriftState=suspected

Wenn der Worker noch laeuft, aber frische Rejects oder Drift-Hinweise auftauchen:

  1. Im admin-portal den betroffenen Club-Host oeffnen und sourceDriftDetail, lastContractRejectReason, Topic und Entity notieren.
  2. In /healthz oder den Worker-Logs bestaetigen, ob der Fehler von einem einzelnen Event oder von einer durchgehenden Serie kommt.
  3. Am Producer-/Connector-Pfad pruefen:
  4. passt source_topic zur Entitaet?
  5. passt source_club_id zum Topic-Scope?
  6. gibt es widerspruechliche Header-/Payload-Herkunft?
  7. Solange noch keine Quarantaene vorliegt, den Worker weiterlaufen lassen und nur den fehlerhaften Producer-Pfad korrigieren.
  8. Nach der Korrektur aktiv beobachten, ob sourceDriftState wieder auf recovered oder none faellt.

Faustregel:

  • suspected oder rejecting ist noch kein automatischer Rollback-Fall.
  • Erst wenn die gleiche Abweichung wiederholt auftritt oder produktiv mehrere Clubs betreffen koennte, wird der Incident wie eine Quarantaene behandelt.

Vorgehen bei admissionState=quarantined

Bei Quarantaene ist der Herkunftsvertrag mehrfach verletzt worden. Dann gilt:

  1. Den betroffenen Producer-/Connector-Pfad zuerst stoppen oder korrigieren.
  2. Keine grossflaechige Replay-Aktion starten, solange der Scope-Fehler noch offen ist.
  3. quarantineTopic, quarantineClubId, quarantineEntity und quarantineReason im Incident festhalten.
  4. Danach entscheiden:
  5. nur der betroffene Pfad war falsch: Worker kann weiterlaufen, sobald wieder gueltige Events kommen
  6. mehrere Pfade oder hoher Druck auf dem Cluster: STAGING_INGEST_REPLICAS reduzieren oder STAGING_INGEST_ENABLED=false setzen und neu deployen
  7. Erst nach erfolgreicher Scope-Korrektur gezielt reprocessen.

Ein recovered-Zustand bedeutet:

  • der Worker hat fuer denselben Pfad spaeter wieder gueltige Herkunft gesehen
  • das ist ein gutes Signal, ersetzt aber nicht die fachliche Kontrolle, ob ein Replay noetig ist

Reprocessing-Vertrag nach einer Korrektur

Nach einem Source- oder Mapping-Fehler wird immer in dieser Reihenfolge gearbeitet:

  1. Ursache am Producer-/Connector beheben
  2. mit /healthz bestaetigen, dass keine neue Reject-Serie mehr entsteht
  3. Reprocessing gezielt fuer den betroffenen Datenschnitt starten
  4. Ergebnis im Zielmodell validieren
  5. erst danach eventuell Replicas wieder hochziehen

Fuer Treffer-/Scheiben-bezogene Rueckverarbeitung:

cd targetshot/backend
npm run ingest:backfill -- --group=targetshot-reprocess-incident 413067:899822614

Fuer einen frischen Replay-Lauf ohne alten Consumer-Offset:

cd targetshot/backend
npm run ingest:backfill -- --group=targetshot-reprocess-incident --new-group 413067:899822614

Fuer historische Schuetzen-Stammdaten:

cd targetshot/backend
npm run ingest:backfill-shooters -- --group=targetshot-shooter-reprocess --new-group 413067

Nach jedem Reprocessing muessen mindestens diese Punkte gruen sein:

  • workerState=running
  • catchUpState geht wieder auf live oder faellt kontrolliert aus catching_up zurueck
  • sourceDriftState bleibt auf none oder recovered
  • keine neuen Contract-Rejects fuer denselben Pfad
  • das fachliche Zielmodell wurde wirklich aktualisiert

Rollback

Wenn eine neue Ingest-Version Probleme macht:

  1. STAGING_INGEST_ENABLED=false setzen und den Staging-Deploy erneut ausfuehren.
  2. Wenn nur zu viel Druck auf dem Cluster entsteht, STAGING_INGEST_REPLICAS auf 1 reduzieren.
  3. Danach Logs, Health und Mirror-DB erneut validieren.
  4. Vor einem erneuten Einschalten pruefen, ob noch ein offener Source-Drift-/Quarantaene-Kontext besteht.
  5. Erst wenn der Ingest-Pfad wieder stabil ist, den Worker wieder hochfahren.

Wichtig: - Der Worker ist absichtlich vom API-Container getrennt. - Ein Rollback kann deshalb den Ingest sauber stoppen, ohne dass die API neu gebaut werden muss. - Ein Rollback ersetzt keine spaetere gezielte Rueckverarbeitung; fehlende Daten muessen danach bewusst nachgezogen werden.

Kurzcheck fuer Operatoren

  • Ist der Worker aktiv?
  • Meldet /healthz den Worker als up und running?
  • Wieviele Replicas laufen?
  • Stehen lastProcessedAt und heartbeatAt noch?
  • Ist lagSampledAt frisch und bleibt lagErrorMessage leer?
  • Wie gross sind totalLag und die auffaelligsten lagTopics?
  • Gibt es sourceDriftState, frische Rejects oder eine laufende Quarantaene?
  • Ist ein neuer Fehler in lastErrorMessage aufgetaucht?
  • Wurde eine gegebene Rueckverarbeitung wirklich im Mirror-DB-Stand sichtbar?
  • Wurde nach einem Incident auch das fachliche Zielmodell wieder geprueft?

Wenn diese Punkte sauber sind, ist der Staging-Ingest aus Ops-Sicht gesund.