Späť na blog

PostgreSQL XID wraparound: núdzový playbook pre vacuum freeze v incidente

Vidíte chybu typu “database is not accepting commands to avoid wraparound data loss” (alebo sa k tomu nebezpečne blížite). Prvá reakcia je pustiť niečo ťažké a dúfať.

Takto si viete zmeniť riešiteľnú situáciu na vlastný výpadok.

Tento playbook je pre 3:00 ráno, keď:

  • XID age je vysoké
  • autovacuum nestíha
  • máte veľké tabuľky
  • a potrebujete sa zachrániť bez toho, aby ste zhoršili IO a locking

Testované na: PostgreSQL 15–17 (self-managed + managed ponuky s rovnakou semantikou), multi‑GB až multi‑TB dataset, mix OLTP workloadov.

Incident (anonymizovaný)

SaaS cluster bežal na multi-tenant Postgrese s vysokým write volume. Autovacuum bol nastavený na throughput, ale tím mal dve slepé miesta:

  1. dlhé “idle in transaction” sessions blokovali vacuum
  2. veľké append-heavy tabuľky rýchlo “stárli”

Monitoring zachytil rastúce age(datfrozenxid) neskoro. Keď incident začal, “normálny” vacuum nestíhal a hrozilo prekročenie wraparound bezpečnostnej hranice.

Blast radius: produkčná DB riskovala forced read-only / shutdown správanie.

Constraint: minimálne downtime; vyhnúť sa ťažkým lockom; udržať repliku zdravú.

Timeline

  • T-0: alert: XID age sa blíži ku kritickému thresholdu.
  • T+10m: identifikácia najhorších DB podľa XID age.
  • T+20m: identifikácia najhorších tabuliek podľa age(relfrozenxid).
  • T+30m: nájdenie long-running tx a idle-in-tx, ktoré blokujú vacuum.
  • T+40m: ukončenie blockerov; spustenie cieleného VACUUM (FREEZE) na najhorších tabuľkách.
  • T+2h: XID age začína klesať; autovacuum dobieha; incident končí.
  • T+1d: guardrails: alerty, timeouty, per-table autovacuum freeze tuning.

Mechanizmus: čo wraparound naozaj je

Postgres prideľuje transaction ID (XID). XID sa časom wrapne. Ak staré row verzie nie sú “frozen”, wraparound spôsobí nejednoznačnosť a riziko nekonzistencie dát.

Prevencia je pravidelný vacuum, ktorý freeze-ne staré tuplíky. Keď sa systém príliš priblíži k wraparound, Postgres zvolí správnosť pred dostupnosťou.

Ak freezing nevie napredovať, Postgres bude chrániť correctness aj za cenu dostupnosti.

Prečo sa vacuum freeze zasekne

Aj keď autovacuum beží, freezing sa vie zastaviť, ak:

  • long-running transakcie držia staré xmin
  • idle-in-tx drží snapshot otvorený
  • vysoký write churn tvorí nekonečný backlog
  • IO budget je príliš nízky (autovacuum nestíha)

Runbook: núdzová reakcia

Čo skontrolovať ako prvé (10 min)

1) Ako blízko sme?

SELECT
  datname,
  age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;

Pozrite aj thresholdy:

SELECT
  current_setting('autovacuum_freeze_max_age') AS freeze_max_age,
  current_setting('autovacuum_freeze_table_age') AS freeze_table_age;

Mentálny model: nie “absolútne číslo”, ale “vzdialenosť k vášmu max”.

2) Najhoršie tabuľky podľa relfrozenxid

SELECT
  n.nspname,
  c.relname,
  pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
  age(c.relfrozenxid) AS xid_age
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','m')
ORDER BY age(c.relfrozenxid) DESC
LIMIT 20;

3) Blokujú nás long-running transakcie?

SELECT
  pid,
  usename,
  state,
  now() - xact_start AS xact_age,
  wait_event_type,
  wait_event,
  left(query, 120) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start ASC
LIMIT 20;

“idle in transaction” s veľkým xact_age je typický blocker.

Ako potvrdiť hypotézu

A. Vacuum progress

SELECT
  pid,
  datname,
  relid::regclass AS relation,
  phase,
  heap_blks_total,
  heap_blks_scanned,
  heap_blks_vacuumed,
  index_vacuum_count
FROM pg_stat_progress_vacuum;

Ak progress stojí a máte long transactions, pravdepodobne máte snapshot blocker.

Bezpečné mitigácie (v poradí)

  1. Zastaviť blockerov
  • ukončiť idle-in-tx sessions
  • potom ukončiť skutočne runaway long tx (opatrne)
SELECT pg_terminate_backend(<pid>);
  1. Cielené VACUUM (FREEZE) na najhoršie tabuľky
VACUUM (FREEZE, VERBOSE) public.big_table;
  1. Dočasne zvýšiť autovacuum kapacitu
  • viac workerov
  • rozumné zvýšenie maintenance memory
  • uistiť sa, že disk/IO to dá
  1. vacuumdb paralelne (off-peak)
vacuumdb --all --freeze --jobs=4

Rizikové mitigácie

  1. VACUUM FULL
  • silnejšie locky, rewrite tabuľky → často horšia dostupnosť
  1. Vypnúť autovacuum
  • prehráte preteky

Čo sme zmenili (konkrétne)

1) Timeouty proti snapshot blockerom

Na úrovni databázy:

ALTER DATABASE appdb SET idle_in_transaction_session_timeout = '60s';
ALTER DATABASE appdb SET statement_timeout = '30s';

Hodnoty prispôsobte workloadu; pointa je: žiadne “idle in transaction navždy”.

2) Autovacuum tuning na freeze debt

Diff (ilustratívny postgresql.conf):

-autovacuum_max_workers = 3
-autovacuum_naptime = 60s
+autovacuum_max_workers = 6
+autovacuum_naptime = 20s

Per-table nastavenia pre najhoršie append-heavy tabuľky:

ALTER TABLE public.big_table SET (
  autovacuum_vacuum_scale_factor = 0.02,
  autovacuum_analyze_scale_factor = 0.05
);

Per-table tuning je často bezpečnejší než agresívny cluster-wide tuning.

3) Wraparound alerty ako SLO

Alertujeme:

  • max(age(relfrozenxid)) pre top tabuľky
  • age(datfrozenxid) per DB
  • idle-in-transaction sessions staršie než X

Ako verifikovať (merateľné)

  1. XID age klesá:
SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC;
  1. Najhoršie tabuľky sa zlepšujú:
SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind='r' ORDER BY 2 DESC LIMIT 10;
  1. Vacuum progress ide dopredu:
SELECT * FROM pg_stat_progress_vacuum;
  1. Dopad na aplikáciu je kontrolovaný:
  • p95 DB latencia v akceptovateľnom pásme
  • replication lag bounded (ak relevantné)

Prevencia / guardrails

Kontrakty

  • Žiadny “idle in transaction” dlhší než X sekúnd.
  • Wraparound budget: alert, keď XID age spotrebuje > Y% z vášho freeze max.
  • Veľké tabuľky musia mať explicitne reviewnuté autovacuum nastavenia.

Checklist alertov

  • age(datfrozenxid) rastie príliš rýchlo
  • najstaršie relfrozenxid nad thresholdom
  • idle-in-tx sessions nad limitom (počet + vek)
  • vacuum nepostupuje (stuck phase)

Súvisiace čítanie

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "PostgreSQL XID wraparound: núdzový playbook pre vacuum freeze v incidente". https://www.michal-drozd.com/sk/blog/postgresql-xid-wraparound-nudzovy-playbook/ (Publikované 16. decembra 2025).