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_timezaostá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
- Logical Replication Slot WAL Bloat: Keď Subscribery Odídu Offline
- PostgreSQL Replication Slot Bloat: Ako Jeden Neaktívny Slot Naplnil 500GB Disk
- pg_waldump WAL Forenzika: Rekonštrukcia Čo Sa Stalo s Tvojimi Dátami
- PostgreSQL Idle in Transaction: Núdzový Playbook pre Zaseknuté Spojenia
- Zero-Downtime PostgreSQL Migrations: Expand/Contract, Backfill a Rollback Strategies
- Transactional Outbox: Ako vyriešiť Dual Write problém bez 2PC
- PostgreSQL Serialization Failures: Viac ako len ‘Retry’
Súvisiace články
PostgreSQL XID wraparound: núdzový playbook pre vacuum freeze v incidente
PostgreSQL môže prejsť do read-only pri XID wraparound. Núdzový playbook: nájsť najstaršie tabuľky, odblokovať vacuum freeze a prevencia do budúcna.
Pasca hot_standby_feedback: ako opravíte repliku a pomaly zabijete primár
hot_standby_feedback zastaví rušenie query na replike, ale vie nafúknuť primár v dňoch. Diagnostika xmin pinningu, bezpečné mitigácie a guardrails.
Logical Replication Slot WAL Bloat: Keď Subscribery Odídu Offline
Disk sa plní WAL súbormi. Príčina: logical replication slot consumer odišiel offline a PostgreSQL drží všetok WAL odvtedy pretože by mohol byť potrebný.
PostgreSQL Read Replica Konflikty: Prečo sa vaše dotazy rušia
Dotazy na read replikách zlyhávajú s 'canceling statement due to conflict with recovery'. Riešenie závisí od toho, ktorý z 5 typov konfliktov máte - tu je návod ako diagnostikovať a vyriešiť každý z nich.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.