20.2 物理流复制
PostgreSQL 物理复制不是“把表同步到另一台机器”,而是让另一套数据目录 持续接收并重放同一个 database cluster 的 WAL 历史。
理解 HA,至少要能回答:
WAL 生成到哪里?
发送到哪里?
备库写到哪里?
flush 到哪里?
replay 到哪里?
当前属于哪条 timeline?
旧 WAL 由谁保留?这些问题都有 PostgreSQL 原生证据,不必靠角色标签猜。
20.2.1 WAL 发送、接收、重放与 LSN
物理复制传的是 WAL
primary 修改 data page 之前,相关变化先以 WAL record 进入 WAL。physical standby 的基本流水线:
primary backend
-> WAL insert
-> WAL buffer / local flush
-> walsender
-> network
-> walreceiver
-> standby WAL write/flush
-> recovery process replay
-> hot standby query visibility因此“复制到达”至少有三层:
received
written/flushed
replayed同步提交等待哪一层,由 synchronous_commit 决定;读请求能否看到,由
replay 位置决定。
LSN 是 WAL 地址
Log Sequence Number 写作:
0/170002B0可理解为单调推进的 WAL byte position。它不是 wall-clock time,也不是 transaction ID。
常用函数:
-- 只在非 recovery 节点调用
SELECT pg_current_wal_lsn();
-- standby 上最后收到/重放的位置
SELECT pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn(),
pg_last_xact_replay_timestamp();
-- byte difference
SELECT pg_wal_lsn_diff('0/170002B0', '0/17000000');不要在 standby 无条件调用 pg_current_wal_lsn()。本章
ha-facts.sql 先判断 pg_is_in_recovery(),
再选择 primary 或 standby 合适的函数。
primary 观察 walsender
SELECT application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)
AS replay_gap_bytes
FROM pg_stat_replication
ORDER BY application_name;字段的方向:
sent_lsn primary 已发送
write_lsn standby 已写到 OS
flush_lsn standby 已报告 durable flush
replay_lsn standby 已重放本章每个稳定 phase 都要求 primary 看到:
exactly two rows
application_name = current two replicas
state = streaming
sync_state = async
client_addr = declared member address
replay_gap_bytes <= 1 MiBstate=streaming 是必要条件,不是业务 freshness 的充分条件。
standby 观察 walreceiver
SELECT status,
sender_host,
sender_port,
written_lsn,
flushed_lsn,
latest_end_lsn
FROM pg_stat_wal_receiver;每个 standby 应看到一个 receiver,且 upstream 地址是当前 primary。在正式 切换中:
timeline 5: pg-test-2/3 receiver -> 10.10.10.11
timeline 6: pg-test-1/3 receiver -> 10.10.10.12
timeline 7: pg-test-2/3 receiver -> 10.10.10.11这比只看 Patroni 的 Role=Replica 多证明了一层:PostgreSQL 自己确实在
接收当前 upstream。
lag 不是一个数字
至少区分:
transport lag
generated - received
flush lag
generated - durable on standby
replay lag
generated - applied
visibility lag
commit on primary - visible to standby query
business freshness
source event time - projection/report watermark用 byte gap 的优势是明确;局限是业务时间随 WAL generation rate 变化。
用 now() - pg_last_xact_replay_timestamp() 也有陷阱:没有新事务时,
timestamp 看起来越来越“旧”,并不代表 standby 落后。
正确做法是组合:
sender/receiver state
LSN byte gaps
replay timestamp with workload context
WAL generation rate
application watermark where neededreplay 与查询冲突
hot standby 一边 replay,一边允许只读查询。长查询可能与 recovery 需要 清理的 tuple、DDL 或锁冲突。系统必须在:
cancel standby query
delay WAL replay
retain more dead rows on primary via feedback之间取舍。HA replica 若同时承担分析负载,可能让 replay lag、bloat 与 failover eligibility 互相影响。
本章把 pg-test-3 标为 offline-query placement intent,但不会把标签当成
已证明的 workload isolation。
观测快照不是连续保证
pg_stat_replication 是当前状态。采到 gap=0 只能说明采样点:
the replica caught up at observation time它不证明上一秒、下一秒或 primary 永久损失时也为零。本章 planned switchover 会选择健康 candidate;这正是它不能代替 catastrophic failover RPO 测试的原因。
20.2.2 timeline、恢复目标与历史分叉
promotion 会创建新 timeline
standby promotion 的本质不是“改角色字段”,而是从共同 WAL 历史的某个点 开始一条新分支。
timeline 5 ------ A ------ promotion
\
timeline 6 B ------ C如果旧 primary 在分支点后也继续写:
timeline 5 X ------ YB/C 与 X/Y 可能都是各自内部合法的事务,但无法通过普通流复制自动合并。
这就是为什么 authority 与 fencing 比“选主速度”更重要。
system identifier 与 timeline
两个身份不要混:
system identifier
initdb 时产生,标识一个 PostgreSQL database cluster lineage
timeline ID
标识该 lineage 中一次 WAL history 分支本章正式证据:
one unchanged system identifier
timeline 5 -> 6 -> 7如果成员 system identifier 不同,它不是这个 physical cluster 的合法 replica;如果 system identifier 相同但 timeline 关系不对,则必须检查 history 与分叉。
原生证据:
SELECT system_identifier
FROM pg_control_system();
SELECT timeline_id
FROM pg_control_checkpoint();第二条查询有一个重要边界,后面单独解释。
timeline history
promotion 会产生 timeline history 文件,描述新 timeline 从哪条父 timeline 的哪个 WAL 位置分叉。recovery 要选择正确 history。
HA standby 通常使用:
recovery_target_timeline = latest这是 PostgreSQL 默认值,使其能跟随 promotion 后的最新历史。官方 warm standby 文档 明确建议 HA 多 standby 使用 latest。
“latest”不是允许随便选择历史。它仍依赖可达的 archive/stream、history 文件和一个被授权的 upstream。
从 WAL 文件名得到 primary 当前 timeline
WAL 文件名的前 8 个十六进制字符编码 timeline。primary 可以:
SELECT split.timeline_id
FROM pg_split_walfile_name(
pg_walfile_name(pg_current_wal_lsn())
) AS split;本章把它保存为:
current_wal_timeline_id并要求它与 Patroni 当前 timeline 一致。
不要在 recovery 中调用 pg_walfile_name(pg_current_wal_lsn());这些
current-WAL 函数不是 standby 当前 replay timeline 的通用接口。
checkpoint timeline 不是 standby 当前 replay timeline
第一次真实演练暴露了一个非常有价值的测量陷阱:
Patroni: pg-test-3 streaming on timeline 3
pg_control_checkpoint().timeline_id on pg-test-3: 1最终正式运行结束后:
Patroni current timeline: 7
pg-test-3 checkpoint_timeline_id: 3
receiver: streaming from current primary没有发生“备库卡在旧 timeline”。pg_control_checkpoint() 返回 control
file 中最近 checkpoint 的信息;一个 standby 可能已经重放后续 timeline,
但还没有用新的 checkpoint metadata 更新到相同数值。
因此证据模型使用精确名称:
checkpoint_timeline_id验收只要求:
[ checkpoint_timeline_id \le current\ phase\ timeline ]
而不是错误地要求 standby checkpoint timeline 必须时时等于 Patroni timeline。
这条经验说明:
观测函数名、状态生命周期和适用节点不清楚时,“更多 SQL”也会产生错误 告警。
如何验证 standby 跟随正确历史
组合证据:
Patroni member timeline and state
standby pg_is_in_recovery() = true
pg_stat_wal_receiver.status = streaming
sender_host = current primary
receive/replay LSN advances
primary walsender sees that application
system identifier unchanged若需要进一步诊断,可检查:
timeline history files
PostgreSQL recovery logs
archive availability
Patroni logs and DCS history不要用一个 checkpoint 字段替代整个 lineage proof。
promotion 是不可逆状态迁移
promotion 后,新 primary 会产生新 timeline。要把它“变回原样”,不是把 配置里的 role 改回 replica:
- 若未产生分叉写入且工具能安全处理,仍需验证;
- 常见路径是
pg_rewind对齐; - rewind 前提不满足或失败时,从新 base backup 重建;
- 任何时候都必须先确认唯一的 source of truth。
本章的第二次 switchover 又创建 timeline 7;这是恢复“教学角色基线”, 不是把 WAL 历史倒回 timeline 5。
20.2.3 复制槽、归档与 WAL 保留
standby 必须拿到连续 WAL
standby 能继续 recovery 的前提:
从自己的起点到当前目标之间,没有缺失所需 WALWAL 来源可以组合:
streaming from primary
local pg_wal
WAL archive via restore_command如果旧 WAL 已在 primary 被 recycle,archive 也没有,而 standby 仍需要:
reinitialize from a new base backup三种主要保留机制
| 机制 | 依据 | 优势 | 风险/局限 |
|---|---|---|---|
wal_keep_size | 至少保留一段量 | 简单 | 不是按 consumer 精确 |
| physical slot | 按 consumer restart LSN | 精确追踪需要 | consumer 卡住可撑满磁盘 |
| WAL archive | 外部历史 | catch-up/PITR 共用 | 需要独立完整性与恢复验证 |
它们不是互斥。一个成熟系统可能同时:
slot protects online replica
archive protects longer recovery history
wal_keep_size absorbs transient behavior复制槽的保证与反噬
physical replication slot 告诉 primary:
在 consumer 确认之前,不要移除它仍需要的 WAL原生查询:
SELECT slot_name,
slot_type,
active,
restart_lsn,
wal_status,
safe_wal_size
FROM pg_replication_slots
ORDER BY slot_name;本章每个 stable primary 都要求两个 active physical slot:
pg-test-1 primary -> pg_test_2, pg_test_3
pg-test-2 primary -> pg_test_1, pg_test_3slot 名使用下划线,因为 PostgreSQL slot 名只允许特定小写字符集合。
但 slot 的保护方式是不删 WAL。如果 replica 离线很久、网络断开或 consumer 永远不回来:
retained WAL grows
pg_wal filesystem fills
primary can stop accepting writes官方文档明确警告这一风险,并提供 max_slot_wal_keep_size 作为边界之一。
边界达到后,slot 可能失去可继续恢复所需的 WAL,运维必须在“磁盘安全”和
“无需重建 replica”之间明确取舍。
slot 监控
至少观测:
active
restart_lsn
current_lsn - restart_lsn
wal_status
safe_wal_size
inactive_since where available
pg_wal filesystem used/free
WAL generation rate
consumer identity and owner告警不能只在 disk 95% 才触发。需要提前估算:
[ time\ to\ full
\frac{free\ bytes}{WAL\ generation\ bytes/s} ]
并考虑 burst、checkpoint、backup 与其他 slot。
archive 是另一条恢复路径
启用:
archive_mode=on只说明 PostgreSQL 会尝试归档。还要检查:
archive_command/library result
last success and last failure
repository independence
retention
timeline history files
restore_command
end-to-end restorepg_stat_archiver.failed_count 是累计量;看到历史失败不能直接判定当前坏,
也不能因为最近一次成功就忽略趋势。第 21 章会从 archive 到 restore
闭环。
slot 不是 archive,archive 不是 backup proof
slot
primary-side retention promise for a replication consumer
archive
copied WAL history
base backup + archive + tested restore
才可能形成可用 recovery chainslot 会随 primary 故障域一起消失;archive 若也在同一主机/账户,就可能 共同消失。
planned switchover 前检查
candidate eligibility 最低检查:
member state = streaming
replay gap inside declared bound
system identifier same
timeline eligible
archive/slot not in dangerous state
candidate not tagged nofailover
no paused HA control
service/drain/maintenance authority ready本章 executable 将 replay gap 上限固定为 1 MiB,并检查两个 sender、 两个 active slot 与 receiver upstream。这个 byte bound 只用于健康计划 切换 preflight,不等于生产 RPO。
诊断顺序
发现 replica lag:
- primary 是否继续生成大量 WAL;
- walsender
sent/write/flush/replay哪一段拉开; - standby receiver 是否 streaming、upstream 是否正确;
- network throughput/error;
- standby disk write 与 recovery apply;
- replay conflict/long query;
- slot retention 与
pg_walheadroom; - archive 是否能补缺;
- 是否已越过必须 rebuild 的 stop line。
不要先 drop slot 或删 pg_wal。“释放空间”的错误动作可能直接删除唯一
可恢复路径。
本节实验查询
在合适的节点使用:
SELECT pg_is_in_recovery();
SELECT * FROM pg_stat_replication;
SELECT * FROM pg_stat_wal_receiver;
SELECT * FROM pg_replication_slots;
SELECT * FROM pg_stat_archiver;
SELECT * FROM pg_control_system();
SELECT * FROM pg_control_checkpoint();公开实验用一个 allowlisted JSON 查询封装这些证据:
不要把复制 credential、primary_conninfo password 或完整 Patroni config
复制进 evidence。
小结
physical replication follows WAL
receive != flush != replay
LSN byte gap != business time
system identifier != timeline
checkpoint timeline != current standby replay timeline
slots protect consumers by retaining WAL
retained WAL can exhaust primary storage
archive claims require restore proof权威参考
- PostgreSQL 18:Streaming Replication、Monitoring、Slots 与 Timelines
- PostgreSQL 18:System Administration Functions
- PostgreSQL 18:
pg_replication_slots - 本章 PostgreSQL evidence query
上一节:从失败模型设计高可用 · 返回本章目录 · 下一节:同步策略与提交语义 · 查看全书目录 · 查看索引中心