# PostgreSQL 高可用到底怎么做？

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

---

七八年前，老冯手里维护着一百套大规模 PostgreSQL 集群，两百多台顶配物理机，开始做高可用方案选型。
那段时间我把市面上能叫得出名字的方案都翻了一遍：Patroni、Corosync + Pacemaker、repmgr、Stolon、PAF、pgpool-II……

最后选了 Patroni。基于它做 HA，上线后效果很稳：这些年碰到几十次真实硬件故障，RTO 基本都在二三十秒区间。
最爽的是：半夜告警响了也不用爬起来抢救，流量自动切换，第二天起床再慢慢研究和调整就好了。

回头看，这个选择属于 “少走七年弯路”。今天无论你看传统 Linux 发行版方案（Pigsty、Percona、AutoBase 等），
还是 K8S Operator（Crunchy PGO 等），主流 PG 发行版高可用几乎都绕不开 Patroni。
Patroni 在 GitHub 上的 Star (8.1K) 也超过其他这些 HA 组件的总和。

那么，为什么是 Patroni 成为了事实标准？它到底好在哪里？
PostgreSQL 高可用到底应该怎么做？今天我们就来聊聊这个话题。

------

## 可用性，RTO与RPO

**可用性** （Availability）是一个服务指标，一般用 “服务可用时间/总时间窗口” 的百分比来计算，时间窗口一般是整年或者整月。
通常 99.99% 以上的可用性（4个9）被称作高可用 —— 意味着年度故障预算 52 分钟，或月度故障预算 4.3 分钟。

但可用性是业务连续性指标，不是数据库的 **技术能力**。一个经典误区是：你完全可以单凭运气做到 100% 的可用性，这很常见。
云厂商承诺几个9的SLA，本质上也不是历史战绩，而是代金券对赌协议。

真正重要的是，发生故障的频率与恢复时间。对于数据库来说，你控制不了故障发生的频率（MTBF）；
但你能控制的是：发生故障后最多丢多少数据，以及要多长时间恢复。这就是两个核心可靠性指标 —— RPO 与 RTO：

**RPO（Recovery Point Objective，恢复点目标）**
: 定义了在主库发生故障时，允许丢失的最大数据量。

**RTO（Recovery Time Objective，恢复时间目标）**
: 定义了在主库发生故障时，系统恢复写入能力所需的最长时间。

RTO 和 RPO 代表了真正 **扛事的能力**，也是我们考察高可用方案的关键所在。

那么 RPO / RTO 要多好才算好？这里有几个相关的国际/国家标准，规定了各个行业要求的 RPO / RTO 水平。
比如 SHARE-78，GB/T 20988-2025，SOX / HIPPA / Basel III 都对 RTO/RPO 提出了合规要求。
于今年元旦开始实行的国内的 《网络安全技术 信息系统灾难恢复规范》将灾难恢复划分为六个等级：

| 等级 | 名称          | RTO         | RPO      |
|:---|:------------|:------------|:---------|
| 1级 | 基本支持        | > 7天        | 1天 ~ 7天  |
| 2级 | 备用场地支持      | > 24小时      | 1天 ~ 7天  |
| 3级 | 电子传输 + 部分设备 | 12小时 ~ 24小时 | 数小时 ~ 1天 |
| 4级 | 电子传输 + 完整设备 | 数小时 ~ 12小时  | 数小时      |
| 5级 | 实时传输 + 完整设备 | 数分钟 ~ 2小时   | 0 ~ 30分钟 |
| 6级 | 零丢失 + 远程集群  | 分钟级         | 0        |

最高等级的容灾要求，通常要求 RTO 在分钟级（几十秒的量级），RPO = 0 不丢失数据。
十几年前，你可能要花几百万上千万采购专有软硬件来满足这样的要求。
而在 2026 年的当下，有了 Patroni + PostgreSQL，实现这种容灾水平的软件成本已经无限接近于零。

