# 同步策略与提交语义

LLMS 索引： [llms.txt](/llms.txt)

---

同步复制不是一个 `on/off` 开关，而是提交必须等谁、等到哪一步、没有合格
副本时宁愿阻塞还是退化的合同。

在调整参数前，先回答：

```text
哪些成功应答绝不能在目标故障中消失？
最多等几个副本？
副本不可用时，是停止写还是降低保护？
候选人如何限制？
延迟和锁等待由谁承担？
```

## 20.3.1 异步、同步与远程应用确认 {#item-20-3-1}

### 两个参数回答两个问题

PostgreSQL 同步复制的核心分工：

```text
synchronous_standby_names
  哪些 replication connection 构成同步候选集合，等几个

synchronous_commit
  当前事务要等到哪个 acknowledgement level
```

如果 `synchronous_standby_names=''`，没有 synchronous standby；即使
session 的 `synchronous_commit=on`，也只完成本地提交语义，不会凭空等一
台远端。

本章正式 baseline：

```text
synchronous_standby_names = ''
synchronous_commit        = on
pg_stat_replication        = sync_state async
Patroni synchronous_mode  = false
```

`synchronous_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 影响 |

PostgreSQL 18 可配置的值只有表中的五种；`on` 就表示等待同步备库持久化
flush。不要把内部等待阶段或监控标签中的 `remote_flush` 当成可设置参数。
具体行为以当前版本
[官方参数文档](https://www.postgresql.org/docs/18/runtime-config-wal.html)
为准。

### `remote_apply` 解决可见性，不自动解决路由

当同步 standby replay 后才放行 commit，应用随后若读到同一 standby，
更容易获得 read-your-writes。但仍需：

```text
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
设置：

```sql
BEGIN;
SET LOCAL synchronous_commit = 'remote_apply';
-- critical write
COMMIT;
```

这允许分层：

```text
money/ledger       stronger acknowledgement
rebuildable event  lower latency
bulk backfill      separately controlled
```

但 policy 不能只靠开发者“记得 SET”。建议把 role/database defaults、
connection initialization、审计和测试组合起来。

### 如何观察

```sql
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 多副本同步集合与退化条件 {#item-20-3-2}

### `FIRST`：优先级集合

```conf
synchronous_standby_names = 'FIRST 1 (pg-a, pg-b)'
```

含义：

```text
按列表优先级选择一个 active synchronous standby
pg-a 可用时优先
pg-a 不可用时 pg-b 可接替
```

适合有明确低延迟/placement 优先级的场景。缺点是高优先级节点的性能与
抖动更容易决定写 tail。

### `ANY`：quorum 集合

```conf
synchronous_standby_names = 'ANY 1 (pg-a, pg-b)'
```

含义：

```text
任意一个候选确认即可
```

`ANY 2 (...)` 则等任意两个。它可以降低单个慢节点对 latency 的影响，
但数据保护与 failover candidate 必须按 quorum history 推理。

### 数量不是 durability 的全部

考虑：

```text
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：

```text
acknowledgement quorum
failure-domain quorum
promotion candidate set
```

三者未必相同。

### Patroni synchronous mode

PostgreSQL 负责 commit wait；Patroni 还要管理 promotion eligibility 与
standby 集合变化。

简化：

```text
synchronous_mode=false
  默认异步 election；可能提升落后 candidate

synchronous_mode=true
  Patroni 只在确认候选包含可能已成功应答的事务时自动提升
  无合格同步 standby 时可能临时退回非同步写，但随后故障不自动提升

synchronous_mode_strict=true
  没有同步 standby 时也不退化；write 会阻塞/不可用
```

准确行为随 Patroni 版本与 dynamic config 变化，应以
[Replication modes](https://patroni.readthedocs.io/en/latest/replication_modes.html)
为准。

这体现两个目标：

```text
data safety
write availability
```

无法无条件同时最大化。

### 退化必须是显式政策

副本不可用时的选择：

| 政策 | write availability | acknowledged data protection |
|---|---:|---:|
| strict block | 降低 | 保持目标 |
| controlled async degrade | 保持 | 降低，必须告警/批准 |
| manual bypass | 操作者决定 | 可能破坏 guarantee |
| reject critical, allow lower tier | 分级 | 分级 |

不要让“超时太多，先改成 async”成为无记录的事故操作。需要：

```text
who may degrade
which traffic
maximum duration
customer/business notice
audit event
re-protection completion
```

### `nosync`、`nofailover` 与 placement intent

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

```text
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

某一时刻：

```text
replica connected
replay lag 0
```

不代表每次 commit 都等待它 durable flush。要看：

```text
synchronous_standby_names
transaction synchronous_commit
pg_stat_replication.sync_state
Patroni synchronous policy
```

同样，“两台 sync”也不证明它们位于独立 failure domain。

### candidate eligibility

选主至少考虑：

```text
member health
replication lag
timeline relationship
tags / maintenance
sync safety state
watchdog/fencing ability
DCS authority
```

本章 baseline：

```text
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 延迟、可用性和数据保护的交换 {#item-20-3-3}

### 一个不可回避的三角

粗略地：

```text
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：

```text
network jitter
standby fsync tail
checkpoint
CPU steal
queueing
replay pressure
```

需要看：

```text
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 替代参数清单

```yaml
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。我们选择：

```text
capture actual policy
accept planned switchover under explicit async exception
block zero-RPO inference
leave production sync decision pending
```

正式结果：

```text
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` 反推业务目标。

### 评审问题

```text
哪个成功应答必须存于几处？
这些“几处”是否独立？
同步确认等到 write、flush 还是 apply？
没有合格副本时谁决定停止写？
manual failover 能否绕过 safety？
timeout 后业务如何查结果？
延迟预算是否包含 remote tail？
```

任何一个“以后再说”，都会在事故中变成临时一致性模型。

## 小结

```text
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](https://www.postgresql.org/docs/18/warm-standby.html#SYNCHRONOUS-REPLICATION)
- [PostgreSQL 18：Replication Settings](https://www.postgresql.org/docs/18/runtime-config-replication.html)
- [PostgreSQL 18：WAL Settings](https://www.postgresql.org/docs/18/runtime-config-wal.html)
- [Patroni：Replication modes](https://patroni.readthedocs.io/en/latest/replication_modes.html)
- [本章实际策略合同](/labs/ch20/requirements.json)

---

[上一节：物理流复制](../02/) · [返回本章目录](../) · [下一节：选主、DCS 与防脑裂](../04/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
