Die Herausforderung
Eine Analytics-Plattform erfasste Produktereignisse einer wachsenden Kundenbasis in einer einzigen Tabelle „raw_events“. Das ursprüngliche Schema war korrekt, aber langsam: Jedes Ereignis landete in einer protokollierten Tabelle mit sieben Indizes, das WAL war der Engpass, und der Ingest-Durchsatz hatte bei etwa 8.000 Ereignissen pro Sekunde seine Grenze erreicht. Die RDS-IOPS waren der dominierende Kostenblock, und eine geplante Verdreifachung der Kundenzahl hätte die Pipeline gesprengt, noch bevor der nächste Vertrag unterzeichnet war.
Das Team hatte die üblichen Ratschläge gelesen – „eine Queue verwenden“, „die Tabelle shardieren“, „auf eine dedizierte TSDB umziehen“ –, aber all diese Antworten bedeuteten eine Migration über mehrere Quartale. Die eigentliche Frage lautete: Kann PostgreSQL mithalten, wenn wir aufhören, dagegen anzukämpfen? Der Haken war eine harte Anforderung: Alles, was die auditierten Reporting-Tabellen erreichte, musste dauerhaft, konsistent und indiziert sein. Wir durften die Integrität auf der Analytics-Seite nicht opfern, um die Ingest-Seite zu entlasten.
Unsere Lösung
Wir haben die Pipeline in zwei klar getrennte Stufen mit zwei unterschiedlichen Dauerhaftigkeitsverträgen aufgeteilt.
Stufe 1 (Ingest): eine UNLOGGED-Staging-Tabelle, die den Datenstrom aufnimmt. UNLOGGED-Tabellen in PostgreSQL überspringen WAL-Schreibvorgänge bei Inserts, was für kurzlebige Staging-Daten genau der richtige Kompromiss ist – wir erhalten den 5- bis 10-fachen reinen Insert-Durchsatz und nehmen dafür in Kauf, die gestageten Zeilen bei einem Serverabsturz zu verlieren. In Kombination mit gebatchtem COPY (nicht INSERT) aus einem Python-Ingest-Service und der bewussten Regel „keine Indizes auf der Staging-Tabelle“ stieg der reine Ingest auf derselben RDS-Instanz von 8 Tsd. auf über 80 Tsd. Ereignisse/Sek.
Stufe 2 (dauerhaft): Ein Celery-Worker leert die Staging-Tabelle in Mikro-Batches in die echte, vollständig protokollierte, indizierte Tabelle `events`, innerhalb einer einzigen Transaktion mit idempotenten Upserts. Der auditierte Reporting-Pfad liest ausschließlich aus der dauerhaften Tabelle. Stürzt der Server mitten im Ingest ab, verlieren wir höchstens einige Sekunden an Ereignissen in der Staging-Tabelle – und die vorgelagerten Produzenten wiederholen, sodass die dauerhafte Tabelle wieder zum korrekten Zustand konvergiert.
Dazu haben wir eine kleine, aber sorgfältige Betriebsschicht ergänzt: ein Grafana-Dashboard für die Füllrate von Stufe 1, einen harten Alarm, falls der Drainer zurückfällt, Partitionsrotation auf der dauerhaften Tabelle und ein schriftliches Runbook für die drei relevanten Fehlerfälle.
- UNLOGGED-Staging-Tabelle ohne Indizes – gezielt für reinen Insert-Durchsatz entwickelt
- Gebatchter COPY-Ingest aus einem Python-/FastAPI-Service statt zeilenweiser INSERTs
- Celery-Drainer, der Mikro-Batches in die dauerhafte, vollständig indizierte Events-Tabelle überführt
- Idempotente Upserts (ON CONFLICT DO NOTHING), damit Wiederholungen der Produzenten sicher sind
- Monatliche Partitionierung der dauerhaften Tabelle, um Vacuum- und Indexarbeit zu begrenzen
- Ende-zu-Ende-Dashboards und Alarme für Verzögerungen in Grafana / Datadog
- Schriftliches Runbook für RDS-Failover, Drainer-Absturz und Back-Pressure der Produzenten
- Audit-Freigabe des Dauerhaftigkeitsprofils vor dem Launch – keine Überraschungen