复制这件事,就是把 WAL 流出去
一句话总结
PostgreSQL 复制就是把 WAL(预写日志)发送到备库,并在那里原样重放。因此,备库会成为主库的逐字节副本,但代价是不能混用不同版本。
为什么需要它
数据库宕机,服务也会随之中断。即使有备份,恢复也可能耗费数小时。复制通过保留一份预先创建好的副本,把这段时间缩短到分钟级。
PostgreSQL 所做的事情很简单。所有变更都会先写入 WAL(这样才能从崩溃中恢复)。复制把这些 WAL 通过网络传出去,再由备库原样重放到自己的数据中。
由此会产生两个结论。
- 备库是只读的。它不能自行生成 WAL,因此不能接受写入。
- 主库和备库必须使用相同的主版本。因为每个版本的 WAL 格式都不同。
它如何运作
搭建过程分为四步。
1) 주 서버 설정 wal_level=replica, max_wal_senders, 복제 계정
2) 복제 슬롯 생성 pg_create_physical_replication_slot('s1')
3) 베이스백업 pg_basebackup -X stream -S s1 -R
4) 대기 서버 기동 standby.signal 이 있으면 자동으로 recovery 모드
pg_basebackup 的 -R 会自动创建 standby.signal 文件和 primary_conninfo 配置。这个文件就是“你是一台备库”的标记。
复制槽的作用
如果没有复制槽,主库就不知道备库已经接收到哪里。因此,主库会按照自己的进度回收 WAL;一旦备库短暂断开后重新连接,它需要的 WAL 可能已经消失。此时复制就会中断,只能从基础备份重新开始。
复制槽相当于一个“在这台备库取走之前,不要删除 WAL”的标记。但如果**备库永远不再恢复,WAL 就会无限堆积并占满磁盘。**因此要通过 max_slot_wal_keep_size 设置上限——超过上限时放弃复制槽,优先保护磁盘。这项设置要求你预先决定发生故障时要舍弃哪一边。
同步还是异步
默认模式是异步复制。主库发出 WAL 后立即返回提交结果。这样速度快,但如果主库突然宕机,尚未传出的事务就会丢失。
设置 synchronous_commit = on 和 synchronous_standby_names 后会变成同步复制。提交必须等到备库确认已经收到 WAL。数据不会丢失,但代价是提交延迟会增加一个网络往返时间,而且备库宕机时主库的写入也会停止。
因此,同步复制的标准做法是准备至少两台备库,并使用 ANY 1 (s1, s2) 这样的配置,表示“两台中只要一台响应即可”。只部署一台备库却启用同步复制,反而会降低可用性。
如何测量延迟,以及看到什么应该警觉
复制延迟不是一个单独的数字。必须把进度拆成四个位置观察,才能知道阻塞发生在哪里。
| 位置 | 含义 | 阻塞在这里时 |
|---|---|---|
sent_lsn |
主库已经发送到的位置 | 网络带宽 |
write_lsn |
备库已经接收并写入的位置 | 备库磁盘 |
flush_lsn |
已经持久化到磁盘的位置 | fsync 性能 |
replay_lsn |
已经实际应用、可供查询看到的位置 | 应用冲突 |
select application_name,
pg_wal_lsn_diff(sent_lsn, replay_lsn) as 적용_잔량,
write_lag, flush_lag, replay_lag
from pg_stat_replication;
报告时应使用时间,而不是字节数。“落后了 8MB”在写入很少的凌晨可能非常严重,但在批处理运行期间可能根本不算问题。在备库上测量 now() - pg_last_xact_replay_timestamp(),可以得到当前看到的是多少秒以前的世界,这个值更接近用户实际感受到的延迟。不过,如果主库完全没有写入,该值会持续增加,因此告警时应同时参考待处理的字节数。
**应用过程是单线程的。**主库可以通过多个连接并行写入,但备库会按照 WAL 顺序逐条应用。因此,一次大规模更新或创建索引就可能显著拉大延迟。如果备库 CPU 很空闲,延迟却仍在增长,通常就是这个原因。
查询可能阻塞应用过程。如果备库上正在执行一个长查询,而主库删除了该查询正在读取的行,WAL 应用就会与这个查询发生冲突。系统会等待 max_standby_streaming_delay 规定的时间,随后取消查询。把分析查询发送到备库后遇到 ERROR: canceling statement due to conflict with recovery,就是这种情况。启用 hot_standby_feedback = on,让备库把自己的查询情况告知主库,可以减少冲突;但这次会导致主库的清理被延迟并产生膨胀。无论选择哪一边都要付出代价,不存在免费的配置。
常见误解
“复制就是备份”——不是。如果误执行 DROP TABLE,该命令也会被复制,备库上的表同样会消失。复制防范的是硬件故障,备份防范的是人为失误,两者缺一不可。
“用备库分担读取负载没有成本”——如果备库上运行长查询,WAL 重放会与该查询冲突,导致应用延迟或查询被取消(可以用 hot_standby_feedback 缓解,但代价是主库清理被延迟)。没有免费的方案。
实务中真正重要的事
必须知道如何观察延迟。
-- 주 서버에서
select client_addr, state, sent_lsn, replay_lsn,
write_lag, flush_lag, replay_lag
from pg_stat_replication;
如果 state 不是 streaming,说明备库尚未连接。如果 replay_lag 持续增大,说明备库无法追上主库——可能是磁盘太慢,也可能是备库上正在运行长查询。