Späť na blog

Pasca hot_standby_feedback: ako opravíte repliku a pomaly zabijete primár

Toto som videl ako “pomalý výpadok”:

  1. Read replica ruší query kvôli recovery konfliktom.
  2. Niekto zapne hot_standby_feedback=on ako fix.
  3. Rušenie query prestane. Všetci sú spokojní.
  4. O týždeň je primár pomalší, disk rastie, autovacuum nestíha.
  5. Príde latency incident, ktorý vyzerá nesúvisiaco… ale nie je.

Toto je runbook na túto pascu: ako dokázať, že hot_standby_feedback vám drží xmin horizon, prečo to vytvára bloat a čo v produkcii mením tak, aby replika bola užitočná bez toho, aby ticho ničila primár.

Testované na: PostgreSQL 15–17, physical streaming replication, mix OLTP + “niekto pustil analytiku na replike”.

Incident (anonymizovaný)

Mali sme vyťažený primár a jednu repliku na dashboard query. Počas peaku replika často rušila dlhšie query (recovery conflicts). Prišiel “fix”:

  • hot_standby_feedback = on

Rušenie query prestalo, ale ~10 dní neskôr:

  • disk na primári rástol
  • index bloat sa zväčšoval
  • p95 latencia query na primári driftovala hore
  • autovacuum bežal skoro stále, ale nikdy “nevyhrával”

Blast radius: degradácia primára, paging na latenciu (nie na replikáciu).

Constraint: nemohli sme zo dňa na deň zakázať analytické query. Potrebovali sme plán, ktorý udrží read workload funkčný bez toho, aby primár skončil ako bloat farma.

Timeline

  • T-0: sťažnosti na rušenie query na replike.
  • T+1h: hot_standby_feedback=on; conflicts padnú.
  • T+7d: primár disk trenduje hore; vacuum aktivita rastie.
  • T+10d: latency incident na primári; autovacuum je pozadu.
  • T+11d: korelujeme bloat s backend_xmin držaným replikou.
  • T+12d: mitigácia: vypnúť feedback na “serving” replike, vynútiť timeouts, pridať dedikovanú analytics repliku s guardrails.
  • T+14d: bloat prestane rásť; latencia primára sa zotaví.

Mechanizmus: prečo hot_standby_feedback vytvára bloat

Konflikty vznikajú, lebo vacuum chce čistiť a replika chce snapshot

Standby vykonáva query nad snapshotom, zatiaľ čo replayuje WAL. Replay niekedy potrebuje odstrániť row verzie/locky, ktoré query ešte potrebuje → konflikt → standby zruší query (alebo odloží replay).

hot_standby_feedback otočí tradeoff

Keď je hot_standby_feedback zapnutý, replika posiela primáru xmin horizon. Primár potom vynechá vacuum cleanup, ktorý by rozbil snapshot na replike.

To zníži rušenie query, ale primár platí:

  • dead tuples žijú dlhšie
  • indexy držia referencie dlhšie
  • autovacuum nedokáže reclaimovať priestor
  • bloat rastie

Pri dlhých query alebo vysokom churne je to stabilný “bloat pump”.

Runbook: diagnostika xmin pinningu a bloat tlaku

Čo skontrolovať ako prvé

1) Ruší replika query kvôli konfliktom?
Na replike:

SELECT
  datname,
  confl_snapshot,
  confl_lock,
  confl_bufferpin,
  confl_deadlock
FROM pg_stat_database_conflicts
ORDER BY (confl_snapshot + confl_lock + confl_bufferpin + confl_deadlock) DESC;

Ak rastie confl_snapshot, problém sú dlhé snapshoty.

2) Drží replika primár späť?
Na primári:

SELECT
  application_name,
  state,
  sync_state,
  write_lag,
  flush_lag,
  replay_lag,
  backend_xmin
FROM pg_stat_replication
ORDER BY application_name;

