{"slug": "replication-monitoring-ha-dashboard", "title": "Replication Monitoring — HA Dashboard", "summary": "A developer built a high-availability (HA) dashboard for PostgreSQL streaming replication monitoring. The dashboard aggregates metrics from pg_stat_replication, pg_stat_wal_receiver, and pg_replication_slots to detect failure modes such as replica disconnection, replay lag, and slot WAL pinning before they become critical. It exposes four LSN-based lag metrics (sent, write, flush, replay) and uses time-based lag columns to provide real-time visibility into replication health.", "body_md": "Streaming replication trong Postgres là một tunnel WAL từ primary sang standby: primary chạy một `walsender`\n\ncho mỗi standby, standby chạy một `walreceiver`\n\nnhận WAL và một `startup`\n\nprocess replay. Đau nhất trong vận hành không phải setup — mà là replica *đã ngắt* nhiều giờ mà không ai biết, tới lúc primary chết mới phát hiện HA thực ra là single-node từ tuần trước. HA dashboard là tập metric + alert được rút ra từ `pg_stat_replication`\n\n, `pg_stat_wal_receiver`\n\n, và `pg_replication_slots`\n\nđể ép mọi trạng thái xấu — replica disconnect, replay đứng, slot pin WAL, sync standby biến mất — thành tín hiệu nhìn thấy được trước khi biến thành sự cố.\n\nTrên primary, mỗi standby đang kết nối tạo ra một backend loại `walsender`\n\n— Postgres đọc WAL từ `pg_wal/`\n\n(hoặc từ WAL buffers khi còn nóng) và stream qua replication connection. Trên standby, `walreceiver`\n\nnhận từng WAL record, ghi vào `pg_wal/`\n\nlocal, fsync (tùy `synchronous_commit`\n\n), rồi `startup`\n\nprocess apply record lên shared buffers — đây chính là replay. Bốn LSN xuất hiện trong luồng này và tương ứng với bốn cột trong `pg_stat_replication`\n\n:\n\n`sent_lsn`\n\n— byte cuối cùng primary đã gửi qua socket.`write_lsn`\n\n— byte cuối cùng standby đã `write()`\n\nvào OS page cache.`flush_lsn`\n\n— byte cuối cùng standby đã `fsync`\n\nxuống đĩa.`replay_lsn`\n\n— byte cuối cùng standby đã replay vào shared buffers (dữ liệu đã \"thấy được\" trên standby).Postgres docs quy định `replay_lsn <= flush_lsn <= write_lsn <= sent_lsn <= pg_current_wal_lsn()`\n\n— bốn \"vạch\" này chính là bốn nhịp của lag. Ba cột `write_lag`\n\n, `flush_lag`\n\n, `replay_lag`\n\n(kiểu `interval`\n\n) là *thời gian* mà standby chậm hơn primary tương ứng với ba mốc write/flush/replay — được đo qua feedback message định kỳ từ standby.\n\n```\n-- Trên primary: bức tranh đầy đủ cho một HA dashboard\nSELECT\n  application_name,\n  client_addr,\n  state,                     -- streaming | catchup | startup | backup | stopping\n  sync_state,                -- async | potential | sync | quorum\n  pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)   AS sent_lag_bytes,\n  pg_wal_lsn_diff(pg_current_wal_lsn(), write_lsn)  AS write_lag_bytes,\n  pg_wal_lsn_diff(pg_current_wal_lsn(), flush_lsn)  AS flush_lag_bytes,\n  pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,\n  write_lag, flush_lag, replay_lag,\n  EXTRACT(EPOCH FROM (now() - reply_time))          AS seconds_since_reply\nFROM pg_stat_replication;\n```\n\nTrên standby, `pg_stat_wal_receiver`\n\ncho biết đang kết nối tới primary nào (`conninfo`\n\n, `sender_host`\n\n), `status`\n\n(`streaming`\n\n/`waiting`\n\n/`stopping`\n\n), `last_msg_send_time`\n\n, `last_msg_receipt_time`\n\n, và `latest_end_lsn`\n\n. Đây là view duy nhất chứng minh standby *thực sự* đang nghe primary — không có row nào trong view này = standby đã ngắt hoặc chưa kết nối lại. `pg_last_wal_replay_lsn()`\n\nvà `pg_last_xact_replay_timestamp()`\n\nlà hai hàm dùng để tính lag theo thời gian: `now() - pg_last_xact_replay_timestamp()`\n\ncho ra \"đọc trên standby chậm bao nhiêu giây so với transaction cuối cùng đã commit trên primary\".\n\nMột cái nữa nằm cạnh: `pg_replication_slots`\n\n. Slot pin `restart_lsn`\n\nlại để primary không recycle WAL segment mà standby chưa consume. Cột `wal_status`\n\n(Postgres 13+) có bốn giá trị `reserved`\n\n/ `extended`\n\n/ `unreserved`\n\n/ `lost`\n\n— trạng thái `lost`\n\nlà khi slot đã bị vượt `max_slot_wal_keep_size`\n\nvà WAL bị invalidate; consumer phải resync từ base backup. `safe_wal_size`\n\ncho biết còn bao nhiêu byte WAL nữa slot có thể tụt trước khi bị lost.\n\n**Failure mode 1: replica ngắt kết nối và không ai biết.** Không có dashboard, cách duy nhất phát hiện là \"một ngày failover và thấy standby đứng ở LSN cũ\". Nguyên nhân thường gặp: TCP giữa primary–standby bị firewall/idle-timeout cắt, `walreceiver`\n\nchết vì crash, `wal_receiver_timeout`\n\ntrên standby hết hạn khi mạng blip, standby được restart nhưng `primary_conninfo`\n\nsai. Row của standby biến mất khỏi `pg_stat_replication`\n\ntrên primary — không có exception ở đâu, log Postgres chỉ có một dòng `terminating walsender process due to replication timeout`\n\nrồi im.\n\n```\n-- Sai: alert kiểu \"SELECT count(*) FROM pg_stat_replication > 0\"\n-- Không phân biệt được standby A hay B đã rớt trong setup nhiều replica\nSELECT count(*) AS replica_count FROM pg_stat_replication;\n-- Đúng: alert theo tên (application_name của từng standby)\n-- Standby set application_name qua primary_conninfo hoặc recovery.conf\nSELECT expected.name,\n       CASE WHEN r.application_name IS NULL THEN 'DOWN'\n            WHEN r.state <> 'streaming'     THEN r.state\n            ELSE 'ok' END AS health\nFROM (VALUES ('standby-1'), ('standby-2')) AS expected(name)\nLEFT JOIN pg_stat_replication r ON r.application_name = expected.name;\n```\n\nÉp mỗi standby set `application_name`\n\n(qua `primary_conninfo='... application_name=standby-1 ...'`\n\n) là điều kiện tiên quyết cho alert theo tên — không có nó, không có cách nhận diện replica nào rớt.\n\n**Failure mode 2: standby ngắt nhưng slot còn — pg_wal/ phình.** Nếu standby dùng physical replication slot (\n\n`primary_slot_name`\n\nset), khi standby down slot vẫn `active=false`\n\nnhưng `restart_lsn`\n\nkhông tiến. Primary giữ mọi WAL segment từ `restart_lsn`\n\ntrở đi → `pg_wal/`\n\nlớn dần không giới hạn cho tới khi hết đĩa và `PANIC: could not write to file ... No space left on device`\n\n. Đây là kịch bản đã có trong nhiều postmortem công khai — GitLab đã có sự cố database năm 2017 mà một trong các layer bảo vệ chính là monitoring bị thiếu tín hiệu. Cứu cứng bằng `max_slot_wal_keep_size`\n\n(Postgres 13+), nhưng phải kèm alert `wal_status IN ('extended','lost')`\n\nđể biết trước khi mất consumer.\n\n```\n-- Cột phải đưa lên dashboard cho MỌI slot\nSELECT slot_name, slot_type, active, wal_status,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained,\n       pg_size_pretty(safe_wal_size)                                      AS safe_left\nFROM pg_replication_slots;\n```\n\n**Failure mode 3: sync replication treo write khi sync standby biến mất.** Với `synchronous_commit = on`\n\nvà `synchronous_standby_names = 'FIRST 1 (standby-1)'`\n\n, khi `standby-1`\n\nrớt, mọi `COMMIT`\n\ntrên primary sẽ *treo* chờ ACK — client thấy connection hang, không phải error. Backend đứng ở state `waiting for sync replication`\n\ntrong `pg_stat_activity`\n\n(`wait_event_type = 'IPC'`\n\n, `wait_event = 'SyncRep'`\n\n). Nếu dashboard chỉ theo dõi `sync_state`\n\nmà không alert khi không còn standby nào ở `sync`\n\n, sẽ không phát hiện được cho tới khi có báo cáo \"app đơ khi commit\". Cách xử lý là dùng quorum thay vì FIRST 1 (`ANY 2 (s1, s2, s3)`\n\ncho phép mất bất kỳ 1 trong 3), và alert cứng khi số standby `sync_state IN ('sync','quorum')`\n\ntụt dưới ngưỡng cần thiết.\n\n**Failure mode 4: replay_lag lớn dù flush_lag gần 0 — replay bị conflict.** Standby đã nhận và fsync WAL, nhưng replay đứng vì recovery conflict: một query dài trên standby giữ snapshot chặn quá trình apply vacuum WAL, hoặc lock trên relation bị hold. Symptom:\n\n`write_lsn`\n\nbám sát `sent_lsn`\n\nnhưng `replay_lsn`\n\ntụt lại. Đọc `pg_stat_database_conflicts`\n\n(`confl_snapshot`\n\n, `confl_lock`\n\n, `confl_bufferpin`\n\n) trên standby để biết loại conflict. Nếu app đọc replica cho query nhạy freshness, người dùng sẽ thấy \"vừa commit xong đọc lại không thấy\" — không phải vì network mà vì replay bị block. Bật `hot_standby_feedback = on`\n\ngiảm `confl_snapshot`\n\nnhưng đổi lại có bloat trên primary; tăng `max_standby_streaming_delay`\n\ncho phép replay đợi thay vì hủy query, nhưng tăng lag; hoặc route query dài sang một replica riêng.**Failure mode 5: dashboard \"chỉ xem khi có sự cố\".** Metric có, panel có, nhưng không ai nhìn cho tới khi PagerDuty kêu. Không có alert threshold + không có runbook = MTTR bằng thời gian phát hiện thủ công. Điểm sửa: mọi metric trên dashboard phải có ít nhất một alert rule tương ứng — nếu không có alert, không nên đưa lên dashboard làm gì.\n\nTriệu chứng cần soi khi ai đó nói \"app đọc dữ liệu cũ\" hoặc \"commit đôi khi treo\":\n\n`pg_stat_replication`\n\n→ replica disconnect.`state <> 'streaming'`\n\nkéo dài — `catchup`\n\nbình thường sau restart nhưng không được kéo dài.`pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)`\n\ntăng đơn điệu → replay đứng.`write_lag`\n\ngần 0 nhưng `replay_lag`\n\nlớn → conflict trên standby, không phải network.`wal_status IN ('extended','lost')`\n\ntrên slot → gần mất consumer.`reply_time`\n\ncũ hơn vài giây → walsender không nhận feedback, connection có thể đã chết.View và hàm chính (đều có trong Postgres docs mục \"System Views\" và \"System Information Functions\"):\n\n```\n-- Primary side: pg_stat_replication, pg_replication_slots\n-- Standby side:\nSELECT status, sender_host, sender_port,\n       last_msg_send_time, last_msg_receipt_time,\n       latest_end_lsn, latest_end_time,\n       EXTRACT(EPOCH FROM (now() - last_msg_receipt_time)) AS silence_seconds\nFROM pg_stat_wal_receiver;\n\nSELECT pg_is_in_recovery(),\n       pg_last_wal_receive_lsn(),\n       pg_last_wal_replay_lsn(),\n       pg_last_xact_replay_timestamp(),\n       EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS replay_delay_sec;\n\n-- Recovery conflict counter\nSELECT datname, confl_tablespace, confl_lock, confl_snapshot,\n       confl_bufferpin, confl_deadlock\nFROM pg_stat_database_conflicts;\n```\n\nTool stack thực tế:\n\n`postgres_exporter`\n\n`pg_stat_replication`\n\n, `pg_replication_slots`\n\n, `pg_stat_wal_receiver`\n\ndưới dạng metric Prometheus. Có custom queries file cho phép thêm bất cứ SELECT nào không có sẵn.`/cluster`\n\nendpoint có role, state, xlog location của từng node — nguồn thứ hai để cross-check với `pg_stat_replication`\n\n.`replay_lag_bytes`\n\n> ngưỡng bucket, (3) `replay_lag`\n\nseconds > SLO đọc replica, (4) sync standby count < required, (5) slot `wal_status`\n\nextended/lost, (6) `walreceiver`\n\nsilence > 30 giây.Rule phòng ngừa cứng:\n\n`application_name`\n\nriêng qua `primary_conninfo`\n\n, không dùng tên mặc định `walreceiver`\n\n.`log_replication_commands = on`\n\nkhi debug — nhưng tắt lại lúc bình thường vì log volume cao.`hot_standby_feedback`\n\nbật cho replica phục vụ query dài; kèm alert bloat trên primary.Dựng dashboard đầy đủ tốn công một lần: exporter, panel, alert rule, runbook. Cái được là **MTTR giảm mạnh** — replica down phát hiện trong vài chục giây thay vì \"phát hiện lúc failover\"; slot pin WAL phát hiện khi `wal_status = 'extended'`\n\n(trước khi `lost`\n\n) thay vì lúc `PANIC`\n\ndisk full; replay conflict phát hiện lúc lag > SLO thay vì lúc user báo bug. Cái mất: mỗi metric là một alert rule cần bảo trì, exporter là một service thêm phải HA, và mỗi standby thêm một hàng metric — với setup cascade nhiều tầng dashboard sẽ đông. Quy tắc thực tế: **dashboard tối thiểu phải trả lời được ba câu — \"mỗi standby có streaming không?\", \"lag byte và second của mỗi standby là bao nhiêu?\", \"có slot nào sắp mất WAL không?\" — nếu ba câu này không đọc được trong 10 giây, dashboard chưa xong; và mọi câu phải có alert đi kèm, không thì không đưa lên.**\n\nReplication trong Postgres cần monitor những gì, và vì sao chỉ đếm số replica trong\n\n`pg_stat_replication`\n\nkhông đủ?\n\nĐủ interview-grade: cần chia theo bốn nhóm metric — (1) **connectivity**: row trong `pg_stat_replication`\n\ntồn tại *cho từng standby theo tên* (`application_name`\n\n), `state = 'streaming'`\n\n, `reply_time`\n\ngần đây; và đối xứng bên standby là `pg_stat_wal_receiver.status = 'streaming'`\n\n, `last_msg_receipt_time`\n\nkhông im lặng quá vài chục giây. (2) **lag** hai chiều: byte lag qua `pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)`\n\ncho mỗi standby, và time lag qua `write_lag/flush_lag/replay_lag`\n\n— phải tách `write_lag`\n\nvới `replay_lag`\n\nvì `replay_lag`\n\ncao mà `write_lag`\n\nthấp là chỉ dấu recovery conflict trên standby chứ không phải network. (3) **sync guarantee**: `sync_state`\n\ncủa từng standby (`async`\n\n/`potential`\n\n/`sync`\n\n/`quorum`\n\n), tổng số standby ở `sync`\n\n/`quorum`\n\nso với yêu cầu của `synchronous_standby_names`\n\n— thiếu là commit sẽ treo. (4) **slot health**: `pg_replication_slots.active`\n\n, `wal_status`\n\n, `pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)`\n\n, `safe_wal_size`\n\n— slot inactive kéo `pg_wal/`\n\nphình và có thể bít cluster. Chỉ đếm `count(*)`\n\nkhông đủ vì (a) không biết replica nào rớt trong nhiều-replica, (b) `state`\n\ncó thể `catchup`\n\nkhông phải `streaming`\n\n, (c) không phát hiện `replay_lag`\n\ncao trong khi vẫn có connection, (d) không thấy sync standby biến mất khi vẫn còn async standby. Hậu quả production khi thiếu: replica chết âm thầm nhiều ngày phát hiện lúc failover; slot bỏ quên làm disk đầy → cluster PANIC dừng nhận write; sync standby rớt → commit trên primary treo và client thấy connection hang chứ không phải error rõ ràng. Điểm ăn điểm là gắn từng nhóm metric với view/hàm cụ thể (`pg_stat_replication`\n\n, `pg_stat_wal_receiver`\n\n, `pg_replication_slots`\n\n, `pg_last_xact_replay_timestamp()`\n\n) và nêu một alert rule cho mỗi — không chỉ liệt kê \"lag, state, sync\".\n\nMục tiêu: dựng primary + standby bằng Docker, dựng physical replication slot, viết query dashboard core, rồi ép hai kịch bản vỡ (standby chết, slot pin WAL) và quan sát đúng metric nào chuyển màu.\n\nBước 1 — Dựng primary với streaming replication bật sẵn:\n\n```\ndocker network create pgnet 2>/dev/null || true\n\ndocker run -d --name pg-primary --network pgnet \\\n  -e POSTGRES_PASSWORD=pw -p 5440:5432 \\\n  postgres:16 \\\n  -c wal_level=replica \\\n  -c max_wal_senders=10 \\\n  -c max_replication_slots=10 \\\n  -c hot_standby=on \\\n  -c log_replication_commands=on\n\nsleep 5\ndocker exec -i pg-primary psql -U postgres <<'SQL'\nCREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'replpw';\nSELECT pg_create_physical_replication_slot('standby_1_slot');\nSQL\n\n# Cho phép replication từ standby (append pg_hba)\ndocker exec pg-primary bash -c \\\n  \"echo 'host replication repl 0.0.0.0/0 md5' >> /var/lib/postgresql/data/pg_hba.conf\"\ndocker exec pg-primary psql -U postgres -c \"SELECT pg_reload_conf();\"\n```\n\nBước 2 — Dựng standby bằng `pg_basebackup`\n\nrồi start:\n\n```\ndocker run -d --name pg-standby --network pgnet \\\n  -e POSTGRES_PASSWORD=pw -p 5441:5432 \\\n  --entrypoint sleep postgres:16 infinity\n\ndocker exec pg-standby bash -c '\n  rm -rf /var/lib/postgresql/data/*\n  PGPASSWORD=replpw pg_basebackup \\\n    -h pg-primary -U repl -D /var/lib/postgresql/data \\\n    -R -X stream -S standby_1_slot -P\n  # -R tạo standby.signal + primary_conninfo tự động\n  # Thêm application_name để dashboard nhận diện được\n  echo \"primary_conninfo = '\"'\"'host=pg-primary user=repl password=replpw application_name=standby-1'\"'\"'\" \\\n    >> /var/lib/postgresql/data/postgresql.auto.conf\n'\ndocker restart pg-standby\nsleep 5\n\n# Standby phải là read-only\ndocker exec pg-standby psql -U postgres -c \"SELECT pg_is_in_recovery();\"\n# t\n```\n\nBước 3 — Query dashboard core trên primary:\n\n```\ndocker exec -it pg-primary psql -U postgres <<'SQL'\nSELECT application_name, state, sync_state,\n       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,\n       replay_lag,\n       EXTRACT(EPOCH FROM (now() - reply_time)) AS since_reply_sec\nFROM pg_stat_replication;\n\nSELECT slot_name, active, wal_status,\n       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained,\n       pg_size_pretty(safe_wal_size) AS safe_left\nFROM pg_replication_slots;\nSQL\n```\n\nTrên standby:\n\n```\ndocker exec -it pg-standby psql -U postgres <<'SQL'\nSELECT status, sender_host, latest_end_lsn,\n       EXTRACT(EPOCH FROM (now() - last_msg_receipt_time)) AS silence_sec\nFROM pg_stat_wal_receiver;\n\nSELECT pg_last_wal_replay_lsn(),\n       EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS replay_delay_sec;\nSQL\n```\n\nBước 4 — Sinh tải trên primary, quan sát LSN tiến trên cả hai:\n\n```\ndocker exec -i pg-primary psql -U postgres <<'SQL'\nCREATE TABLE IF NOT EXISTS t(id bigserial PRIMARY KEY, data text);\nINSERT INTO t(data) SELECT repeat('x', 200) FROM generate_series(1, 200000);\nSQL\ndocker exec pg-primary psql -U postgres -c \"SELECT pg_current_wal_lsn();\"\ndocker exec pg-standby psql -U postgres -c \"SELECT pg_last_wal_replay_lsn();\"\n# Hai LSN gần nhau; lặp lại để thấy chúng cùng tiến\n```\n\nBước 5 — Ép failure mode 1 (standby chết) và xem metric nào chuyển:\n\n```\ndocker stop pg-standby\n\n# Chờ walsender phát hiện — thường theo wal_sender_timeout (default 60s)\nsleep 15\ndocker exec pg-primary psql -U postgres -c \\\n  \"SELECT application_name, state FROM pg_stat_replication;\"\n# Row của standby-1 biến mất — đây là tín hiệu alert \"replica down\"\n\ndocker exec pg-primary psql -U postgres -c \\\n  \"SELECT slot_name, active,\n          pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained\n   FROM pg_replication_slots;\"\n# active=false nhưng slot vẫn giữ WAL — đây là mầm của failure mode 2\n```\n\nBước 6 — Ép failure mode 2 (slot pin WAL), quan sát `pg_wal/`\n\nphình:\n\n```\n# Giữ standby down, sinh nhiều WAL trên primary\ndocker exec -i pg-primary psql -U postgres <<'SQL'\nINSERT INTO t(data) SELECT repeat('y', 500) FROM generate_series(1, 500000);\nCHECKPOINT;\nINSERT INTO t(data) SELECT repeat('z', 500) FROM generate_series(1, 500000);\nCHECKPOINT;\nSQL\n\ndocker exec pg-primary du -sh /var/lib/postgresql/data/pg_wal\ndocker exec pg-primary psql -U postgres -c \\\n  \"SELECT slot_name, wal_status,\n          pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained\n   FROM pg_replication_slots;\"\n# retained tăng dần; wal_status có thể vẫn 'reserved' nếu chưa set max_slot_wal_keep_size\n```\n\nÁp lưới cứu và quan sát `wal_status`\n\nchuyển:\n\n```\ndocker exec pg-primary psql -U postgres <<'SQL'\nALTER SYSTEM SET max_slot_wal_keep_size = '32MB';\nSELECT pg_reload_conf();\nSQL\n\ndocker exec -i pg-primary psql -U postgres <<'SQL'\nINSERT INTO t(data) SELECT repeat('w', 500) FROM generate_series(1, 500000);\nCHECKPOINT;\nSQL\n\ndocker exec pg-primary psql -U postgres -c \\\n  \"SELECT slot_name, wal_status, pg_size_pretty(safe_wal_size) AS safe_left\n   FROM pg_replication_slots;\"\n# wal_status chuyển 'extended' rồi 'lost' — đây chính là metric HA dashboard\n# phải alert TRƯỚC khi disk đầy\n```\n\nBước 7 — Khôi phục standby, xác nhận slot lost thì phải resync:\n\n```\ndocker start pg-standby\nsleep 10\ndocker logs --tail 30 pg-standby\n# Nếu wal_status='lost', standby báo lỗi không thể catch up\n# Fix: pg_basebackup lại từ primary (chấp nhận mất consumer thay vì mất cluster)\n```\n\nBước 8 — Dọn:\n\n```\ndocker rm -f pg-primary pg-standby\ndocker network rm pgnet\n```\n\nSau bài này, dashboard không còn là \"vài panel Grafana đẹp\" — nó là ánh xạ 1-1 giữa từng dòng trong `pg_stat_replication`\n\n/`pg_replication_slots`\n\n/`pg_stat_wal_receiver`\n\nvà một alert rule, để mỗi khi bức tranh HA lệch khỏi trạng thái tốt, có đúng một tín hiệu ping ra ngoài, kèm đúng một runbook.", "url": "https://wpnews.pro/news/replication-monitoring-ha-dashboard", "canonical_source": "https://dev.to/nguoidungai/replication-monitoring-ha-dashboard-32mc", "published_at": "2026-07-07 15:49:50+00:00", "updated_at": "2026-07-07 15:58:33.857054+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["PostgreSQL", "pg_stat_replication", "pg_stat_wal_receiver", "pg_replication_slots", "WAL", "LSN", "HA dashboard"], "alternates": {"html": "https://wpnews.pro/news/replication-monitoring-ha-dashboard", "markdown": "https://wpnews.pro/news/replication-monitoring-ha-dashboard.md", "text": "https://wpnews.pro/news/replication-monitoring-ha-dashboard.txt", "jsonld": "https://wpnews.pro/news/replication-monitoring-ha-dashboard.jsonld"}}