Skip to content

quorum 同步提交 ​

quorum 同步提交是 PostgreSQL 10 引入的一种同步复制模式,通过 synchronous_standby_names 的 ANY 语法启用。它与传统的 FIRST(优先级)模式解决的是同一个问题——「提交要等几个备库确认」——但选择确认者的方式完全不同:FIRST 按顺序挑,ANY 任取足够数量。

这个差异在多副本环境下非常有意义。假设备库有 3 台,业务要求「至少 2 个副本确认才算提交成功」。用 FIRST 2 (s1, s2, s3) 的话,确认者总是 s1 与 s2,s3 只是候补;一旦 s1 出问题,整套逻辑要重新排位。用 ANY 2 (s1, s2, s3) 的话,任意 2 台确认即可,s3 从「候补」变成了「正式的参与者」。

本页会讲清 quorum 的语法、与 FIRST 的对比、容错能力、监控方式与局限。

基础概念 ​

quorum 这个词直接来自分布式共识领域,含义是「法定人数」:一个决策获得足够多的成员同意即可通过,而不要求特定成员同意。

概念说明
quorum法定人数,达到该数量的确认即可认为提交成功
ANY n (...)quorum 模式的语法,任意 n 个备库确认即可
num_sync需要确认的备库数量,即法定人数
sync_state = 'quorum'quorum 模式下已确认的同步备库状态
PRIORITY(FIRST)优先级模式,按列表顺序选确认者
QUORUM(ANY)法定人数模式,不关心具体是谁

quorum 与 PRIORITY 的定位差异 ​

PostgreSQL 官方文档把这两种模式称为 PRIORITY-based 与 QUORUM-based。它们的定位差异可以用一句话概括:

  • PRIORITY(FIRST):我要指定的人确认。适合有明确层级关系的场景(同机房优先、指定机房必须确认)。
  • QUORUM(ANY):我要足够多的人确认。适合多副本对等的场景(不关心具体是哪几台,只要够数)。

这个差异决定了两种模式在故障时的行为不同,也决定了它们的适用场景不同。

quorum 模式的角色状态 ​

在 FIRST 模式下,pg_stat_replication.sync_state 会出现 sync 与 potential;而在 ANY 模式下,参与同步的备库状态是 quorum。

sync_state出现在哪种模式含义
syncFIRST当前承担同步确认的备库
potentialFIRST排在后面、可补位的备库
quorumANY已确认的同步备库
async两者异步备库

这是区分两种模式最直观的方法:看到 sync/potential 就是 FIRST,看到 quorum 就是 ANY。

quorum 模式的本质:关注「数量」而非「身份」 ​

理解 quorum 的关键,是意识到它把「同步复制」的判定标准从「谁确认了」改成了「几个人确认了」。

这一点直接影响架构设计:

  • 在 FIRST 模式下,可以设计「必须有机房 A 的副本确认」这样的策略。
  • 在 ANY 模式下,无法保证「必须某台确认」,只能保证「总共够数」。

因此如果业务有「某个特定副本必须收到」的合规要求(例如数据必须落地到某个地域),FIRST 才是正确的选择,ANY 无法满足。

语法与用法 ​

ANY 是 synchronous_standby_names 的一种写法:

conf
# 语法:ANY num_sync (standby_name [, ...])

# 任意 2 个备库确认即可
synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'

# 任意 1 个备库确认即可(等价于 FIRST 1 的效果,但语义不同)
synchronous_standby_names = 'ANY 1 (standby1, standby2, standby3)'

在线生效:

sql
ALTER SYSTEM SET synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)';
SELECT pg_reload_conf();

-- 确认
SHOW synchronous_standby_names;

配合档位使用(ANY 与三个备库档位可以任意组合):

conf
# 任意 2 个备库确认「已重放」,即备库上立即可读
synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'
synchronous_commit = remote_apply

查看 quorum 状态:

sql
SELECT application_name, state, sync_state, sync_priority
FROM pg_stat_replication
ORDER BY application_name;
text
 application_name |   state   | sync_state | sync_priority
