TL;DR — Kurzzusammenfassung

PostgreSQL VACUUM und Autovacuum richtig tunen: Table Bloat beseitigen, XID-Wraparound verhindern und die Datenbankperformance in der Produktion maximieren.

PostgreSQL VACUUM ist eine der am meisten missverstandenen und gleichzeitig kritischsten Wartungsoperationen in jedem PostgreSQL-Deployment. Ohne ordentliches Tuning sammeln Tabellen still und leise tote Tupel an, die den Speicher aufblähen, die Abfrageleistung verschlechtern und im schlimmsten Fall einen katastrophalen XID-Wraparound auslösen, der die gesamte Datenbank offline nimmt. Dieser Leitfaden deckt alles ab, was Sie benötigen, um Autovacuum korrekt zu konfigurieren und Produktionsdatenbanken gesund zu halten.

Voraussetzungen

  • PostgreSQL 13 oder höher (die meisten Beispiele gelten ab PostgreSQL 10+)
  • psql oder ein SQL-Client mit DBA-Zugang
  • Möglichkeit, postgresql.conf zu bearbeiten oder ALTER SYSTEM zu verwenden
  • Grundlegende Vertrautheit mit PostgreSQL-Speicherkonzepten

Warum VACUUM existiert: MVCC und tote Tupel

PostgreSQL verwendet Multi-Version Concurrency Control (MVCC), um gleichzeitige Lese- und Schreibzugriffe ohne Sperren zu verwalten. Jedes UPDATE oder DELETE modifiziert Daten nicht an Ort und Stelle — stattdessen markiert es die alte Zeilenversion als tot und schreibt eine neue Version. Das macht Lesezugriffe nicht-blockierend, hinterlässt aber tote Tupel: Zeilenversionen, die für keine Transaktion mehr sichtbar sind, aber weiterhin Speicherplatz belegen.

Mit der Zeit verursachen tote Tupel drei ernste Probleme:

  1. Table Bloat: Die physische Tabellendatei wächst, selbst wenn die logische Zeilenanzahl konstant bleibt. Eine Tabelle mit 10 Millionen aktiven Zeilen könnte den Platz von 50 Millionen Zeilen auf der Festplatte belegen.
  2. Index Bloat: B-Tree-Indizes sammeln Zeiger auf tote Tupel an und erhöhen deren Größe und Scan-Kosten.
  3. XID-Wraparound: PostgreSQL verwendet 32-Bit-Transaktions-IDs. Nach etwa 2 Milliarden Transaktionen wickeln sich die IDs um. PostgreSQL erzwingt einen datenbankweiten Notstopp, wenn VACUUM alte Transaktionen nicht rechtzeitig einfrieren kann.

VACUUM löst alle drei Probleme, indem es Tabellenseiten scannt, tote Tupel als wiederverwendbaren Raum markiert und den Einfrierungshorizont für alte Transaktionen vorschiebt.

VACUUM-Typen im Vergleich

BefehlSperreSpeicherrückgewinnungAnwendungsfall
VACUUMKeine (teilt mit Lese-/Schreibzugriffen)Markiert Platz zur Wiederverwendung, verkleinert Datei nichtRoutinewartung, Autovacuum
VACUUM ANALYZEKeineWie oben + aktualisiert Planer-StatistikenNach Masseninserts/-löschungen
VACUUM FULLExklusiv (Tabelle gesperrt)Schreibt Tabelle neu, gibt Platz an OS zurückEinmalige Bloat-Bereinigung
VACUUM FREEZEKeineErzwingt Einfrieren aller alten XIDsWraparound-Prävention

Verwenden Sie VACUUM FULL nur als einmaligen Bereinigungsschritt für stark aufgeblähte Tabellen — es hält eine exklusive Sperre, die alle Lese- und Schreibzugriffe für die Dauer blockiert. Für Online-Tabellen-Neuschreibungen verwenden Sie pg_repack.

Der Autovacuum-Daemon: Kernparameter

Autovacuum läuft als Hintergrund-Daemon, der Worker-Prozesse zum automatischen Vacuuming von Tabellen startet. Die Schlüsselparameter in postgresql.conf:

# Wie oft der Launcher nach Arbeit sucht
autovacuum_naptime = 1min

# Maximale gleichzeitige Autovacuum-Worker
autovacuum_max_workers = 3

# Löst Vacuum aus wenn tote Tupel > threshold + scale_factor * n_live_tup
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2       # 20% der Tabelle

# Löst ANALYZE aus wenn geänderte Tupel dies überschreiten
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.1      # 10% der Tabelle

# Friert Zeilen ein, die so viele Transaktionen alt sind
autovacuum_freeze_max_age = 200000000

Der kritische Tuning-Punkt ist autovacuum_vacuum_scale_factor. Der Standardwert 0.2 bedeutet, dass Autovacuum auslöst, wenn 20% der Zeilen einer Tabelle tot sind. Bei einer 100-Zeilen-Tabelle ist das in Ordnung. Bei einer 50-Millionen-Zeilen-Tabelle löst Autovacuum nicht aus, bis 10 Millionen tote Tupel angesammelt sind.

Empfohlene globale Werte für Systeme mit hohem Schreibaufkommen:

autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02
autovacuum_max_workers = 5
autovacuum_naptime = 30s

