Späť na blog

PostgreSQL logical replication lag: veľké transakcie a reorder buffer spilly

Symptómy sú až podozrivo pokojné:

  • subscriber beží, CPU je mierne
  • subscription status vyzerá “connected”
  • a napriek tomu lag rastie z minút na hodiny
  • apply sa tvári zaseknutý na jednom bode v čase
  • downstream systémy timeoutujú, lebo závisia na čerstvých replikovaných dátach

V incidentoch ľudia často riešia “sieť” alebo “subscriber výkon”. Reálny root cause je často jednoduchší:

Jedna obrovská transakcia na publishery pripne logical replication, kým necommitne.

Testované na: PostgreSQL 15–17, logical replication cez pgoutput, subscribery v Kubernetes aj VM, backfily a migračné workloady.

Incident (anonymizovaný)

Pustili sme backfill, ktorý updateuje desiatky miliónov riadkov. Bol napísaný ako “jedna transakcia pre safety”.

V produkcii:

  • backfill bežal približne hodinu
  • lag na subscriberi rástol lineárne
  • apply worker vyzeral živý, ale nedobiehal
  • logical slot začal držať viac WAL než sme čakali

Incident nebol “logical replication je pomalá”. Bol “logical replication je transakčná”: zmeny sú viditeľné až po commit, a veľké transakcie spravia wall-clock lag aj keď je všetko healthy.

Constraint: viac služieb záviselo na replike pre read path. Čakať hodinu na commit nebolo prijateľné. Potrebovali sme runbook a prevenciu, ktorá spraví giant transakcie prakticky nemožné.

Timeline

  • T-0: lag alert; downstream číta stale dáta.
  • T+10m: subscription vyzerá connected, ale latest_end_time zaostáva.
  • T+20m: nájdeme long transakciu na publishery.
  • T+30m: mitigácia: abort transakcie; nahradiť chunkovaním.
  • T+60m: lag začne klesať; apply dobehne.
  • T+1d: guardrails: bounded backfily, decoding budgety, alerty na apply delay a slot lag.

Mechanizmus: prečo veľké transakcie zmrazia progres

Logical replication aplikuje zmeny v poradí transakcií. Pri veľkej transakcii:

  • publisher dekóduje zmeny z WAL
  • subscriber ich prijme
  • subscriber ich nevie spraviť viditeľné, kým nepríde commit

Tvoj “lag” je potom primárne dĺžka tej transakcie, nie sieť alebo bandwidth.

Reorder buffer spill spraví z CPU problému IO problém

Decoding potrebuje pamäť. Keď sa zmeny v transakcii nezmestia do budgetu, Postgres použije reorder buffer a vie spillnúť na disk. Potom to vyzerá, že apply je “stuck”, aj keď reálne robí IO prácu.

Preto môže byť systém mesiace OK a potom “zrazu” lag: jedna migrácia zmení tvar transakcií.

Runbook: nájdi vinníka a zotav sa bezpečne

1) Zmeraj apply delay na subscriberi

SELECT
  subname,
  status,
  received_lsn,
  latest_end_lsn,
  latest_end_time,
  last_msg_receipt_time
FROM pg_stat_subscription
ORDER BY latest_end_time NULLS LAST;

Ak je latest_end_time ďaleko za “teraz”, máš apply delay.

2) Zmeraj slot lag na publishery

SELECT
  slot_name,
  active,
  restart_lsn,
  confirmed_flush_lsn,
  pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS confirmed_lag
FROM pg_replication_slots
WHERE slot_type = 'logical'
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) DESC;

Toto povie “ako zlé” a či sa slot hýbe.

3) Nájsť long transakciu na publishery

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

Väčšinou je to jasné: backfill, migrácia alebo bulk update.

4) Potvrdiť, že je to pin

Potvrdím, keď:

  • existuje long-running transakcia
  • lag rastie počas nej
  • lag začne klesať krátko po commit alebo abort

Najrýchlejší dôkaz je operatívny: stopni transakciu a sleduj, či lag začne klesať.

Bezpečné mitigácie

1) Abort veľkej transakcie (zastav pin)

Ak transakcia ešte necommitla, abort je najrýchlejší spôsob ako vrátiť “freshness”.

Trade-off je reálny:

  • necháš ju bežať: replika ostane stale a slot drží WAL
  • abortneš: job re-runneš neskôr, ale downstream sa zotaví teraz

2) Nahradiť giant transakciu bounded batchmi

Backfill má byť:

  • chunkovaný
  • commit po každom batchi
  • resumable
  • s progress trackingom

3) Tunenie decoding budgetov

Ak často replikuješ veľké zmeny, tuning decoding budgetov (napr. logical_decoding_work_mem) rob vedome:

  • nízko: reorder buffer spill na disk, apply sa stane IO-bound
  • vysoko: memory tlak pri concurrency

Pointa: je to budget knob a musíš ho testovať pri realistickom loade.

Rizikové mitigácie

  • vypnúť subscription aby “prestal lag” (zastavíš apply úplne a WAL retention sa môže zhoršiť)
  • dropnúť a znovu vytvoriť subscription/slot (často resync a riziko straty)
  • slepo zvyšovať pamäť a timeouty bez bounded transakcií (incident sa vráti)

Čo sme zmenili (konkrétne)

1) “Max transakcia budget” pre backfily

Spravili sme to enforceable:

  • cap na rows per transakciu
  • time budget (statement_timeout pre backfill rolu)
  • progress tracking a resume logika

Príklad:

ALTER ROLE backfill SET statement_timeout = '5min';

2) Alerty skôr, než sa z toho stane aj WAL incident

Alertujeme:

  • apply delay nad threshold
  • slot confirmed lag growth rate
  • long-running transakcie (najmä pre backfill roly)

Ako verifikovať

Na subscriberi sa latest_end_time musí posúvať k “teraz”:

SELECT subname, latest_end_time, last_msg_receipt_time
FROM pg_stat_subscription;

Na publishery sa slot lag stabilizuje:

SELECT
  slot_name,
  pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS confirmed_lag
FROM pg_replication_slots
WHERE slot_type='logical';

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 logical replication lag: veľké transakcie a reorder buffer spilly". https://www.michal-drozd.com/sk/blog/postgresql-logical-replication-lag-velke-transakcie/ (Publikované 1. januára 2026).