------------------+-----------+------------+---------------
 standby1         | streaming | quorum     |             0
 standby2         | streaming | quorum     |             0
 standby3         | streaming | quorum     |             0
(3 rows)

在 ANY 模式下 sync_priority 通常为 0,因为 quorum 不关心优先级。判断是否在同步,应看 sync_state 是否为 quorum。

详细说明 ​

语法细节与关键字的匹配规则 ​

ANY 与 FIRST 的语法结构完全一致,只是关键字不同:

text
语法: [FIRST | ANY] num_sync ( standby_name [, ...] )

几个语法要点:

要点说明
关键字大小写不敏感,ANY 与 any 等价
省略关键字's1, s2' 等价于 FIRST 1 (s1, s2),不是 ANY
* 通配'ANY 1 (*)' 匹配任意备库名
引号名字含特殊字符时需用双引号
列表中的备库可以混合存在,未列入的备库为 async

特别注意第二点:省略关键字时默认是 FIRST,而不是 ANY。这一点在从简化写法迁移到 quorum 写法时容易搞错,结果写成了 's1, s2, s3' 却发现行为依然是优先级模式。

quorum 与 FIRST 的完整对比 ​

维度FIRST(PRIORITY)ANY(QUORUM)
确认者的选择按列表顺序取前 num_sync 个可用的任意 num_sync 个确认即可
是否保证特定备库收到是否
备库故障时的行为顺延到列表中的下一个用剩余备库凑够数量
全部备库都在时的确认者列表前 num_sync 个最先确认的 num_sync 个(通常是最快的)
sync_state 取值sync / potentialquorum
sync_priority反映列表顺序通常为 0
引入版本早期版本PostgreSQL 10
延迟特征稳定(总是同一批备库)可能波动(谁先确认算谁)
适用场景有明确层级(同机房优先、指定机房必须确认)多副本对等,追求更高容错

从「延迟特征」这一行可以看出一个重要的实践差异:FIRST 的延迟是可预期的,因为它总是等同一批备库;ANY 的延迟取决于哪几台先确认,如果备库分布在不同延迟的机房,确认者可能在不同时刻变化,从而导致延迟抖动。

这一点在跨机房部署时尤其明显:ANY 2 (local1, local2, remote1) 中,如果 local1 与 local2 都正常,确认者通常是这两台,延迟低且稳定;但如果 local1 故障,确认者可能变成 local2 + remote1,延迟立刻上升到跨机房水平。使用 ANY 时,必须对「故障后延迟上升」有预期。

故障切换时的行为差异 ​

用一个「一主三备、要求 2 个确认」的例子来对比两种模式在故障时的行为。假设备库为 s1、s2、s3,其中 s1 延迟最低、s3 延迟最高。

FIRST 2 (s1, s2, s3)

状态确认者说明
全部正常s1, s2s3 是 potential
s1 故障s2, s3顺延,s2 变 sync,s3 变 sync
s1、s2 故障仅 s3凑不够 2 个,写入阻塞

ANY 2 (s1, s2, s3)

状态确认者说明
全部正常最先确认的 2 台通常是最快的两台
s1 故障s2, s3剩余两台确认
s1、s2 故障仅 s3凑不够 2 个,写入阻塞

两种模式在「可用备库少于 num_sync」的极端情况下表现一致:都会导致写阻塞。它们的差异主要体现在「可用备库恰好等于 num_sync」以及「有备库延迟差异」时的行为:FIRST 会按顺序重新排位,ANY 则直接使用当前可用的。

quorum 的容错能力 ​

quorum 模式的核心价值是容错能力的可预期性。可以用一个简单的公式来理解:

text
可容忍故障的备库数 = 备库总数 - num_sync

以 3 台备库为例:

配置可容忍故障备库数说明
ANY 1 (s1, s2, s3)2只要还有 1 台活着就能提交
ANY 2 (s1, s2, s3)1需要至少 2 台活着
ANY 3 (s1, s2, s3)0任何一台故障都会写阻塞

这个公式说明:num_sync 越大,容错能力越弱,但数据冗余度越高。选择 num_sync 就是在「容错」与「冗余」之间做权衡。