Tabellen-spezifische Autovacuum-Überschreibungen

Die leistungsfähigste Tuning-Technik ist das Setzen von Autovacuum-Parametern pro Tabelle:

-- Für eine Tabelle mit sehr hohem Schreibaufkommen (z.B. Events-/Audit-Log)
ALTER TABLE ereignisse SET (
    autovacuum_vacuum_scale_factor = 0.01,
    autovacuum_vacuum_threshold = 500,
    autovacuum_analyze_scale_factor = 0.005,
    autovacuum_vacuum_cost_delay = 2
);

-- Für eine nahezu statische Lookup-Tabelle (weniger aggressives Vacuum)
ALTER TABLE laendercodes SET (
    autovacuum_vacuum_scale_factor = 0.5,
    autovacuum_vacuum_threshold = 1000
);

Table Bloat überwachen

Mit pg_stat_user_tables

-- Top 20 Tabellen nach Anzahl toter Tupel
SELECT
    schemaname,
    relname AS tabellenname,
    n_live_tup,
    n_dead_tup,
    round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS pct_tot,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

Tabellen nahe am XID-Wraparound erkennen

SELECT
    schemaname,
    relname,
    age(relfrozenxid) AS xid_alter,
    pg_size_pretty(pg_total_relation_size(oid)) AS tabellengroesse
FROM pg_class
JOIN pg_namespace ON pg_namespace.oid = relnamespace
WHERE relkind = 'r'
ORDER BY age(relfrozenxid) DESC
LIMIT 20;

Jede Tabelle mit xid_alter über 1,5 Milliarden erfordert sofortige Aufmerksamkeit.

Tatsächlichen Bloat mit pgstattuple messen

CREATE EXTENSION IF NOT EXISTS pgstattuple;

SELECT
    dead_tuple_count,
    round(dead_tuple_percent, 1) AS pct_tot,
    free_space,
    round(free_percent, 1) AS pct_frei
FROM pgstattuple('public.bestellungen');

XID-Wraparound-Prävention

Überwachen Sie das Alter auf Datenbankebene:

SELECT
    datname,
    age(datfrozenxid) AS xid_alter,
    pg_size_pretty(pg_database_size(datname)) AS db_groesse
FROM pg_database
ORDER BY age(datfrozenxid) DESC;

Notfallwiederherstellung wenn eine Datenbank nahe am Wraparound ist:

vacuumdb --all --freeze --verbose --analyze

VACUUM FULL vs pg_repack

Für Produktionssysteme, die keine Ausfallzeit tolerieren können, verwenden Sie pg_repack:

sudo apt install postgresql-16-repack
pg_repack -h localhost -U postgres -d meinedb -t bestellungen
pg_repack -h localhost -U postgres -d meinedb --only-indexes -t bestellungen

Häufige Probleme und Lösungen

Autovacuum durch lang laufende Transaktionen abgebrochen: Veraltete Transaktionen suchen:

SELECT pid, now() - xact_start AS dauer, query, state
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY dauer DESC
LIMIT 10;

“Autovacuum: preventing wraparound” in den Logs: Autovacuum ist im Notfallmodus und ignoriert das Cost-Throttling. Diese Vacuum-Prozesse nicht blockieren oder beenden.

Tuning-Referenz nach Datenbankgröße

DB-Größeautovacuum_max_workersautovacuum_vacuum_scale_factorautovacuum_vacuum_cost_limit
< 10 GB3 (Standard)0.05200 (Standard)
10–100 GB4–50.02400
100 GB – 1 TB6–80.01600
> 1 TB8–10 + pro Tabelle0.005 bei aktiven Tabellen800

Datenbankvergleich

FunktionPostgreSQL VACUUMMySQL OPTIMIZE TABLESQL Server Index RebuildOracle Segment Shrink
Benötigte SperrenKeine (regulär), Exklusiv (FULL)ExklusivOnline (Enterprise)Zeilen-exklusiv
Online-Neuschreib-Toolpg_repackpt-online-schema-changeOnline-RebuildSHRINK SPACE COMPACT
AutomatischAutovacuum-DaemonManuell oder Event-SchedulerAutomatische Index-WartungNicht automatisch
Wraparound-RisikoJa (XID)NeinNeinNein
Bloat-Sichtbarkeitpg_stat_user_tables, pgstattupleinformation_schemasys.dm_db_index_physical_statsDBA_SEGMENTS

Zusammenfassung

  • VACUUM gewinnt Platz von toten Tupeln aus MVCC-Updates und -Löschungen zurück; ohne es blähen sich Tabellen auf und XID-Wraparound hält die Datenbank schließlich an.
  • Autovacuum ist die automatische Lösung, benötigt aber Tuning — der Standard autovacuum_vacuum_scale_factor = 0.2 ist für große Tabellen zu hoch.
  • Verwenden Sie tabellen-spezifische Konfiguration mit ALTER TABLE SET für Tabellen mit hohem Schreibaufkommen.
  • Überwachen Sie pg_stat_user_tables und age(datfrozenxid) in pg_database.
  • Verwenden Sie pg_repack statt VACUUM FULL auf Produktionstabellen um Ausfallzeiten zu vermeiden.
  • Halten Sie age(datfrozenxid) deutlich unter 1,5 Milliarden auf allen Datenbanken.

Verwandte Artikel