跳至内容
20.3 同步策略与提交语义

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  = false

synchronous_commit=on 在这里不能被误读成“同步复制已开启”。

acknowledgement levels

简化比较:

synchronous_commitprimary 等待点远端保证(有同步 standby 时)典型代价
off不等本地 WAL durable flush最低延迟,进程/OS crash 可丢近期提交
local本地 durable flush不等 remote本地 durability
remote_writeremote 写到 OS未要求 remote durable flush较低跨网延迟、保证较弱
onremote durable flush同步 standby WAL 已落盘至少网络 RTT 与 remote storage
remote_applyremote replaystandby 查询可见最大等待,受 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 class

remote_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 是 asyncpotentialsyncquorum 等状态。配置意图必须回到 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 availabilityacknowledged 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 completion

nosyncnofailover 与 placement intent

某些 standby 不应承担同步或提升角色:

remote high-latency DR
offline analytical replica
hardware below write requirement
maintenance member
delayed replica

HA 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 migrationmaintenanceexplicit 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

从目标反推策略

决策顺序:

  1. 定义具体 failure scenario;
  2. 定义哪些 acknowledgment 不能丢;
  3. 映射独立 failure domains;
  4. 选择同步集合和 acknowledgement level;
  5. 决定失去 standby 时 block 还是 degrade;
  6. 约束 candidate 和 manual override;
  7. 预算正常/故障 latency;
  8. 让 client 支持 timeout、unknown 与 idempotency;
  9. 用故障演练验证;
  10. 用 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

权威参考


上一节:物理流复制 · 返回本章目录 · 下一节:选主、DCS 与防脑裂 · 查看全书目录 · 查看索引中心

最后更新于