一个常见的误判是认为「quorum 模式在备库减少时会自动降低 num_sync,从而避免写阻塞」。需要明确的是:num_sync 是配置值,不会因为备库减少而自动降低。如果业务要求「至少有 2 个副本」,那么在只剩 1 个副本时,正确的行为就是拒绝写入而不是降级——这正是同步复制「宁可不返回,也不违反承诺」的设计取向。

关于 quorum 模式在可用备库不足时的行为

在 quorum 模式下,若可用同步备库数量少于 num_sync,PostgreSQL 不会永久阻塞提交,而是按当前可用备库继续。这一行为涉及具体版本的实现细节,实际部署前请以所用版本的官方 synchronous_standby_names 文档与实测结果为准,不要仅依据本文档的表述做容量与容错设计。

需要强调的是:无论哪种模式,num_sync 大于备库总数都应当被视为配置错误。设计时请保证 num_sync 小于备库总数,留出容错余量。

备库增多时的容错能力 ​

quorum 模式的一个好处是「备库增多时容错能力线性提升」,而配置可以保持不变:

备库总数配置可容忍故障数说明
2ANY 1 (...)1常见的一主一备
3ANY 2 (...)1常见的一主三备(含 1 台容灾)
5ANY 3 (...)2大规模集群,多数派确认

对比 FIRST 模式:备库从 3 台增加到 5 台时,FIRST 2 的确认者仍然是列表最前的两台,新增的备库只是候补,容错能力并没有实质提升(因为容错能力取决于「列表前 num_sync 台之外还剩多少台」,这个数量确实增加了,但确认者的身份集合没有变化)。

这个差异的实践含义是:在多副本对等的集群(例如跨三机房各一台)中,ANY 是更自然的选择;而在「有明确主备层级」的场景(例如同机房两台 + 异地一台),FIRST 更合适。

与复制槽的配合 ​

复制槽与 quorum 模式的组合是推荐做法:

机制作用
复制槽主库为每台备库保留 WAL,避免备库断连后需要重建
quorum 同步保证 num_sync 个备库确认后才返回成功

在 quorum 模式下,如果某台备库断连又重连,复制槽能保证它从断点继续拉取 WAL,快速回到 quorum 状态。如果没有复制槽,备库断连时间较长后可能需要重建,重建期间的备库不参与 quorum,会降低容错余量。

每个备库的复制槽配置方式:

conf
# 备库 postgresql.conf
primary_conninfo = 'host=10.0.0.10 port=5432 user=repl application_name=standby1'
primary_slot_name = 'standby1_slot'

提交等待的监控 ​

在 quorum 模式下监控提交等待,关注三个层面。

第一层:主库侧的等待会话

sql
SELECT count(*) AS waiting, max(now() - query_start) AS longest_wait
FROM pg_stat_activity
WHERE wait_event = 'SyncRep';
text
 waiting |     longest_wait
---------+----------------------
      14 | 00:00:00.284611
(1 row)

第二层:备库的 quorum 状态

sql
SELECT count(*) FILTER (WHERE sync_state = 'quorum') AS quorum_standbys,
       count(*) FILTER (WHERE sync_state = 'async')  AS async_standbys
FROM pg_stat_replication;
text
 quorum_standbys | async_standbys
-----------------+----------------
               2 |              1
(1 row)

quorum_standbys 应与配置的 num_sync 一致。如果它小于 num_sync,说明容错余量已经耗尽,随时可能写阻塞。

第三层:备库的延迟差

sql
SELECT application_name, write_lag, flush_lag, replay_lag
FROM pg_stat_replication
ORDER BY replay_lag NULLS LAST;
text
 application_name |  write_lag   |  flush_lag   |  replay_lag
------------------+--------------+--------------+--------------
 standby1         | 00:00:00.000 | 00:00:00.001 | 00:00:00.002
 standby2         | 00:00:00.001 | 00:00:00.002 | 00:00:00.003
 standby3         | 00:00:00.002 | 00:00:00.006 | 00:00:00.012
(3 rows)

