VACUUM, Autovacuum und Tabellen-Bloat
PostgreSQL aktualisiert eine Zeile nie an Ort und Stelle. Jedes UPDATE schreibt eine neue Zeilenversion und markiert die alte als tot; jedes DELETE markiert nur. Der Platz wird später asynchron von VACUUM zurückgewonnen. Wenn diese Schleife funktioniert, denken Sie nie daran. Wenn sie hinterherhinkt, wachsen Tabellen und Indexe stillschweigend — das ist Bloat — und jede Abfrage bezahlt dafür.
Warum tote Zeilen überhaupt existieren: MVCC
Unter MVCC (Multi-Version Concurrency Control) blockieren Leser nie Schreiber und umgekehrt, weil jede Transaktion einen konsistenten Snapshot sieht: alte Zeilenversionen werden so lange aufbewahrt, wie irgendeine Transaktion sie noch benötigen könnte. In dem Moment, in dem keine aktive Transaktion eine tote Version mehr sehen kann, wird sie zu Müll — aber PostgreSQL gewinnt sie nicht inline zurück. Das ist VACUUMs Aufgabe.
Zwei unmittelbare Konsequenzen:
- Eine
UPDATE-lastige Last schreibt ungefähr so viel wie eineINSERT-lastige, plus Index-Churn. - Eine einzige lang laufende Transaktion hält die Bereinigung für die gesamte Datenbank zurück — VACUUM kann keine Zeilenversion entfernen, die der Snapshot dieser Transaktion noch sehen könnte, unabhängig davon, welche Tabellen sie berührte. Eine vergessene
idle in transaction-Session am Freitag bedeutet eine aufgeblähte Datenbank am Montag.
Was VACUUM tatsächlich tut (und nicht tut)
- Einfaches
VACUUMsucht nach toten Tupeln, macht ihren Platz für zukünftige Schreibvorgänge in derselben Tabelle wiederverwendbar, aktualisiert die Free Space Map und die Visibility Map und friert alte Tupel ein, um Transaktions-ID-Wraparound zu verhindern. Es läuft neben dem normalen Verkehr. - Es verkleinert die Datei auf der Festplatte nicht (außer im Sonderfall vollständig leerer Seiten ganz am Ende der Tabelle). Eine Tabelle, die einmal auf 50 GB anwuchs, bleibt 50 GB auf der Festplatte, selbst wenn sie innen zu 90 % leer ist.
VACUUM FULLschreibt die Tabelle in eine neue kompakte Datei um und gibt Platz an das Betriebssystem zurück — nimmt aber während des gesamten Umschreibens eineACCESS EXCLUSIVE-Sperre. Bei einer großen, viel genutzten Tabelle ist das ein Ausfall; schauen Sie sich pg_repack für Online-Rebuilds an.ANALYZE(oft zusammen ausgeführt) aktualisiert die Planer-Statistiken — eine andere Aufgabe, die ebenfalls wichtig ist, siehe EXPLAIN ANALYZE lesen.
Wie Autovacuum entscheidet, zu laufen
Der Autovacuum-Launcher prüft jede Tabelle regelmäßig gegen einen Schwellenwert:
vacuum when: n_dead_tup > autovacuum_vacuum_threshold
+ autovacuum_vacuum_scale_factor × reltuples
-- defaults: 50 + 0.20 × reltuples (20% of the table)
Der voreingestellte 20-%-Scale-Factor ist für kleine Tabellen in Ordnung und schrecklich für große: eine Tabelle mit 100 Millionen Zeilen sammelt 20 Millionen tote Zeilen an, bevor Autovacuum überhaupt startet. Setzen Sie für große, heiße Tabellen einen Override pro Tabelle:
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_vacuum_threshold = 10000
);
Autovacuum wird außerdem bewusst durch kostenbasierte Verzögerungen gedrosselt (autovacuum_vacuum_cost_delay / autovacuum_vacuum_cost_limit), damit es I/O nicht sättigt. Auf moderner Hardware sind die Voreinstellungen konservativ; wenn Autovacuum ständig läuft, aber nie aufholt, ist das Erhöhen von autovacuum_vacuum_cost_limit meist der erste Hebel.
Bloat erkennen, bevor es wehtut
SELECT relname,
n_live_tup,
n_dead_tup,
last_autovacuum,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
n_dead_tupgroß und wachsend, mitlast_autovacuumalt oder NULL → Autovacuum kommt nicht hinterher (oder eine lange Transaktion pinnt den Horizont: prüfen Siepg_stat_activityauf altesxact_start).- Abfrageseitiges Symptom: Buffer-Zahlen im Missverhältnis zu den zurückgegebenen Zeilen in
EXPLAIN (ANALYZE, BUFFERS)— 40.000 Seiten für 500 Zeilen zu lesen bedeutet, überwiegend toten Raum zu scannen. - Größenseitige Prüfung: vergleichen Sie
pg_total_relation_size()über die Zeit oder nutzen Sie die Erweiterungpgstattuplefür einen exakten Prozentsatz des toten Raums bei einer verdächtigen Tabelle.
HOT-Updates: das kostenlose Mittagessen, für das sich Engineering lohnt
Wenn ein UPDATE nur Spalten ändert, die nicht indexiert sind, und die neue Version auf dieselbe Seite passt, führt PostgreSQL ein HOT-Update (Heap-Only-Tuple) durch: es werden überhaupt keine Index-Einträge geschrieben, und die tote Version kann günstig innerhalb der Seite bereinigt werden. Deshalb kann das Hinzufügen von „nur einem weiteren Index" auf einer häufig aktualisierten Spalte die Schreib-Performance ruinieren — es macht aus jedem HOT-Update ein vollständiges. Ein niedrigerer fillfactor (z. B. 80–90 für aktualisierungslastige Tabellen) lässt Platz auf jeder Seite und erhöht die HOT-Rate.
Faustregeln
- Deaktivieren Sie Autovacuum nie. Wenn es wehtut, ist es untertunt, nicht unnötig — und es schützt Sie außerdem vor Transaktions-ID-Wraparound, einem die Datenbank stoppenden Ereignis.
- Beenden oder Timeout untätiger Transaktionen:
idle_in_transaction_session_timeoutist eine günstige Versicherung. - Tunen Sie pro Tabelle, nicht global: eine Handvoll heißer Tabellen verursacht meist den größten Teil des Schmerzes.
- Bloat, den Sie bereits haben, verschwindet nicht von selbst: einfaches VACUUM stoppt das Wachstum; das Zurückgewinnen von Festplattenplatz braucht
VACUUM FULLoder einen Online-Rebuild.
EXPLAIN (ANALYZE, BUFFERS) aus und werfen Sie die Ausgabe in den EXPLAIN Visualizer, um zu sehen, welcher Knoten das übermäßige Lesen verursacht.
