DevOps & Monitoring

PostgreSQL: Ungültige Indizes verschlechtern still die Schreibperformance nach fehlgeschlagenen Builds

Fehlgeschlagene parallele Index-Builds in PostgreSQL hinterlassen ungültige Einträge, die Ressourcen verbrauchen. Erfahren Sie, wie Sie diese versteckten Datenbankprobleme erkennen, verstehen und bereinigen können.

Serverschränke mit Warnlichtern, die versteckte Systemprobleme anzeigen
Für diesen Artikel erstellte Illustration

Automatisch aus dem englischen Original übersetzt.

Ein häufiges Muster bei modernen Datenbank-Migrationen ist die Verwendung von CREATE INDEX CONCURRENTLY, um Tabellen während Schemaänderungen nicht zu sperren. Wenn dieser Vorgang jedoch aufgrund von Timeouts oder Datenkonflikten fehlschlägt, bleiben oft "ungültige" Indexeinträge zurück. Diese Überreste helfen zwar nicht bei Abfragen, können aber Schreiboperationen erheblich verlangsamen, da die Datenbank gezwungen wird, sie unnötigerweise zu pflegen.

Was passiert ist

Wenn ein Entwickler oder ein automatisierter Agent einen eindeutigen Index auf einer Live-Tabelle anfordert, ist der richtige Ansatz die Verwendung der Option für parallelen Build. Schlägt der Prozess mitten im Ablauf fehl – etwa wegen eines Verstoßes gegen eine Duplikatschlüssel-Einschränkung, eines Deadlocks oder eines Statement-Timeouts –, führt PostgreSQL nicht einfach ein Rollback der gesamten Operation durch. Stattdessen wird der anfängliche Katalogeintrag für den Index committet, bevor der Build abgeschlossen ist. Das Ergebnis ist ein Index, der im System existiert, aber als ungültig markiert ist.

Das kritische Problem entsteht, wenn Migrationsskripte erneut ausgeführt werden, ohne den Zustand der Datenbank zu prüfen. Ein häufiger Fehler besteht darin, dem Wiederholungskommando IF NOT EXISTS hinzuzufügen. Dies ermöglicht es der Migration, die Continuous-Integration-Prüfungen zu bestehen, ignoriert jedoch die Tatsache, dass der vorhandene Index defekt ist. Die Datenbank trägt weiterhin den Overhead dieser ungültigen Struktur mit sich, was zu einer Leistungsverschlechterung führt, die schwer zu diagnostizieren ist, da Standard-Abfragepläne ihre Nutzung nicht anzeigen.

Wichtige Details

  • Ungültige Indizes werden erstellt, wenn ein paralleler Build fehlschlägt, nachdem die anfängliche Katalogtransaktion committet wurde.
  • Zwei Flags bestimmen die Kosten: indisvalid = false bedeutet, dass der Index für Abfragen nutzlos ist, während indisready = false bedeutet, dass Schreibvorgänge ihn ignorieren.
  • Wenn indisready true ist, aktualisiert die Datenbank den Index bei jedem Insert oder Update weiter, was CPU und Speicher verschwendet.
  • Eindeutigkeitsbeschränkungen aus fehlgeschlagenen Builds können weiterhin Regeln zur Durchsetzung der Eindeutigkeit erzwingen, selbst wenn der Planner den Index nie für Lookups verwendet.
  • Migrationswerkzeuge wie Rails, Knex und golang-migrate umschließen Befehle oft mit Transaktionen, was dazu führt, dass CONCURRENTLY-Operationen sofort fehlschlagen.
  • Die Bereinigung erfordert DROP INDEX CONCURRENTLY, das außerhalb eines Transaktionsblocks ausgeführt werden muss.

Hintergrund

Um zu verstehen, warum dies passiert, hilft es zu wissen, dass ein paralleler Index-Build keine einzelne atomare Transaktion ist. Er erfolgt in drei verschiedenen Phasen über separate Transaktionen hinweg. Erstens wird der Index im Systemkatalog als ungültig registriert. Zweitens scannt die Datenbank die vorhandenen Tabellendaten, um den Index zu füllen. Drittens wartet sie darauf, dass alle alten Transaktionen beendet sind, bevor der Index als bereit zur Verwendung markiert wird.

Wenn der Prozess nach der ersten Phase, aber vor der letzten Phase unterbrochen wird, bleibt der Index im Katalog erhalten. Je nachdem, wann genau der Fehler aufgetreten ist, kann sich der Index in einem Zustand befinden, in dem er nicht für Abfragen bereit ist, aber dennoch von neuen Schreibvorgängen aktualisiert wird. Dies ist besonders tückisch, weil der Index scheinbar existiert, aber keinen Lesenvorteil bietet, während er die Schreibperformance aktiv beeinträchtigt.

Warum es wichtig ist

Für Teams, die ihre eigene Software betreiben, ist eine stille Leistungsverschlechterung ein großes operatives Risiko. Ein ungültiger Index, der noch gepflegt wird, verbraucht Speicherplatz, erhöht das Volumen des Write-Ahead Logs (WAL) und verhindert Heap-Only Tuple (HOT)-Updates. Dies führt zu höheren Speicherkosten und langsameren Anwendungsantwortzeiten bei der Datenaufnahme. Da der Index vom Query Planner ignoriert wird, melden Standard-Monitoring-Tools, die sich auf die Leseperformance konzentrieren, das Problem möglicherweise nicht.

Darüber hinaus können automatisierte Deployment-Pipelines das Problem verschlimmern. Wenn ein Coding-Agent oder CI-Skript eine fehlgeschlagene Migration mit IF NOT EXISTS wiederholt, verbirgt es effektiv den Fehler. Das Deployment gelingt, aber die Datenbank häuft technische Schulden in Form defekter Indizes an. Im Laufe der Zeit können mehrere fehlgeschlagene Versuche zu mehreren ungültigen Indizes auf einer einzigen Tabelle führen, was den Schreib-Overhead kumulativ erhöht und zukünftige Wartungsarbeiten komplexer macht.

Was Sie tun können

  • Fragen Sie regelmäßig den Systemkatalog pg_index ab, um Einträge zu finden, bei denen indisvalid false ist.
  • Prüfen Sie das Flag indisready, um festzustellen, ob der ungültige Index weiterhin Schreibressourcen verbraucht.
  • Verwenden Sie DROP INDEX CONCURRENTLY, um ungültige Indizes zu entfernen, ohne die Tabelle für Lese- oder Schreibzugriffe zu sperren.
  • Stellen Sie sicher, dass Migrationswerkzeuge so konfiguriert sind, dass die Transaktionsumschließung für parallele Index-Operationen deaktiviert ist.
  • Vermeiden Sie die Verwendung von IF NOT EXISTS bei Wiederholungen; prüfen Sie stattdessen die Gültigkeit und löschen Sie den defekten Index zuerst.
  • Fügen Sie eine Post-Deployment-Prüfung hinzu, die eine Ausnahme auslöst, wenn noch ungültige Indizes in der Datenbank vorhanden sind.

Weitere News

Alle News