20.3 同步策略与提交语义
同步复制不是一个 on/off 开关,而是提交必须等谁、等到哪一步、没有合格
副本时宁愿阻塞还是退化的合同。
在调整参数前,先回答:
哪些成功应答绝不能在目标故障中消失?
最多等几个副本?
副本不可用时,是停止写还是降低保护?
候选人如何限制?
延迟和锁等待由谁承担?20.3.1 异步、同步与远程应用确认
两个参数回答两个问题
PostgreSQL 同步复制的核心分工:
synchronous_standby_names
哪些 replication connection 构成同步候选集合,等几个
synchronous_commit
当前事务要等到哪个 acknowledgement level如果 synchronous_standby_names='',没有 synchronous standby;即使
session 的 synchronous_commit=on,也只完成本地提交语义,不会凭空等一
台远端。
本章正式 baseline:
synchronous_standby_names = ''
synchronous_commit = on
pg_stat_replication = sync_state async
Patroni synchronous_mode = falsesynchronous_commit=on 在这里不能被误读成“同步复制已开启”。
acknowledgement levels
简化比较:
synchronous_commit | primary 等待点 | 远端保证(有同步 standby 时) | 典型代价 |
|---|---|---|---|
off | 不等本地 WAL durable flush | 无 | 最低延迟,进程/OS crash 可丢近期提交 |
local | 本地 durable flush | 不等 remote | 本地 durability |
remote_write | remote 写到 OS | 未要求 remote durable flush | 较低跨网延迟、保证较弱 |
on | remote durable flush | 同步 standby WAL 已落盘 | 至少网络 RTT 与 remote storage |
remote_apply | remote replay | standby 查询可见 | 最大等待,受 replay 影响 |
还有 remote_flush 等内部/兼容语义时,应以当前版本
官方参数文档
为准,不从名称猜。
remote_apply 解决可见性,不自动解决路由
当同步 standby replay 后才放行 commit,应用随后若读到同一 standby, 更容易获得 read-your-writes。但仍需:
read request actually routed to that eligible standby
session/transaction semantics compatible
failover does not choose another stale node
application knows required consistency classremote_apply 不是把所有 replica 都变成线性一致读。
commit 等待与 transaction 生命周期
top-level commit 等同步确认时:
- transaction locks 仍可能影响其他 session;
- write latency 增加;
- remote storage/network jitter 进入 tail latency;
- timeout/connection loss仍可能造成 outcome unknown;
- read-only transaction 与 rollback 不需要相同等待。
同步复制把数据保护成本放进前台写路径。它没有消除成本,只是把风险从 “故障时可能丢”移动到“正常时更慢、故障时可能不可写”。
session 可以覆盖
synchronous_commit 可按系统、database、role、session 或 transaction
设置:
BEGIN;
SET LOCAL synchronous_commit = 'remote_apply';
-- critical write
COMMIT;这允许分层:
money/ledger stronger acknowledgement
rebuildable event lower latency
bulk backfill separately controlled但 policy 不能只靠开发者“记得 SET”。建议把 role/database defaults、 connection initialization、审计和测试组合起来。
如何观察
SHOW synchronous_commit;
SHOW synchronous_standby_names;
SELECT application_name,
state,
sync_state,
sync_priority,
write_lsn,
flush_lsn,
replay_lsn
FROM pg_stat_replication;sync_state 可见当前 connection 是 async、potential、sync 或
quorum 等状态。配置意图必须回到 live view。
20.3.2 多副本同步集合与退化条件
FIRST:优先级集合
synchronous_standby_names = 'FIRST 1 (pg-a, pg-b)'含义:
按列表优先级选择一个 active synchronous standby
pg-a 可用时优先
pg-a 不可用时 pg-b 可接替适合有明确低延迟/placement 优先级的场景。缺点是高优先级节点的性能与 抖动更容易决定写 tail。
ANY:quorum 集合
synchronous_standby_names = 'ANY 1 (pg-a, pg-b)'含义:
任意一个候选确认即可ANY 2 (...) 则等任意两个。它可以降低单个慢节点对 latency 的影响,
但数据保护与 failover candidate 必须按 quorum history 推理。
数量不是 durability 的全部
考虑:
primary in AZ-a
sync standby 1 in AZ-a
sync standby 2 in AZ-b等任意一个,通常可能总由同 AZ 的低延迟 standby 确认。若 AZ-a 整体 消失,AZ-b standby 是否一定拥有所有 acknowledged commits,需要根据实际 选择集合和时间分析。
所以配置应连接 placement:
acknowledgement quorum
failure-domain quorum
promotion candidate set三者未必相同。
Patroni synchronous mode
PostgreSQL 负责 commit wait;Patroni 还要管理 promotion eligibility 与 standby 集合变化。
简化:
synchronous_mode=false
默认异步 election;可能提升落后 candidate
synchronous_mode=true
Patroni 只在确认候选包含可能已成功应答的事务时自动提升
无合格同步 standby 时可能临时退回非同步写,但随后故障不自动提升
synchronous_mode_strict=true
没有同步 standby 时也不退化;write 会阻塞/不可用准确行为随 Patroni 版本与 dynamic config 变化,应以 Replication modes 为准。
这体现两个目标:
data safety
write availability无法无条件同时最大化。
退化必须是显式政策
副本不可用时的选择:
| 政策 | write availability | acknowledged data protection |
|---|---|---|
| strict block | 降低 | 保持目标 |
| controlled async degrade | 保持 | 降低,必须告警/批准 |
| manual bypass | 操作者决定 | 可能破坏 guarantee |
| reject critical, allow lower tier | 分级 | 分级 |
不要让“超时太多,先改成 async”成为无记录的事故操作。需要:
who may degrade
which traffic
maximum duration
customer/business notice
audit event
re-protection completionnosync、nofailover 与 placement intent
某些 standby 不应承担同步或提升角色:
remote high-latency DR
offline analytical replica
hardware below write requirement
maintenance member
delayed replicaHA manager tags 可以表达候选意图。但标签只是 declaration,仍要验证 live role、routing 和 performance。一个 offline tag 不能代替资源隔离。
多副本并不自动等于多份 durable commit
某一时刻:
replica connected
replay lag 0不代表每次 commit 都等待它 durable flush。要看:
synchronous_standby_names
transaction synchronous_commit
pg_stat_replication.sync_state
Patroni synchronous policy同样,“两台 sync”也不证明它们位于独立 failure domain。
candidate eligibility
选主至少考虑:
member health
replication lag
timeline relationship
tags / maintenance
sync safety state
watchdog/fencing ability
DCS authority本章 baseline:
maximum_lag_on_failover = 1 MiB
check_timeline not asserted by this lab
synchronous_mode=false因此本章不会把 maximum_lag_on_failover 误写成 zero-loss guarantee。
Patroni 官方说明实际 worst-case 还受采样周期与近期 WAL generation 影响。
20.3.3 延迟、可用性和数据保护的交换
一个不可回避的三角
粗略地:
stronger acknowledged durability
lower write latency
higher write availability during replica/network fault不能在所有故障条件下同时无代价最大化。
同步 commit latency 下界近似包含:
[ L_{commit} \ge L_{primary\ flush} +RTT +L_{standby\ acknowledgement} ]
若 remote_apply,还要加入 replay queue 与 conflict。
P50 不够
同步写路径把远端 tail 带入本地 transaction:
network jitter
standby fsync tail
checkpoint
CPU steal
queueing
replay pressure需要看:
P50 / P95 / P99 / max
timeout rate
lock hold/wait
WAL bytes/s
sync standby churn
degradation events平均 RTT 很漂亮,也可能因为偶发 5 秒 stall 让 checkout 大面积超时。
timeout 不等于 rollback
即使同步 commit 等待超时或连接中断,事务可能已经:
- 在 primary durable;
- 在 standby durable;
- 只是应答未到客户端。
应用仍需 token/reconciliation。同步复制提高 durability,不消除 distributed commit outcome uncertainty。
业务分层
示例:
| 写入类 | 丢失代价 | latency tolerance | 建议方向 |
|---|---|---|---|
| 账务事实 | 极高 | 可接受更高 | strict sync + independent domains |
| 订单状态 | 高 | 中 | sync 或 durable event contract |
| clickstream | 可重建 | 低 | async/batched |
| cache projection | 可重建 | 低 | async |
| admin migration | 高 | maintenance | explicit stronger setting |
这不是固定答案。关键是同一 service 内可能需要不同 acknowledgement class。
用 decision record 替代参数清单
decision: critical writes wait for one remote durable flush
scope:
failures: one host or one AZ
simultaneous_region_loss: excluded
standbys:
candidates: pg-b, pg-c
placement: separate AZ
postgresql:
synchronous_standby_names: "ANY 1 (pg-b, pg-c)"
synchronous_commit: "on"
patroni:
synchronous_mode: true
synchronous_mode_strict: true
degradation:
automatic_async_fallback: forbidden
operator_override: incident-commander approval
client:
idempotency_required: true
evidence:
fault drills, token reconciliation, latency distribution参数必须从 decision 推导;否则升级或换平台时只剩一堆无法解释的数。
本章为什么保留 async baseline
把沙箱临时改成 sync,可能让实验数据更“好看”,却掩盖第 19 章真实交付的 policy。我们选择:
capture actual policy
accept planned switchover under explicit async exception
block zero-RPO inference
leave production sync decision pending正式结果:
all 95 acknowledged tokens present
25 unknown tokens reconciled absent它是有价值的 commit evidence,但不能越过 EX20-ASYNC-BASELINE。
从目标反推策略
决策顺序:
- 定义具体 failure scenario;
- 定义哪些 acknowledgment 不能丢;
- 映射独立 failure domains;
- 选择同步集合和 acknowledgement level;
- 决定失去 standby 时 block 还是 degrade;
- 约束 candidate 和 manual override;
- 预算正常/故障 latency;
- 让 client 支持 timeout、unknown 与 idempotency;
- 用故障演练验证;
- 用 production observation 持续校准。
不要从 synchronous_mode: true 反推业务目标。
评审问题
哪个成功应答必须存于几处?
这些“几处”是否独立?
同步确认等到 write、flush 还是 apply?
没有合格副本时谁决定停止写?
manual failover 能否绕过 safety?
timeout 后业务如何查结果?
延迟预算是否包含 remote tail?任何一个“以后再说”,都会在事故中变成临时一致性模型。
小结
synchronous_commit and synchronous_standby_names solve different axes
remote_write != remote flush != remote apply
FIRST priority != ANY quorum
Patroni sync mode constrains automatic promotion
strict protection trades write availability
timeout still permits unknown outcome
one successful async drill cannot prove zero RPO权威参考
- PostgreSQL 18:Synchronous Replication
- PostgreSQL 18:Replication Settings
- PostgreSQL 18:WAL Settings
- Patroni:Replication modes
- 本章实际策略合同
上一节:物理流复制 · 返回本章目录 · 下一节:选主、DCS 与防脑裂 · 查看全书目录 · 查看索引中心