LabHub
学习 学习路径 课程

PostgreSQL 复制与提升

复制这件事,就是把 WAL 流出去

在 LabHub 中继续学习

一句话总结

PostgreSQL 复制就是把 WAL(预写日志)发送到备库,并在那里原样重放。因此,备库会成为主库的逐字节副本,但代价是不能混用不同版本。

概念图: 把 WAL(预写日志)发送到备库,并在那里原样重放 · 预先创建好的副本 · 只读的 · 相同的主版本

为什么需要它

数据库宕机,服务也会随之中断。即使有备份,恢复也可能耗费数小时。复制通过保留一份预先创建好的副本,把这段时间缩短到分钟级。

PostgreSQL 所做的事情很简单。所有变更都会先写入 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 = onsynchronous_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 持续增大,说明备库无法追上主库——可能是磁盘太慢,也可能是备库上正在运行长查询。