在 ANY 模式下,确认者通常是延迟最低的两台。如果低延迟备库出问题,确认者会切到延迟较高的组合,提交延迟随之上升。因此监控「各备库延迟差」可以提前发现「故障后延迟会上升多少」。

实战配置:一主三备的 quorum 部署 ​

一个典型的 quorum 部署配置如下。

主库:

conf
# 主库 postgresql.conf
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1GB

# 任意 2 个备库确认即可(可容忍 1 台备库故障)
synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'
synchronous_commit = on

备库(以 standby1 为例,另外两台同理改名字):

conf
# 备库 postgresql.conf
primary_conninfo = 'host=10.0.0.10 port=5432 user=repl password=*** application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on

创建复制槽与复制用户(主库执行):

sql
-- 复制用户
CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD '***';

-- 为每台备库创建物理复制槽
SELECT pg_create_physical_replication_slot('standby1_slot');
SELECT pg_create_physical_replication_slot('standby2_slot');
SELECT pg_create_physical_replication_slot('standby3_slot');

验证:

sql
SELECT slot_name, slot_type, active FROM pg_replication_slots;
text
   slot_name   | slot_type | active
---------------+-----------+--------
 standby1_slot | physical  | t
 standby2_slot | physical  | t
 standby3_slot | physical  | t
(3 rows)

quorum 的局限 ​

quorum 虽然容错性更好,但有几个必须清楚的局限:

局限说明
不保证特定备库收到无法满足「必须落地到某地域」的合规要求
延迟可能波动确认者是谁取决于谁先回应,延迟不如 FIRST 稳定
无法避免写阻塞可用备库少于 num_sync 时仍会阻塞
不解决脑裂quorum 只约束提交等待,不做领导者选举
不替代共识层多主/自动故障转移仍需 etcd 等外部共识组件

其中第二条容易被忽略。在 FIRST 模式下,可以确定「确认者永远是最快那两台」,因此延迟可预期;在 ANY 模式下,确认者可能是「当前 respond 最快的那两台」,而在高负载或网络抖动时,这个集合可能变化,导致延迟出现阶跃。在做延迟预算时,ANY 需要留更大的余量。

与自动故障转移的配合 ​

quorum 模式与自动故障转移方案是两个不同层面的机制,需要配合而不是互相替代:

层面机制解决的问题
提交确认quorum(ANY)提交要等几个备库确认
领导者选举Patroni + etcd/Consul谁是主库
流量切换VIP / HAProxy / DNS应用连接指向谁

一个常见的误解是「用了 quorum 就不需要 etcd 了」。实际上 quorum 只解决「提交等待」的问题,完全不涉及「主库挂了谁来接管」。自动故障转移仍然需要外部共识层来判断主库状态并做选举。

Patroni 等工具对同步复制有自己的管理方式,配置项通常包括:

配置项作用
synchronous_mode开启同步复制
synchronous_mode_strict严格模式,不允许降级为异步
synchronous_node_count对应 num_sync

在这种托管环境中,quorum 模式是否可用取决于工具是否暴露对应配置。使用前应查阅所用工具版本的文档,确认它是否支持生成 ANY 形式的 synchronous_standby_names。

quorum 与「多数派」的区别 ​

一个需要注意的概念澄清:PostgreSQL 的 quorum 同步提交不等于分布式共识里的「多数派(majority)」概念。

概念含义
PostgreSQL quorum任意 num_sync 个备库确认即可,num_sync 由用户指定
多数派超过半数的节点同意,通常为 floor(n/2) + 1

因此 ANY 2 (s1, s2, s3) 并不是「多数派」——它只要求 2 台,而 3 台中的多数派也是 2 台,这看起来巧合,但 ANY 1 (s1, s2, s3) 与多数派无关。PostgreSQL 不强制 num_sync 必须过半,用户可以设置成任意值。

这一点在架构讨论中容易造成混淆。当有人说「我们用 quorum 保证多数派」时,应确认他指的是「配置了 ANY」还是「配置了过半数的 num_sync」——这两者不是同一件事。

两种模式的选型判断 ​

