Wie eine fehlende Indexierung unsere Datenbank lahmgelegt hat

Ein einziger fehlender Index verwandelte eine 8-ms-API in einen 8-Sekunden-Albtraum.

Wir haben vor einem großen Release einen Lasttest durchgeführt. In der lokalen Entwicklung lief alles mit 100 Datensätzen einwandfrei. Dann simulierten wir 500 Nutzer gegen 1,5 Millionen Zeilen in Datenmengen in Produktionsgröße.

Die Ergebnisse waren schlecht:

  • Die API-Antwortzeiten sprangen von 45 ms auf 8.000 ms.
  • Die Datenbank-CPU erreichte 100 %.
  • Die Connection Pools waren erschöpft, was zu Timeout-Fehlern führte.

Wir prüften das Slow-Query-Log und fanden einen Endpunkt, der die Bestellhistorie nach User-ID und Status abfragte.

Die Ausführung von EXPLAIN ANALYZE in PostgreSQL zeigte einen Sequential Scan. Die Engine las für jede Anfrage alle 1,5 Millionen Zeilen, da kein Index auf user_id vorhanden war.

Bei 100 gleichzeitigen Anfragen wurden 150 Millionen Zeilen auf einmal gescannt.

Die Behebung dauerte fünf Minuten.

Anstatt eines einfachen Index auf user_id erstellten wir einen zusammengesetzten Index auf (user_id, status, created_at DESC). Das ermöglichte der Datenbank:

  • Nach user_id zu filtern.
  • Nach status zu filtern.
  • Die neuesten Zeilen sofort zurückzugeben.
  • Zusätzliche Sortierschritte zu überspringen.

Wir verwendeten CREATE INDEX CONCURRENTLY, damit die Tabelle während des Vorgangs nicht gesperrt wurde.

Nach der Behebung:

  • Die Abfragezeit sank von 8.150 ms auf 0,14 ms.
  • Die API-Latenz fiel von 8 Sekunden auf 12 ms.
  • Die CPU-Auslastung sank von 100 % auf unter 8 %.

Was wir gelernt haben:

  • Lokale Tests sind irreführend; 100 Zeilen repräsentieren keine Million.
  • Indiziert eure Fremdschlüssel – die meisten ORMs lassen dies aus.
  • Führt EXPLAIN ANALYZE aus, bevor ihr mehr Hardware kauft.
  • Entwerft Indizes passend zu den Abfragen, die ihr tatsächlich ausführt.

Skaliert nicht zuerst die Server. Skaliert die Abfragen.

Quelle: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32