Replication Monitoring — HA Dashboard 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. Streaming replication trong Postgres là một tunnel WAL từ primary sang standby: primary chạy một walsender cho mỗi standby, standby chạy một walreceiver nhận WAL và một startup process 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 , pg stat wal receiver , và pg replication slots để é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ố. Trên primary, mỗi standby đang kết nối tạo ra một backend loại walsender — Postgres đọc WAL từ pg wal/ hoặc từ WAL buffers khi còn nóng và stream qua replication connection. Trên standby, walreceiver nhận từng WAL record, ghi vào pg wal/ local, fsync tùy synchronous commit , rồi startup process 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 : sent lsn — byte cuối cùng primary đã gửi qua socket. write lsn — byte cuối cùng standby đã write vào OS page cache. flush lsn — byte cuối cùng standby đã fsync xuống đĩa. replay lsn — 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 — bốn "vạch" này chính là bốn nhịp của lag. Ba cột write lag , flush lag , replay lag kiểu interval 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. -- Trên primary: bức tranh đầy đủ cho một HA dashboard SELECT application name, client addr, state, -- streaming | catchup | startup | backup | stopping sync state, -- async | potential | sync | quorum pg wal lsn diff pg current wal lsn , sent lsn AS sent lag bytes, pg wal lsn diff pg current wal lsn , write lsn AS write lag bytes, pg wal lsn diff pg current wal lsn , flush lsn AS flush lag bytes, pg wal lsn diff pg current wal lsn , replay lsn AS replay lag bytes, write lag, flush lag, replay lag, EXTRACT EPOCH FROM now - reply time AS seconds since reply FROM pg stat replication; Trên standby, pg stat wal receiver cho biết đang kết nối tới primary nào conninfo , sender host , status streaming / waiting / stopping , last msg send time , last msg receipt time , và latest end lsn . Đâ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 và pg last xact replay timestamp là hai hàm dùng để tính lag theo thời gian: now - pg last xact replay timestamp cho ra "đọc trên standby chậm bao nhiêu giây so với transaction cuối cùng đã commit trên primary". Một cái nữa nằm cạnh: pg replication slots . Slot pin restart lsn lại để primary không recycle WAL segment mà standby chưa consume. Cột wal status Postgres 13+ có bốn giá trị reserved / extended / unreserved / lost — trạng thái lost là khi slot đã bị vượt max slot wal keep size và WAL bị invalidate; consumer phải resync từ base backup. safe wal size cho biết còn bao nhiêu byte WAL nữa slot có thể tụt trước khi bị lost. 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 chết vì crash, wal receiver timeout trên standby hết hạn khi mạng blip, standby được restart nhưng primary conninfo sai. Row của standby biến mất khỏi pg stat replication trên primary — không có exception ở đâu, log Postgres chỉ có một dòng terminating walsender process due to replication timeout rồi im. -- Sai: alert kiểu "SELECT count FROM pg stat replication 0" -- Không phân biệt được standby A hay B đã rớt trong setup nhiều replica SELECT count AS replica count FROM pg stat replication; -- Đúng: alert theo tên application name của từng standby -- Standby set application name qua primary conninfo hoặc recovery.conf SELECT expected.name, CASE WHEN r.application name IS NULL THEN 'DOWN' WHEN r.state < 'streaming' THEN r.state ELSE 'ok' END AS health FROM VALUES 'standby-1' , 'standby-2' AS expected name LEFT JOIN pg stat replication r ON r.application name = expected.name; Ép mỗi standby set application name qua primary conninfo='... application name=standby-1 ...' 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. Failure mode 2: standby ngắt nhưng slot còn — pg wal/ phình. Nếu standby dùng physical replication slot primary slot name set , khi standby down slot vẫn active=false nhưng restart lsn không tiến. Primary giữ mọi WAL segment từ restart lsn trở đi → pg wal/ lớ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 . Đâ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 Postgres 13+ , nhưng phải kèm alert wal status IN 'extended','lost' để biết trước khi mất consumer. -- Cột phải đưa lên dashboard cho MỌI slot SELECT slot name, slot type, active, wal status, pg size pretty pg wal lsn diff pg current wal lsn , restart lsn AS retained, pg size pretty safe wal size AS safe left FROM pg replication slots; Failure mode 3: sync replication treo write khi sync standby biến mất. Với synchronous commit = on và synchronous standby names = 'FIRST 1 standby-1 ' , khi standby-1 rớt, mọi COMMIT trên primary sẽ treo chờ ACK — client thấy connection hang, không phải error. Backend đứng ở state waiting for sync replication trong pg stat activity wait event type = 'IPC' , wait event = 'SyncRep' . Nếu dashboard chỉ theo dõi sync state mà không alert khi không còn standby nào ở sync , 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 cho phép mất bất kỳ 1 trong 3 , và alert cứng khi số standby sync state IN 'sync','quorum' tụt dưới ngưỡng cần thiết. 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: write lsn bám sát sent lsn nhưng replay lsn tụt lại. Đọc pg stat database conflicts confl snapshot , confl lock , confl bufferpin 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 giảm confl snapshot nhưng đổi lại có bloat trên primary; tăng max standby streaming delay cho 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ì. Triệu chứng cần soi khi ai đó nói "app đọc dữ liệu cũ" hoặc "commit đôi khi treo": pg stat replication → replica disconnect. state < 'streaming' kéo dài — catchup bình thường sau restart nhưng không được kéo dài. pg wal lsn diff pg current wal lsn , replay lsn tăng đơn điệu → replay đứng. write lag gần 0 nhưng replay lag lớn → conflict trên standby, không phải network. wal status IN 'extended','lost' trên slot → gần mất consumer. reply time cũ 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" : -- Primary side: pg stat replication, pg replication slots -- Standby side: SELECT status, sender host, sender port, last msg send time, last msg receipt time, latest end lsn, latest end time, EXTRACT EPOCH FROM now - last msg receipt time AS silence seconds FROM pg stat wal receiver; SELECT pg is in recovery , pg last wal receive lsn , pg last wal replay lsn , pg last xact replay timestamp , EXTRACT EPOCH FROM now - pg last xact replay timestamp AS replay delay sec; -- Recovery conflict counter SELECT datname, confl tablespace, confl lock, confl snapshot, confl bufferpin, confl deadlock FROM pg stat database conflicts; Tool stack thực tế: postgres exporter pg stat replication , pg replication slots , pg stat wal receiver dướ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 endpoint có role, state, xlog location của từng node — nguồn thứ hai để cross-check với pg stat replication . replay lag bytes ngưỡng bucket, 3 replay lag seconds SLO đọc replica, 4 sync standby count < required, 5 slot wal status extended/lost, 6 walreceiver silence 30 giây.Rule phòng ngừa cứng: application name riêng qua primary conninfo , không dùng tên mặc định walreceiver . log replication commands = on khi debug — nhưng tắt lại lúc bình thường vì log volume cao. hot standby feedback bậ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' trước khi lost thay vì lúc PANIC disk 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. Replication trong Postgres cần monitor những gì, và vì sao chỉ đếm số replica trong pg stat replication không đủ? Đủ interview-grade: cần chia theo bốn nhóm metric — 1 connectivity : row trong pg stat replication tồn tại cho từng standby theo tên application name , state = 'streaming' , reply time gần đây; và đối xứng bên standby là pg stat wal receiver.status = 'streaming' , last msg receipt time khô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 cho mỗi standby, và time lag qua write lag/flush lag/replay lag — phải tách write lag với replay lag vì replay lag cao mà write lag thấp là chỉ dấu recovery conflict trên standby chứ không phải network. 3 sync guarantee : sync state của từng standby async / potential / sync / quorum , tổng số standby ở sync / quorum so với yêu cầu của synchronous standby names — thiếu là commit sẽ treo. 4 slot health : pg replication slots.active , wal status , pg wal lsn diff pg current wal lsn , restart lsn , safe wal size — slot inactive kéo pg wal/ phình và có thể bít cluster. Chỉ đếm count không đủ vì a không biết replica nào rớt trong nhiều-replica, b state có thể catchup không phải streaming , c không phát hiện replay lag cao 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 , pg stat wal receiver , pg replication slots , pg last xact replay timestamp và nêu một alert rule cho mỗi — không chỉ liệt kê "lag, state, sync". Mụ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. Bước 1 — Dựng primary với streaming replication bật sẵn: docker network create pgnet 2 /dev/null || true docker run -d --name pg-primary --network pgnet \ -e POSTGRES PASSWORD=pw -p 5440:5432 \ postgres:16 \ -c wal level=replica \ -c max wal senders=10 \ -c max replication slots=10 \ -c hot standby=on \ -c log replication commands=on sleep 5 docker exec -i pg-primary psql -U postgres <<'SQL' CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'replpw'; SELECT pg create physical replication slot 'standby 1 slot' ; SQL Cho phép replication từ standby append pg hba docker exec pg-primary bash -c \ "echo 'host replication repl 0.0.0.0/0 md5' /var/lib/postgresql/data/pg hba.conf" docker exec pg-primary psql -U postgres -c "SELECT pg reload conf ;" Bước 2 — Dựng standby bằng pg basebackup rồi start: docker run -d --name pg-standby --network pgnet \ -e POSTGRES PASSWORD=pw -p 5441:5432 \ --entrypoint sleep postgres:16 infinity docker exec pg-standby bash -c ' rm -rf /var/lib/postgresql/data/ PGPASSWORD=replpw pg basebackup \ -h pg-primary -U repl -D /var/lib/postgresql/data \ -R -X stream -S standby 1 slot -P -R tạo standby.signal + primary conninfo tự động Thêm application name để dashboard nhận diện được echo "primary conninfo = '"'"'host=pg-primary user=repl password=replpw application name=standby-1'"'"'" \ /var/lib/postgresql/data/postgresql.auto.conf ' docker restart pg-standby sleep 5 Standby phải là read-only docker exec pg-standby psql -U postgres -c "SELECT pg is in recovery ;" t Bước 3 — Query dashboard core trên primary: docker exec -it pg-primary psql -U postgres <<'SQL' SELECT application name, state, sync state, pg wal lsn diff pg current wal lsn , replay lsn AS replay lag bytes, replay lag, EXTRACT EPOCH FROM now - reply time AS since reply sec FROM pg stat replication; SELECT slot name, active, wal status, pg size pretty pg wal lsn diff pg current wal lsn , restart lsn AS retained, pg size pretty safe wal size AS safe left FROM pg replication slots; SQL Trên standby: docker exec -it pg-standby psql -U postgres <<'SQL' SELECT status, sender host, latest end lsn, EXTRACT EPOCH FROM now - last msg receipt time AS silence sec FROM pg stat wal receiver; SELECT pg last wal replay lsn , EXTRACT EPOCH FROM now - pg last xact replay timestamp AS replay delay sec; SQL Bước 4 — Sinh tải trên primary, quan sát LSN tiến trên cả hai: docker exec -i pg-primary psql -U postgres <<'SQL' CREATE TABLE IF NOT EXISTS t id bigserial PRIMARY KEY, data text ; INSERT INTO t data SELECT repeat 'x', 200 FROM generate series 1, 200000 ; SQL docker exec pg-primary psql -U postgres -c "SELECT pg current wal lsn ;" docker exec pg-standby psql -U postgres -c "SELECT pg last wal replay lsn ;" Hai LSN gần nhau; lặp lại để thấy chúng cùng tiến Bước 5 — Ép failure mode 1 standby chết và xem metric nào chuyển: docker stop pg-standby Chờ walsender phát hiện — thường theo wal sender timeout default 60s sleep 15 docker exec pg-primary psql -U postgres -c \ "SELECT application name, state FROM pg stat replication;" Row của standby-1 biến mất — đây là tín hiệu alert "replica down" docker exec pg-primary psql -U postgres -c \ "SELECT slot name, active, pg size pretty pg wal lsn diff pg current wal lsn , restart lsn AS retained FROM pg replication slots;" active=false nhưng slot vẫn giữ WAL — đây là mầm của failure mode 2 Bước 6 — Ép failure mode 2 slot pin WAL , quan sát pg wal/ phình: Giữ standby down, sinh nhiều WAL trên primary docker exec -i pg-primary psql -U postgres <<'SQL' INSERT INTO t data SELECT repeat 'y', 500 FROM generate series 1, 500000 ; CHECKPOINT; INSERT INTO t data SELECT repeat 'z', 500 FROM generate series 1, 500000 ; CHECKPOINT; SQL docker exec pg-primary du -sh /var/lib/postgresql/data/pg wal docker exec pg-primary psql -U postgres -c \ "SELECT slot name, wal status, pg size pretty pg wal lsn diff pg current wal lsn , restart lsn AS retained FROM pg replication slots;" retained tăng dần; wal status có thể vẫn 'reserved' nếu chưa set max slot wal keep size Áp lưới cứu và quan sát wal status chuyển: docker exec pg-primary psql -U postgres <<'SQL' ALTER SYSTEM SET max slot wal keep size = '32MB'; SELECT pg reload conf ; SQL docker exec -i pg-primary psql -U postgres <<'SQL' INSERT INTO t data SELECT repeat 'w', 500 FROM generate series 1, 500000 ; CHECKPOINT; SQL docker exec pg-primary psql -U postgres -c \ "SELECT slot name, wal status, pg size pretty safe wal size AS safe left FROM pg replication slots;" wal status chuyển 'extended' rồi 'lost' — đây chính là metric HA dashboard phải alert TRƯỚC khi disk đầy Bước 7 — Khôi phục standby, xác nhận slot lost thì phải resync: docker start pg-standby sleep 10 docker logs --tail 30 pg-standby Nếu wal status='lost', standby báo lỗi không thể catch up Fix: pg basebackup lại từ primary chấp nhận mất consumer thay vì mất cluster Bước 8 — Dọn: docker rm -f pg-primary pg-standby docker network rm pgnet Sau 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 / pg replication slots / pg stat wal receiver và 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.