判断问题回答建议模式
是否要求特定备库(或特定机房)必须收到?是FIRST
是否要求确认者身份固定、延迟可预期?是FIRST
是否有多台对等备库、只关心数量?是ANY
是否希望新增备库自动提升容错能力?是ANY
备库延迟差异是否很大?是FIRST(避免确认者漂移)

这张表可以作为选型的快速判断依据。总结起来:「指定谁」用 FIRST,「够数就行」用 ANY。

实战示例 ​

示例一:配置 quorum 并验证状态 ​

sql
ALTER SYSTEM SET synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)';
SELECT pg_reload_conf();
SHOW synchronous_standby_names;
text
          synchronous_standby_names
--------------------------------------------
 ANY 2 (standby1, standby2, standby3)
(1 row)
sql
SELECT application_name, state, sync_state
FROM pg_stat_replication
ORDER BY application_name;
text
 application_name |   state   | sync_state
------------------+-----------+------------
 standby1         | streaming | quorum
 standby2         | streaming | quorum
 standby3         | streaming | quorum
(3 rows)

三台备库都显示 quorum,说明它们都在参与 quorum 确认。

示例二:观察备库故障后的容错 ​

停掉 standby1:

bash
pg_ctl -D /var/lib/pgsql/18/data stop -m fast

在主库观察:

sql
SELECT count(*) FILTER (WHERE sync_state = 'quorum') AS quorum_standbys,
       array_agg(application_name) FILTER (WHERE sync_state = 'quorum') AS members
FROM pg_stat_replication;
text
 quorum_standbys |        members
-----------------+------------------------
               2 | {standby2,standby3}
(1 row)

仍有两台处于 quorum,满足 num_sync = 2,写入正常进行。这说明 ANY 2 可以容忍 1 台备库故障。

示例三:验证故障会消耗容错余量 ​

继续停掉 standby2:

bash
pg_ctl -D /var/lib/pgsql/18/data stop -m fast
sql
SELECT count(*) FILTER (WHERE sync_state = 'quorum') AS quorum_standbys,
       array_agg(application_name) FILTER (WHERE sync_state = 'quorum') AS members
FROM pg_stat_replication;
text
 quorum_standbys |  members
-----------------+------------
               1 | {standby3}
(1 row)

现在只剩 1 台 quorum 备库,小于 num_sync = 2,容错余量已经耗尽,写入面临阻塞风险。这个查询非常适合做成监控指标——当 quorum_standbys < num_sync 时立刻告警。

示例四:对比 FIRST 与 ANY 的确认者 ​

先用 FIRST 2 (standby1, standby2, standby3):

sql
ALTER SYSTEM SET synchronous_standby_names = 'FIRST 2 (standby1, standby2, standby3)';
SELECT pg_reload_conf();
sql
SELECT application_name, sync_state, sync_priority
FROM pg_stat_replication ORDER BY sync_priority DESC;
text
 application_name | sync_state | sync_priority
------------------+------------+---------------
 standby1         | sync       |             1
 standby2         | sync       |             2
 standby3         | potential  |             3
(3 rows)

切到 ANY:

sql
ALTER SYSTEM SET synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)';
SELECT pg_reload_conf();
sql
SELECT application_name, sync_state, sync_priority
FROM pg_stat_replication ORDER BY application_name;
text
 application_name | sync_state | sync_priority
------------------+------------+---------------
 standby1         | quorum     |             0
 standby2         | quorum     |             0
 standby3         | quorum     |             0
(3 rows)

对比可以看到:FIRST 下 standby3 是 potential、有优先级编号;ANY 下三台都是 quorum、优先级都是 0。

示例五:测量 quorum 下的提交延迟 ​

sql
SET synchronous_commit = 'on';
bash
pgbench -c 32 -j 8 -T 30 -f update.sql pgbench
text
latency average = 3.628 ms
tps = 8821.47 (without initial connection time)

停掉延迟最低的 standby1 后重测:

bash
pgbench -c 32 -j 8 -T 30 -f update.sql pgbench
text
latency average = 5.911 ms
tps = 5413.02 (without initial connection time)