Ak je backend_xmin non-null a dlhodobo “starý”, replika pinne cleanup.

3) Rastú dead tuples a veľkosť tabuliek?

SELECT
  schemaname,
  relname,
  n_live_tup,
  n_dead_tup,
  round(100.0 * n_dead_tup / (n_live_tup + n_dead_tup + 1), 2) AS dead_pct
FROM pg_stat_all_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

A veľkosti:

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

Ako potvrdiť hypotézu

“Smoking gun” pattern:

  • dlhé query na replike
  • hot_standby_feedback=on
  • backend_xmin je držané replikou
  • dead tuples rastú a vacuum ich nevie zraziť
  • primárna latencia driftuje hore

Ak sedí všetko, nehádate. Platíte za feedback.

Mitigácie: bezpečné vs rizikové

Bezpečné mitigácie

  1. Timeouty na replike Dashboardy zvyčajne nepotrebujú 30-minútové query.
ALTER DATABASE appdb SET statement_timeout = '30s';
  1. Vypnúť feedback na replike, ktorá nesmie škodiť primáru hot_standby_feedback vypnúť na “serving” replike.

  2. Dedikovaná analytics replika (voliteľné) Ak potrebujete dlhé query, dajte tomu vlastnú repliku a cenu budgetujte.

  3. Použiť max_standby_streaming_delay vedome Radšej povoliť replike trochu zaostávať, než pinovať primár donekonečna (bolesť sa presunie do staleness).

Rizikové mitigácie

  • zapnúť hot_standby_feedback na všetkých replikách
  • VACUUM FULL počas peaku (locky + rewrite)
  • agresívny manuálny vacuum “všade” (IO a latency spikes)

Čo sme zmenili (konkrétne)

1) Prestali sme sa tváriť, že replika je “free analytika”

Rozdelili sme repliky podľa roly:

  • Replika A (serving reads, bezpečná pre primár): feedback off, strict timeouts
  • Replika B (analytics): feedback on, ale guardrails + očakávaná cena

2) Konfig zmeny (ilustratívne)

Replika A:

hot_standby_feedback = off
statement_timeout = 30s

Replika B (analytics):

hot_standby_feedback = on
statement_timeout = 2min

3) Autovacuum tuning na najhoršie tabuľky

Per-table tuning (nie globálne kladivo):

ALTER TABLE public.events SET (
  autovacuum_vacuum_scale_factor = 0.02,
  autovacuum_vacuum_threshold = 5000
);

Ako verifikovať

  1. backend_xmin už nie je pinovaný Na primári:
SELECT application_name, backend_xmin FROM pg_stat_replication;

Očakávanie:

  • Replika A už nedrží xmin
  • Replika B môže držať, ale vedome a budgetovane
  1. Dead tuples klesajú (alebo aspoň prestanú explodovať) Sledujte pg_stat_all_tables v čase.

  2. Primárna latencia sa zotaví

  • p95 query time klesne
  • autovacuum vie reclaimovať priestor
  1. Konflikty na replike sa vrátia, ale v rámci politiky Občasné zrušenie dlhého query na Replike A je OK. Alternatíva je degradovať primár.

Prevencia / guardrails

  • Kontrakt roly repliky
    • serving replika nesmie dlho pinovať xmin
    • analytics replika musí mať explicitné budgety + timeouts
  • Bloat budget
    • alert na dead tuple ratio pre top tabuľky
    • alert na rýchly rast tabuliek/indexov
  • Conflict budget
    • rušenie query je akceptovateľné do N/deň na serving replike
  • Runbook
    • “Ak rastú konflikty: skracuj query, nezapínaj feedback naslepo”

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. "Pasca hot_standby_feedback: ako opravíte repliku a pomaly zabijete primár". https://www.michal-drozd.com/sk/blog/postgresql-hot-standby-feedback-bloat-pasca/ (Publikované 12. decembra 2025).