但显然，因为信息不对称，很多人并不知道这件事。
所以今天我就来给大家讲讲 PostgreSQL 高可用的 SOTA 事实标准 —— [**Patroni**](https://patroni.readthedocs.io/en/)。

--------

## 太长不看

Patroni 在合理配置的情况下，可以轻松做到 **RTO < 30s，RPO = 0** 的水平，这是经过理论推演与实战检验的结果。

在 RPO 上，Patroni 可以实现 Oracle 最大性能/最大可用/最大保护模式，甚至提供了比 Oracle 最大保护模式更强的数据一致性选项。
在 RTO 上，Patroni 可以在常规硬件上实现端到端 RTO < 30s 的水平，包含从故障检测，主从切换，到负载均衡器健康检查的全链路耗时。
接下来，我们将详细介绍高可用中 RPO 与 RTO 的利弊权衡。

--------

## RPO 利弊权衡

[**RPO**](https://pigsty.cc/docs/concept/ha/rpo/) 定义了主库故障时允许丢失的最大数据量。对于金融交易这类数据完整性至关重要的场景，通常要求 RPO = 0，即不允许任何数据丢失。

然而更严格的 RPO 指标是有代价的：它会引入更高的写入延迟，降低系统吞吐量，并且存在从库故障导致主库不可用的风险。
因此对于常规场景，通常可以接受一定量的数据丢失（例如不超过 1MB），以换取更高的可用性与性能。

在异步复制场景下，从库和主库之间会存在一定的复制延迟（取决于网络和吞吐量，正常在 10KB～100KB / 100µs～10ms 的数量级）。
这意味着主库故障时，从库可能还没有完全同步最新数据。此时如果发生故障切换，新主库可能会丢失一些尚未复制的数据。

![rpo-diagram.webp](rpo-diagram.webp)

### 实现原理

Patroni 提供了一个参数 `maximum_lag_on_failover`，用于控制潜在数据丢失量的上限，默认为 1048576 （1MB）。
这意味着自动故障转移时，最多可以容忍 1MB 的数据丢失。当主库宕机时，如果有任何一个从库的复制延迟在这个值以内，Patroni 将自动提升该从库为新主库。

然而当所有从库的复制延迟都超出这个阈值时，Patroni 将拒绝进行自动故障切换以避免数据丢失。
此时需要人工介入决策：等待主库恢复（可能永远不会恢复），还是接受数据损失并强制提升一个从库。

这就引入了第一项利弊权衡：你需要根据业务需求配置这个值，在 **可用性** 和 **一致性** 之间进行 利弊权衡。
增大这个值可以提高自动故障切换的成功率（降低不可用时长），但也会增加潜在的数据丢失量上限。

在不允许任何数据丢失的场景下，你可以使用 Patroni 的同步模式与严格同步模式，来确保 RPO = 0。

### Oracle 类比

对于熟悉 Oracle 的用户来说，Patroni 的复制模式可以类比 Oracle Data Guard 提供的三种数据保护模式：最大性能、最大可用、最大保护。

![protect-mode.webp](protect-mode.webp)

实际上，通过配置 Patroni 和 PostgreSQL，你甚至可以实现比 Oracle 最大保护模式更强的数据一致性选项。
例如 Oracle Data Guard 的最大保护模式只要求一个同步从库确认写入，而 Patroni 可以配置多个同步从库确认写入，
甚至确认重放完成（`remote_apply`），来进一步提高数据持久性与一致性。

![rpo-param.webp](rpo-param.webp)

> JEPSEN 提供了一个非常罕见的例子，我们会有一篇专门的文章详细介绍。

对于绝大多数业务来说，异步复制（最大性能）模式提供的 RPO 保证已经足够。
对于对数据完整性要求极高的场景，可以使用最大可用 / 最大保护模式来确保 RPO = 0。

--------

## RTO 利弊权衡

[**RTO**](https://pigsty.cc/docs/concept/ha/rto/) 定义了主库故障时，系统恢复写入能力所需的最长时间。

当主库故障时，整个恢复流程涉及多个阶段：检测故障、DCS 锁过期、新主选举、执行 promote、负载均衡器感知新主库。因此不同于 RPO，RTO 不可能等于零。

而且需要注意，**RTO 并非越小越好**。更短的 RTO 意味着缩短各阶段的超时时间，这会使集群对网络抖动更加敏感，从而增加误切风险。
RTO 设置的太小，切换耗时虽然变短，但是误切概率上升了，切换频率增加，整体可用性反而下降了。

这就引出了第二项利弊权衡，你需要根据实际网络条件选择合适的配置，在 **恢复耗时** 与 **误切概率** 之间取得平衡：
网络质量越差，越应该选择保守的配置；网络质量越好，越可以选择激进的配置。

### 关于 RTO 的营销话术

因为 RPO 这个指标没什么好吹的，RTO 已经沦为数据库营销吹牛的重灾区。有时候是拿特例场景乐观路径来以点盖面，或者在驱动 / 连接池层面排队并用统计口径做文章。因此讨论 RTO 的时候，需要明确几个因素：

**故障域**
:是计算节点故障，还是存储故障、网络故障？

**测量口径**
:可用标准是数据库可写入，还是 LB，APP，连接池/驱动层可用？

**统计量**
:使用的是最优值、最差值，还是平均值与中位数？

**网络条件**
:是同机柜、同机房、同城，还是跨大洲的全球复制？

通常来说，在同机柜网络条件中，常见故障路径下，**RTO < 30s** 算是业界顶级水平。
如果有人不带场景、故障域、统计口径说自己 RTO < 10 秒，通常可以判定为吹牛。

严肃的 RTO 的讨论非常复杂，在下面，我们会讨论四种典型网络条件下的参数配置，两种主要故障路径下的 RTO 拆解，以及最优，平均，最劣三种情况；
并使用更为严格的 HAPRXOY 端侧接受连接写入作为 RTO 的测量口径，如果没有特别解释，我们讨论的是用于兜底的 **最劣** 情况，而非平均或最优情况。

### 架构原理

RTO 无法脱离架构，场景，环境，资源来讨论，因此我们需要先来介绍一下基于 Patroni / Etcd / HAProxy 的经典高可用架构，在这个架构中：

![ha.webp](ha.webp)

- PostgreSQL 使⽤标准流复制搭建物理从库，主库故障时由从库接管。
- Patroni 负责管理 PostgreSQL 服务器进程，处理高可用相关事宜。
- Etcd 提供分布式配置存储（DCS）能力，并用于故障后的领导者选举
- Patroni 依赖 Etcd 达成集群领导者共识，并对外提供健康检查接口。
- HAProxy 对外暴露集群服务，并利⽤ Patroni 健康检查接口，自动分发流量至健康节点。

在这套架构中，高可用 RTO 主要取决于 Patroni 参数，次要取决于 Haproxy 参数。

### 参数配置

Patroni 中关于 RPO 的核心参数只有三个，但考虑 RTO 时，总共要纳入的参数有 10 个：
Patroni 5 个，HAProxy 健康检查 5 个。这十个参数的组合决定了 RTO 的表现。

请注意，默认参数的表现并不是最优的。我在长期实践中提出了四组不同网络条件下的参数优化配置：fast、normal、slow、safe。

![rto-mode.webp](rto-mode.webp)

四种模式实际上对应着四组不同的参数配置，如下所示：

![rto-param.webp](rto-param.webp)

这四种模式下，RTO 的最坏，最好，平均表现水平如下图所示。

![rto-chart.webp](rto-chart.webp)

我们以最坏口径作为讨论的基准，
在默认配置下 RTO < 45s，最优模式下 RTO < 30s。
更宽容的模式则设置有 90s / 150s 的 RTO 上限目标。

这里特别需要提到的是，市面上绝大多熟 PG 高可用方案几乎都没有修改 Patroni 默认参数，
尽管默认参数在常规被动故障切换中提供 RTO < 45 秒的表现，但默认 `primary_start_timeout` 的 300 秒配置，
会导致 PG 主库崩溃重新拉起这个故障场景中，最劣表现高达 324 秒，违背常规的 RTO 目标。

如果你自己手搓 Patroni 高可用，这一点务必注意。

### 故障路径

那么这个图里的数据是怎么计算得到的呢？这里我们就要讨论 **故障路径** 了。

在 Patroni 这套高可用架构中，有 10 种典型故障：节点宕机 / 假活、PG 崩溃 / 拒绝连接 / 假活、Patroni 崩溃 / 假活、主库 / DCS 网络中断、存储故障等等。
这些故障总体可以划分为五条 RTO 拆解路径，在讨论最坏情况时，归并到两条典型的故障路径：

[**被动检测**](https://pigsty.cc/docs/concept/ha/failure/passive)
: Patroni 挂了或者网络隔离，主库无法续租，触发集群选举

[**主动检测**](https://pigsty.cc/docs/concept/ha/failure/active)
: Patroni 活着，尝试修复 PG 挂了这种问题，超时后触发集群选举

这两种故障路径殊途同归，可以用下面的流程图表示：

![rto-diagram.webp](rto-diagram.webp)

这两条故障路径的 RTO 计算方式有差异，下面会简要介绍，详细完整的分析报告请参阅下面的文档：
http://pigsty.cc/docs/concept/ha/failure/

![active.webp](active.webp)

![passive.webp](passive.webp)

这里的 RTO 时序拆解，给出了 RTO 的下界和上界。
我们在设定的时候，以上界最悲观的情况设置，确保满足最严格的容灾等级要求。

考虑 **平均** 情况的话 fast 档位在 23-24 秒，默认 norm 档位在 34-35 秒的水平。

![rto-mode-param.webp](rto-mode-param.webp)

### 关于 RAC 与分布式数据库

有些数据库承诺非常低的 RTO，甚至号称秒级切换、RTO = 0。这些方案通常使用共享存储 RAC 架构，或者分布式 Raft/Paxos 协议。
但仔细推敲就会发现，这些声称往往只针对 **特定故障域** 成立，而且会因为架构上的利弊权衡引入其他局限性。

以 Oracle RAC 为例，多个计算节点访问同一个底层磁盘阵列。在实例故障时确实可以快速切换，但当底层存储出现单点故障时，RTO 就直接炸了 —— 所有节点一起完蛋。
这本质上是把复杂度和风险下推到存储层，逼着你购买价格高昂的企业级 SAN 存储来兜底。
而且真要做跨区域容灾，还是得靠 Data Guard 这类主从复制技术。然后 RTO 又回到几十秒的量级了。
所以 Oracle RAC 的营销宣传在我看来极具误导性。

一些 NewSQL 分布式数据库稍微诚实一点。比如 CockroachDB 用 Raft 协议管理多节点集群，在主要故障场景下号称 RPO = 0、RTO < 9s。
这个数字是可信的，但代价是什么？翻了几倍的写入延迟，几分之一的性能吞吐，以及更高的架构/运维复杂度。
关于这一点，老冯在《分布式数据库是伪需求吗》中详细探讨过（还有《DDIA 第六章：复制》）。
说到底一切都是利弊权衡，只要你不在乎代价，AWS 开源的 pgactive PG 扩展号称能把 RTO 做到亚秒级。

相比之下，无共享架构（Shared Nothing）的思路就清爽得多：显式管理多套存储副本，每个节点都是自治的，没有存储单点。
共享存储方案很难水平扩展，而无共享架构可以轻松拉出几十个从库 —— OpenAI 1 主 40 从 ，我们在探探1主32从的 PG 集群就是这么玩的。
这也是为什么过去二十年，无共享架构逐渐成为数据库高可用的主流选择。

老冯自己的看法是，这年头鼓吹共享存储高可用方案，有较大概率是为了给硬件/云盘带货。
老冯对这种事向来不感冒，本文就不进一步具体展开批判了。
但也许后面会专门写一篇，聊聊 RAC 与分布式数据库的真实表现与实际代价，
以及 Corosync + Pacemaker 这种极其繁琐的老古董 PG 高可用方案为什么该进博物馆了。

--------

## 关于实操

聊完原理，说说落地。我知道很多人看到这里会想：道理我都懂，但让我自己一台一台去配 Etcd、Patroni、HAProxy，俺做不到啊。

放心，老冯自己也不会干这种傻事。

我把这套 PG 高可用方案做成了开源免费、一键部署的完整解决方案：[**Pigsty**](https://pigsty.cc)。

![iac.jpg](iac.jpg)

你也可以用别的 —— AutoBase，Percona，各种 K8S PG Operator 也大同小异。
反正高可用部分的核心架构基本都是一样的 Patroni + etcd + Haproxy。

不过，很多用着 patroni 的方案几乎都清一色用着默认参数。
看起来没人认真优化过，也没人做过精细的 RTO 时序分析，也许是放到“企业级”方案里去了吧。
而 Pigsty 在大规模生产环境中实战打磨出来，针对不同网络条件提供了多种 RTO/RPO 策略可选，
除此之外，还内置了连接池、自动故障切换、完整的监控告警体系 —— 开箱即用不操心。

另外提醒一句：光有高可用还不够。硬件故障 Patroni 能扛，但误删数据、逻辑错误这类场景，还得靠 PITR 来兜底。
所以今天讲的是 PostgreSQL 高可用的事实标准——Patroni，后面还会介绍备份恢复的事实标准 —— pgBackRest。

朋友们，别折腾手搓 PG 高可用了！土法自建对技术成长和业务发展都没啥帮助 —— 把原理搞明白会用就行。
与其重复造轮子，不如折腾折腾怎么用好 PG 本身，这里能玩的扩展太多了，可比折腾什么 HA PITR 有意思多了。

--------

## 小结

通过精细的参数调优，Patroni 的 RTO 上界可以被精确控制在不同档位。
在常规网络环境下，30 秒的 RTO 水平触手可及。
这些年我在生产环境中经历的几十次真实故障切换，监控数据显示 RTO 稳定在 20～30 秒，完全满足最严苛的金融级容灾要求。

七年前做这个选型时，Patroni 还是个小众方案。当时不少人觉得它不够"企业级"，不如商业方案有保障。
现在再看，那些迷信复杂架构和昂贵软件的团队，要么还在为高可用焦头烂额，要么早就悄悄换成了 Patroni。

技术选型这件事，从来不是看谁更复杂、谁更贵，而是看谁能真正把问题解决掉。
Patroni 用最简洁的架构实现了最可靠的效果，这就是它成为事实标准的原因。
