4. 同步复制与quorum commit
PostgreSQL 的流复制默认是异步的:主库提交事务时只写本地 WAL,不等备库确认。要获得“提交即已落到备库”的语义,必须启用同步复制。而同步复制的核心开关就是 synchronous_standby_names,它决定了“哪些备库、以什么方式、达到几个确认才算提交成功”。理解这个参数,是跨地域容灾方案设计的关键一步。
一、synchronous_standby_names 的语法与语义
该参数在主库的 postgresql.conf 中设置,取值是一个“同步备库选择表达式”,由三部分组成:数量限定符、备库标识列表、可选的 ANY / FIRST 关键字。基本形式如下:
# 形式一:FIRST(默认),按列表顺序取前 N 个
synchronous_standby_names = 'FIRST 1 (standby_a, standby_b)'
# 形式二:ANY,列表中任意 N 个确认即可
synchronous_standby_names = 'ANY 2 (standby_a, standby_b, standby_c)'
# 形式三:省略关键字,等价于 FIRST
synchronous_standby_names = '1 (standby_a, standby_b)'
列表中的名字必须与备库 primary_conninfo 里的 application_name 一致,否则主库无法把该备库识别为同步候选。例如备库连接串写成 application_name=standby_a,主库列表里也必须写 standby_a。名字不匹配是同步复制“配了却不生效”的最常见原因。
FIRST 与 ANY 的差别在于“谁算数”。FIRST 1 (a, b) 表示只有排在最前、且当前处于连接状态的备库 a 被计入同步集合;a 掉线后,b 才顶上。而 ANY 2 (a, b, c) 表示 a、b、c 中任意两个确认即可,容错性更好,适合多副本场景。跨地域部署中,如果同城有一个低延迟备库、异地有一个高延迟备库,常用 ANY 1 (local, remote) 来保证“至少一份同步副本”,同时避免被异地延迟拖死。
二、synchronous_commit 与 remote_apply 模式
仅有 synchronous_standby_names 还不够,提交需要等待到哪一步,由 synchronous_commit 决定。它有几个档位,从弱到强大致为:off、local、remote_write、on(等价 remote_flush)、remote_apply。
remote_write:备库已把 WAL 写入操作系统缓存,但未必落盘。延迟最低,但备库断电可能丢数据。on:备库已把 WAL 刷入磁盘(fsync 完成)。这是最常用的强同步档位,保证备库持久化。remote_apply:备库不仅落盘,还已经回放(apply)完该事务,查询在备库上能立刻看到这条数据。这是最强的一致性保证,代价也最大。
remote_apply 的价值在于“读一致性”:主库提交返回时,备库上的只读查询已经能看到该事务结果,适合读写分离且要求读后立即一致的业务。但它的等待时间 = 网络往返 + 备库写盘 + 备库回放,三者叠加。跨地域场景下,回放本身可能受备库负载影响而滞后,导致提交延迟出现长尾。
SET synchronous_commit = remote_apply;,只对关键事务使用强同步,普通事务保持 on 或 local,从而在一致性与吞吐之间取得平衡。三、跨地域同步的性能影响
同步复制的提交延迟近似等于“最慢的那个同步备库的确认时间”。同城机房 RTT 通常 0.5~2ms,而跨地域(如北京—上海)RTT 约 20~40ms,跨国可能 100ms 以上。这意味着每笔事务的提交都要额外等待一个 RTT,TPS 会显著下降。
假设同城备库确认耗时 1ms,异地备库确认耗时 30ms。若配置 ANY 1 (local, remote),提交只需等较快者,延迟约 1ms,异地仅作兜底;若配置 FIRST 1 (remote) 强制等异地,则每笔提交增加约 30ms,单连接 TPS 从约 1000 降到约 33。这个数量级差异说明:同步备库的选择直接决定写入性能上限。
此外还有两个隐性成本:一是主库的 WAL 发送进程要为每个同步备库维护确认状态,备库越多、网络抖动越大,提交延迟的抖动越明显;二是当同步备库全部失联时,主库会阻塞所有写事务,直到有备库恢复或管理员介入。跨地域链路不稳定时,这种阻塞风险必须提前评估。
四、配置与验证示例
下面给出一个跨地域双备库的典型配置。主库设置同步候选为同城与异地两个备库,采用 ANY 1 保证至少一份同步副本,同时把默认提交档位设为 on,仅对账务类事务使用 remote_apply。
# 主库 postgresql.conf
synchronous_standby_names = 'ANY 1 (standby_local, standby_remote)'
synchronous_commit = on
# 备库 primary_conninfo 中必须带 application_name
primary_conninfo = 'host=10.0.0.10 port=5432 user=repl
application_name=standby_local'
# 关键事务会话级提升一致性
BEGIN;
SET LOCAL synchronous_commit = remote_apply;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
验证是否生效,可在主库查询 pg_stat_replication:
SELECT application_name, sync_state, sync_priority,
write_lag, flush_lag, replay_lag
FROM pg_stat_replication;
其中 sync_state 为 sync 表示该备库当前被计入同步集合,potential 表示是候选但未轮到,async 表示异步。replay_lag 反映回放滞后,使用 remote_apply 时应重点观察它是否稳定在毫秒级。若 replay_lag 持续增大,说明备库回放能力不足,提交延迟会被拖长,此时应检查备库 CPU、磁盘 IO 以及是否有长查询阻塞回放。
wal_sender_timeout 并制定降级预案,例如临时将 synchronous_standby_names 置空以恢复写入,待备库恢复后再改回。总结来说,synchronous_standby_names 决定“等谁”,synchronous_commit 决定“等到哪一步”,两者组合决定了跨地域同步复制的延迟与一致性边界。设计时应先明确业务能接受的最大提交延迟,再据此选择 ANY 或 FIRST、on 或 remote_apply,而不是一味追求最强同步。