延迟从 3.6ms 上升到 5.9ms,反映了「确认者从低延迟组合切换到较高延迟组合」的影响。这正是前面提到的「ANY 的延迟可能波动」——做容量规划时,应按「故障后的延迟」而不是「正常时的延迟」来估算。

注意事项 ​

ANY 只保证数量,不保证特定备库收到。 如果有「必须落地到某地域」的合规要求,应使用 FIRST 而不是 ANY。

num_sync 不会随备库减少而自动降低。 可用备库少于 num_sync 时会导致写阻塞。设计中必须保证 num_sync 小于备库总数,留出容错余量。

省略关键字时的默认是 FIRST。 写成 's1, s2, s3' 等价于 FIRST 1 (s1, s2, s3),不是 quorum。启用 quorum 必须显式写 ANY。

ANY 模式下的确认者可能漂移。 在备库延迟差异较大的环境中,确认者集合可能随负载与网络状况变化,导致提交延迟出现阶跃。延迟预算要留更大余量。

sync_state 在 ANY 模式下是 quorum,不是 sync。 监控脚本必须把 quorum 纳入「同步备库」的判断,否则会误报「同步复制已失效」。

sync_priority 在 ANY 模式下通常为 0。 不要用优先级字段判断 quorum 的同步状态,应使用 sync_state。

quorum 不等于多数派。 num_sync 由用户指定,PostgreSQL 不强制它过半,因此不要把它当作分布式共识里的多数派机制。

quorum 不解决脑裂与领导者选举。 它只约束提交等待,自动故障转移仍需 etcd、Consul 等外部共识层。

使用托管方案时先确认工具是否支持 ANY。 部分自动化工具可能只生成 FIRST 形式的配置,需要查阅对应版本文档后再决定。

变更 num_sync 会改变数据冗余等级。 从 ANY 2 改到 ANY 1 会降低冗余度,从 ANY 1 改到 ANY 2 会降低容错能力。这类变更应当作为架构变更走评审流程。

最佳实践 ​

  1. 先明确「是否需要保证特定备库收到」。 需要就用 FIRST,不需要且备库对等就用 ANY,这是选型的第一分叉点。

  2. num_sync 始终小于备库总数。 这是保证「单台故障不阻塞写入」的硬性前提,不要为了追求冗余而把它设满。

  3. 对「可用 quorum 备库数」设置告警。 当 count(sync_state = 'quorum') < num_sync 时立刻告警,这是写入即将阻塞的最早信号。

  4. 按故障后的延迟做容量规划。 ANY 模式的确认者会漂移,容量估算应基于「延迟较高的备库组合」而不是正常状态下的最优组合。

  5. 在备库延迟差异大的环境中优先选 FIRST。 如果备库分布在延迟相差一个数量级的机房,FIRST 能提供更稳定的延迟表现。

  6. 同时配置复制槽。 复制槽保证备库断连后能快速回到 quorum 状态,避免长期处于容错余量不足的状态。

  7. 把 quorum 状态纳入统一的同步复制监控口径。 监控脚本应把 sync 与 quorum 都视为「同步备库」,避免不同部署模式使用不同脚本导致的漏报。

  8. 定期演练多台备库同时故障的场景。 验证在容错余量耗尽时的表现(写阻塞、告警是否触发),并确认应急摘除流程可用。

  9. 不要在跨机房场景下无脑使用 ANY。 如果确认者可能漂移到跨机房备库,提交延迟会突然上升到 RTT 级别。跨机房场景建议「同机房同步 + 跨机房异步」的分层设计。

  10. 文档中明确记录模式与 num_sync 的选型理由。 「为什么用 ANY 2 而不是 FIRST 1」这类决策应留痕,避免后续维护者随意改动。

  11. 升级到 PostgreSQL 10 及以上版本后才可使用 quorum。 在 9.x 版本上没有 ANY 语法,迁移时应规划好版本升级路径。

  12. 把「临时摘除同步复制」作为兜底手段保留。 无论用哪种模式,应急手册里都应有 ALTER SYSTEM SET synchronous_standby_names = ''; 加 pg_reload_conf() 的快速恢复步骤,并附「事后必须改回」的检查项。

下一节

继续学习:逻辑复制原理

参考链接 ​