PostgreSQL 本机高可用性:Patroni、流复制和生产故障转移策略
PostgreSQL 具有 Patroni、流复制和 HAProxy 的本机 HA
运行单个 PostgreSQL 实例非常简单,直到第一次计划外中断提醒您数据库的价值取决于其可用性。磁盘故障、内核恐慌、网络分区和拙劣的升级并不是理论上的风险——它们是在足够长的时间内运行的确定性。 PostgreSQL 不附带内置的自动故障转移,但它确实提供了构建高可用集群所需的所有复制原语。 Patroni 是 Zalando 维护的开源 HA 框架,它将这些原语编排到生产级故障转移系统中,该系统已经在全球数千个 PostgreSQL 集群中进行了大规模的测试。
本文是深入的工程指南。我们将介绍 PostgreSQL 流复制(同步和异步)、Patroni 架构和配置、作为分布式配置存储的 etcd、用于具有读写分离的连接路由的 HAProxy、用于连接池的 PgBouncer、WAL 归档和时间点恢复、用于选择性数据同步的逻辑复制、用于初始备用配置的 pg_basebackup、作为 Patroni 替代方案的 repmgr、AWS、Azure 的云特定部署模式以及GCP、使用 Rancher 和 Longhorn 进行裸机 k3s 部署、使用 pg_stat_replication 和 Prometheus/Grafana 进行监控、切换与故障转移程序、裂脑预防、生产调整以及用于故障转移验证的混沌工程。
PostgreSQL 流复制基础知识
流复制是 PostgreSQL 高可用性的支柱。它的工作原理是连续将预写日志 (WAL) 记录从主服务器传送到一台或多台备用服务器。备用数据库实时应用这些 WAL 记录,维护主数据库几乎相同的数据副本。该机制在 PostgreSQL 9.0 中引入,并在后续版本中进行了完善。
流式复制有两种模式:异步和同步。在异步模式下,主服务器在提交事务之前不会等待备用服务器确认收到 WAL 记录。这提供了最大的写入性能,但引入了潜在数据丢失的窗口 - 如果主数据库在备用数据库收到最新的 WAL 之前发生故障,那么这些事务就会丢失。在同步模式下,主服务器会等待至少一个备用服务器来确认 WAL 记录已写入持久存储,然后再报告事务已提交。这消除了数据丢失,但代价是增加了提交延迟,因为每次写入都必须往返于备用数据库。
同步和异步复制之间的选择不是二元的。 PostgreSQL 在会话级别支持synchronous_commit,因此对延迟敏感的工作负载可以选择异步提交,而关键财务事务则在同一集群内使用同步提交。
配置主节点以进行复制
主服务器必须配置为生成足以进行复制的级别的 WAL 记录并允许备用连接。以下postgresql.conf设置至关重要。
# postgresql.conf on the primary
wal_level = replica # minimum for streaming replication
max_wal_senders = 10 # max concurrent replication connections
max_replication_slots = 10 # prevent WAL removal before standby consumption
wal_keep_size = 2GB # retain WAL as fallback if slots are unused
hot_standby = on # allow read queries on standbys
synchronous_commit = on # 'on' for sync, 'off' for pure async
synchronous_standby_names = 'ANY 1 (standby1, standby2)' # sync replication targets
archive_mode = on # enable WAL archiving for PITR
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
listening_addresses = '*'
port = 5432复制连接的身份验证在pg_hba.conf中处理。复制连接使用专用连接类型。
# pg_hba.conf — replication entries
# TYPE DATABASE USER ADDRESS METHOD
host replication replicator 10.0.1.0/24 scram-sha-256
host replication replicator 10.0.2.0/24 scram-sha-256
host all all 10.0.0.0/16 scram-sha-256在主服务器上创建复制用户。
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'strong_replication_password';使用 pg_basebackup
配置备用pg_basebackup实用程序创建主数据库数据目录的物理副本,该副本成为新备用数据库的起点。它以原子方式处理基本备份和 WAL 流,因此生成的副本是一致的。
# On the standby server
pg_basebackup -h primary-host -U replicator -D /var/lib/postgresql/16/main \
-Fp -Xs -P -R
# -Fp: plain format
# -Xs: stream WAL during backup
# -P: show progress
# -R: create standby.signal and configure primary_conninfo in postgresql.auto.conf-R标志至关重要 - 它将primary_conninfo写入postgresql.auto.conf并创建standby.signal,这告诉 PostgreSQL 在待机模式下启动。在PostgreSQL 12及更高版本中,recovery.conf被这两种机制取代。
# postgresql.auto.conf (generated by pg_basebackup -R)
primary_conninfo = 'host=primary-host port=5432 user=replicator password=strong_replication_password application_name=standby1'
primary_slot_name = 'standby1_slot'在启动备用数据库之前在主数据库上创建复制槽,以防止 WAL 在备用数据库消耗它之前被清理。
SELECT pg_create_physical_replication_slot('standby1_slot');
SELECT pg_create_physical_replication_slot('standby2_slot');Patroni:自动化 HA 编排
流复制为您提供数据冗余,但它不会为您提供自动故障转移。如果主数据库崩溃,某人(操作员或自动化系统)必须将备用数据库升级为主数据库,重新配置剩余的备用数据库以遵循新的主数据库,并更新连接路由。 Patroni 使这一切自动化。
Patroni 是一个 Python 守护进程,与每个 PostgreSQL 实例一起运行。它使用分布式配置存储(DCS)——通常是 etcd,但也有 ZooKeeper 或 Consul——来协调领导者选举和集群状态。每个 Patroni 节点不断地将其健康状态写入 DCS。当领导者(主节点)未能在配置的 TTL 内更新其 DCS 密钥时,Patroni 将在健康的备用节点中发起领导者选举。获胜者将升级为主节点,其余节点将自身重新配置为新主节点的备用节点 — 所有这些都是自动完成的,通常在 10-30 秒内完成。
Patroni YAML 配置
Patroni 通过 YAML 文件进行配置,该文件定义 DCS 连接、PostgreSQL 参数、复制行为和引导设置。以下是主节点的生产级配置。
# /etc/patroni/patroni.yml — Node 1 (Primary)
scope: pg-ha-cluster
namespace: /postgresql-ha/
name: node1
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.1.10:8008
etcd3:
hosts:
- 10.0.2.10:2379
- 10.0.2.11:2379
- 10.0.2.12:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576 # 1MB — only promote standbys within this lag
synchronous_mode: true
synchronous_mode_strict: false
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: 'on'
max_connections: 200
max_wal_senders: 10
max_replication_slots: 10
wal_keep_size: 2GB
synchronous_commit: 'on'
archive_mode: 'on'
archive_command: 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout: 60
wal_log_hints: 'on'
shared_preload_libraries: 'pg_stat_statements'
track_commit_timestamp: 'on'
pg_hba:
- host replication replicator 10.0.0.0/16 scram-sha-256
- host all all 10.0.0.0/16 scram-sha-256
- host all all 0.0.0.0/0 scram-sha-256
initdb:
- encoding: UTF8
- data-checksums
users:
admin:
password: 'admin_secure_password'
options:
- createrole
- createdb
replicator:
password: 'repl_secure_password'
options:
- replication
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.1.10:5432
data_dir: /var/lib/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
config_dir: /var/lib/postgresql/16/main
pgpass: /tmp/pgpass0
authentication:
superuser:
username: postgres
password: 'postgres_secure_password'
replication:
username: replicator
password: 'repl_secure_password'
rewind:
username: postgres
password: 'postgres_secure_password'
parameters:
unix_socket_directories: '/var/run/postgresql'
create_replica_methods:
- basebackup
basebackup:
max-rate: 100M
checkpoint: fast
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false备用节点使用相同的配置及其自己的name、connect_address和listen值。 Patroni 处理剩下的事情——它根据 DCS 状态检测节点是否应该成为领导者或副本,并相应地配置 PostgreSQL。
etcd 作为分布式配置存储
etcd 是 Patroni 集群的神经系统。它存储当前领导者身份、集群拓扑、所需配置以及每个节点的健康状态。 三节点 etcd 集群是生产环境的最低要求,可以在维持仲裁的同时容忍一个节点故障。
# Install and configure etcd on three dedicated nodes
# /etc/etcd/etcd.conf.yml — Node etcd1 (10.0.2.10)
name: etcd1
data-dir: /var/lib/etcd
listen-client-urls: http://0.0.0.0:2379
listen-peer-urls: http://0.0.0.0:2380
advertise-client-urls: http://10.0.2.10:2379
initial-advertise-peer-urls: http://10.0.2.10:2380
initial-cluster: etcd1=http://10.0.2.10:2380,etcd2=http://10.0.2.11:2380,etcd3=http://10.0.2.12:2380
initial-cluster-state: new
initial-cluster-token: patroni-etcd-cluster
# Start etcd
systemctl enable --now etcd
# Verify cluster health
etcdctl endpoint health --cluster \
--endpoints=http://10.0.2.10:2379,http://10.0.2.11:2379,http://10.0.2.12:2379对于生产部署,在 etcd 对等点之间以及 etcd 和 Patroni 客户端之间启用 TLS。未加密的 etcd 流量会将集群凭据和配置暴露给网络级攻击者。
HAProxy 用于连接路由
Patroni 在每个节点(默认端口 8008)上公开一个 REST API,报告该节点是当前领导者还是副本。 HAProxy 使用这些健康检查端点来路由流量:写入到领导者,读取到健康副本。这使您可以自动进行读写分离,而无需进行任何应用程序级别的更改。
# /etc/haproxy/haproxy.cfg
global
maxconn 2000
log /dev/log local0
stats socket /var/run/haproxy.sock mode 660 level admin
defaults
mode tcp
log global
retries 3
timeout client 30m
timeout connect 4s
timeout server 30m
timeout check 5s
maxconn 1000
listen pg_write
bind *:5000
option httpchk GET /primary
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.1.10:5432 check port 8008
server node2 10.0.1.11:5432 check port 8008
server node3 10.0.1.12:5432 check port 8008
listen pg_read
bind *:5001
balance roundrobin
option httpchk GET /replica
http-check expect status 200
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
server node1 10.0.1.10:5432 check port 8008
server node2 10.0.1.11:5432 check port 8008
server node3 10.0.1.12:5432 check port 8008
listen stats
bind *:7000
mode http
stats enable
stats uri /
stats refresh 10s/primary端点仅在当前 Patroni 领导者上返回 HTTP 200。/replica端点在正常待机状态下返回 200。发生故障转移时,新的主节点开始在/primary上返回 200,并且 HAProxy 自动重定向写入流量 — 通常在单个运行状况检查间隔(3 秒)内。on-marked-down shutdown-sessions指令立即终止与发生故障的主节点的现有连接,强制客户端重新连接到新的主节点。
用于连接池的 PgBouncer
PostgreSQL 为每个客户端连接创建一个新的后端进程。在规模上(数百或数千个微服务,每个微服务维护连接池),进程创建和内存消耗的开销变得很大。 PgBouncer 位于应用程序和 PostgreSQL 之间,维护服务器端连接池并将客户端连接复用到它们上。
# /etc/pgbouncer/pgbouncer.ini
[databases]
* = host=127.0.0.1 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
reserve_pool_timeout = 3
server_lifetime = 3600
server_idle_timeout = 600
server_connect_timeout = 5
server_login_retry = 3
log_connections = 1
log_disconnections = 1
stats_period = 60与 Patroni 一起使用时,PgBouncer 通常位于每个 PostgreSQL 节点或 HAProxy 节点上。transaction池模式是大多数工作负载的最佳选择 - 它在事务期间分配服务器连接,并在事务之间将其返回到池。这比session模式高效得多,session 模式为整个客户端会话保留连接。
WAL 归档和时间点恢复
流复制可以防止服务器故障,但不能防止逻辑错误 - 意外的DROP TABLE或错误的应用程序迁移会立即复制到所有备用数据库。 WAL 归档与时间点恢复 (PITR) 相结合,让您可以恢复到错误发生之前的任何时刻。
WAL 归档将完整的 WAL 段复制到持久归档 — 通常是 S3 存储桶、NFS 安装或专用备份服务器。pgBackRest和WAL-G等工具通过压缩、加密和并行传输高效处理归档。
# pgBackRest configuration — /etc/pgbackrest/pgbackrest.conf
[global]
repo1-type=s3
repo1-s3-bucket=pg-wal-archive
repo1-s3-endpoint=s3.eu-west-1.amazonaws.com
repo1-s3-region=eu-west-1
repo1-s3-key=AKIAIOSFODNN7EXAMPLE
repo1-s3-key-secret=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
repo1-path=/pgbackrest
repo1-retention-full=4
repo1-retention-diff=7
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=strong_encryption_passphrase
compress-type=zst
compress-level=3
process-max=4
[pg-ha-cluster]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
# In postgresql.conf
archive_command = 'pgbackrest --stanza=pg-ha-cluster archive-push %p'
restore_command = 'pgbackrest --stanza=pg-ha-cluster archive-get %f "%p"'要执行时间点恢复,请指定目标时间戳。
# Restore to a specific point in time
pgbackrest --stanza=pg-ha-cluster --type=time \
--target="2026-04-12 11:25:00" \
--target-action=promote \
restore用于选择性数据同步的逻辑复制
流复制创建整个数据库集群的精确物理副本,而逻辑复制则在表级别运行,在独立的 PostgreSQL 实例之间复制单个表或数据子集。这对于零停机主要版本升级、仅需要特定表的跨区域只读副本、数据仓库馈送和多租户数据分发非常有用。
# On the publisher (source database)
wal_level = logical # must be 'logical' — higher than 'replica'
CREATE PUBLICATION app_pub FOR TABLE orders, customers, products;
# On the subscriber (target database)
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=publisher-host port=5432 dbname=appdb user=replicator password=pass'
PUBLICATION app_pub;逻辑复制可以与流复制一起运行。一种常见模式是使用流复制实现 HA(快速物理故障转移),使用逻辑复制实现仅需要表子集的跨区域分析副本。
多区域流式复制
对于灾难恢复和全局读取性能,PostgreSQL 集群可以跨越多个区域。标准模式是区域内同步复制(本地故障转移时零数据丢失)和跨区域异步复制(以避免每次写入时的跨区域延迟损失)。每个区域都有自己的 HAProxy 用于本地读取路由。
故障转移和切换过程
了解故障转移和切换之间的区别至关重要。故障转移是由当前主节点故障触发的计划外升级。切换是有计划的、优雅的角色更改 — 通常在维护之前执行。帕特罗尼都支持。
计划切换
# List cluster members
patronictlctl -c /etc/patroni/patroni.yml list
# Perform switchover to a specific node
patronictlctl -c /etc/patroni/patroni.yml switchover \
--master node1 --candidate node2 --force
# Or use the Patroni REST API
curl -s http://10.0.1.10:8008/switchover -XPOST \
-d '{"leader": "node1", "candidate": "node2"}'在切换期间,Patroni 将当前主数据库降级为备用数据库,提升目标候选数据库,并重新配置所有其他备用数据库以遵循新的主数据库。该过程需要 5-15 秒。 HAProxy 通过健康检查检测变化并自动重定向流量。
自动故障转移序列
当主服务器意外发生故障时,Patroni 会遵循精确的顺序来恢复服务。下图说明了这些步骤。
脑裂预防
裂脑(两个节点同时认为自己是主节点)是任何 HA 系统中最危险的故障模式。 Patroni 通过多种机制防止脑裂:
- 基于 DCS 的领导者锁:任何时候只有一个节点可以持有 etcd 中的领导者密钥。密钥有一个 TTL,领导者必须不断更新它。如果网络分区将领导者与 etcd 隔离,则密钥将过期,并且领导者将自身降级。
- 看门狗:Patroni 可以配置 Linux 看门狗器件 (
/dev/watchdog)。如果 Patroni 失去对 DCS 的访问并且无法确认它应该保持领导者状态,看门狗将重新启动或关闭节点电源 - 这是一种硬隔离机制,可保证旧的主节点不会继续接受写入。 - pg_rewind:当前一个主节点重新上线时,它可能具有从未复制的 WAL 记录。
pg_rewind将时间线倒回到分歧点,允许节点作为备用节点重新加入,而无需完整的基础备份。 Patroni 的use_pg_rewind: true设置可自动执行此操作。
# Enable watchdog in Patroni config
bootstrap:
dcs:
postgresql:
use_pg_rewind: true
parameters:
wal_log_hints: 'on' # required for pg_rewind
# Watchdog configuration
watchdog:
mode: required # 'off', 'automatic', or 'required'
device: /dev/watchdog
safety_margin: 5 # seconds before TTL expiry to trigger watchdogrepmgr 作为 Patroni
的替代品revmgr是 PostgreSQL 的另一种流行的 HA 工具。它提供备用管理、自动故障转移和切换功能。 然而,它采用了与 Patroni 根本不同的方法。 repmgr 使用见证节点和守护进程 (repmgrd) 进行故障检测,而不是分布式共识存储。这使得部署更简单,但在复杂的网络分区场景中更容易受到裂脑的影响。
# repmgr.conf on the primary
node_id=1
node_name='node1'
conninfo='host=10.0.1.10 user=repmgr dbname=repmgr connect_timeout=2'
data_directory='/var/lib/postgresql/16/main'
failover=automatic
promote_command='repmgr standby promote -f /etc/repmgr.conf --log-to-file'
follow_command='repmgr standby follow -f /etc/repmgr.conf --log-to-file --upstream-node-id=%n'
monitoring_history=yes
monitor_interval_secs=5
reconnect_attempts=6
reconnect_interval=10对于新部署,Patroni 是推荐选择,因为它具有更强的裂脑预防保证和更活跃的开发社区。对于更简单的设置或已经投资该工具的组织来说,repmgr 仍然是一个合理的选择。
云部署模式
AWS 部署:EC2、EBS 和 Route53
在 AWS 上,将每个 PostgreSQL + Patroni 节点部署在具有 EBS gp3 或 io2 卷的 EC2 实例上。跨多个可用区使用单独的实例来实现高可用性。 etcd 节点也应该跨越可用区。
# Terraform sketch for PostgreSQL HA on AWS
resource "aws_instance" "pg_node" {
count = 3
ami = "ami-0abcdef1234567890" # Ubuntu 22.04
instance_type = "r6g.2xlarge" # 8 vCPU, 64GB RAM
subnet_id = aws_subnet.private[count.index].id
vpc_security_group_ids = [aws_security_group.pg_sg.id]
availability_zone = element(["eu-west-1a", "eu-west-1b", "eu-west-1c"], count.index)
root_block_device {
volume_size = 50
volume_type = "gp3"
}
tags = {
Name = "pg-node-${count.index + 1}"
Role = "patroni"
}
}
resource "aws_ebs_volume" "pg_data" {
count = 3
availability_zone = element(["eu-west-1a", "eu-west-1b", "eu-west-1c"], count.index)
size = 500
type = "gp3"
iops = 6000
throughput = 250
encrypted = true
tags = {
Name = "pg-data-${count.index + 1}"
}
}
resource "aws_route53_health_check" "pg_primary" {
count = 3
ip_address = aws_instance.pg_node[count.index].private_ip
port = 8008
type = "HTTP"
resource_path = "/primary"
failure_threshold = 3
request_interval = 10
}如果您更喜欢 AWS 托管解决方案,请使用网络负载均衡器 (NLB) 而不是 HAProxy。 NLB 可以使用针对 Patroni REST API 的目标组运行状况检查将流量路由到当前主节点。
Azure 部署:虚拟机、托管磁盘和 Azure LB
在 Azure 上,将 Standard_E8s_v5 VM(内存优化)与高级 SSD 托管磁盘用于数据卷。跨可用区部署。 Azure 负载均衡器提供了相当于 HAProxy 的功能,并针对 Patroni REST API 进行运行状况探测。
# Azure CLI — create PostgreSQL VM with Managed Disk
az vm create \
--resource-group pg-ha-rg \
--name pg-node-1 \
--image Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest \
--size Standard_E8s_v5 \
--zone 1 \
--vnet-name pg-vnet \
--subnet pg-subnet \
--nsg pg-nsg \
--admin-username pgadmin \
--ssh-key-value ~/.ssh/id_rsa.pub
az disk create \
--resource-group pg-ha-rg \
--name pg-data-1 \
--size-gb 512 \
--sku Premium_LRS \
--zone 1
az vm disk attach \
--resource-group pg-ha-rg \
--vm-name pg-node-1 \
--name pg-data-1
# Azure Load Balancer health probe for Patroni
az network lb probe create \
--resource-group pg-ha-rg \
--lb-name pg-lb \
--name patroni-primary-probe \
--protocol Http \
--port 8008 \
--path /primary \
--interval 5 \
--threshold 3GCP 部署:计算引擎和云负载平衡
在 GCP 上,将 n2-highmem-8 实例(8 vCPU、64GB RAM)与 SSD 永久磁盘结合使用。在一个区域内跨区域分布。使用内部 TCP/UDP 负载平衡器和 Patroni 运行状况检查。
# GCP — create instance and persistent disk
gcloud compute instances create pg-node-1 \
--zone=europe-west1-b \
--machine-type=n2-highmem-8 \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=50GB \
--network=pg-network \
--subnet=pg-subnet
gcloud compute disks create pg-data-1 \
--zone=europe-west1-b \
--size=500GB \
--type=pd-ssd
gcloud compute instances attach-disk pg-node-1 \
--disk=pg-data-1 \
--zone=europe-west1-b
# Health check for Patroni primary endpoint
gcloud compute health-checks create http patroni-primary-check \
--port=8008 \
--request-path=/primary \
--check-interval=5s \
--timeout=5s \
--unhealthy-threshold=3 \
--healthy-threshold=2裸机 k3s 部署与 Rancher 和 Longhorn
对于运行自己的硬件的组织来说,使用 k3s、Rancher 和 Longhorn 在裸机上部署 PostgreSQL HA 可提供完全开源、独立于云的基础设施。 k3s 是轻量级 Kubernetes 发行版,可在裸机服务器上高效运行,而无需完整 Kubernetes 发行版的开销。
k3s 和 Longhorn 设置
# Install k3s on the first server node
curl -sfL https://get.k3s.io | K3S_TOKEN=my-cluster-token \
INSTALL_K3S_EXEC="server --cluster-init --disable traefik --disable servicelb" sh -
# Join additional server nodes
curl -sfL https://get.k3s.io | K3S_TOKEN=my-cluster-token \
K3S_URL=https://10.0.0.1:6443 \
INSTALL_K3S_EXEC="server" sh -
# Install Longhorn for distributed block storage
helm repo add longhorn https://charts.longhorn.io
helm install longhorn longhorn/longhorn \
--namespace longhorn-system --create-namespace \
--set defaultSettings.defaultDataPath=/mnt/longhorn \
--set defaultSettings.replicaCount=3 \
--set defaultSettings.storageMinimalAvailablePercentage=15
# Deploy PostgreSQL with Patroni using the Zalando Postgres Operator
helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm install postgres-operator postgres-operator-charts/postgres-operator \
--namespace postgres-system --create-namespace# PostgreSQL cluster manifest for the Zalando Postgres Operator
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
name: pg-ha-cluster
namespace: production
spec:
teamId: "platform"
numberOfInstances: 3
volume:
size: 500Gi
storageClass: longhorn
users:
appuser:
- superuser
- createdb
replicator: []
databases:
appdb: appuser
postgresql:
version: "16"
parameters:
shared_buffers: "16GB"
effective_cache_size: "48GB"
work_mem: "256MB"
maintenance_work_mem: "2GB"
max_connections: "200"
max_wal_senders: "10"
wal_level: replica
synchronous_commit: "on"
wal_keep_size: "2GB"
archive_mode: "on"
track_commit_timestamp: "on"
patroni:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
synchronous_mode: true
resources:
requests:
cpu: "4"
memory: 32Gi
limits:
cpu: "8"
memory: 64Gi适用于 HAProxy VIP
# /etc/keepalived/keepalived.conf on lb1
vrrp_script chk_haproxy {
script "killall -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_PG {
state MASTER
interface eth0
virtual_router_id 52
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass pgha_vip_pass
}
virtual_ipaddress {
10.0.0.100/24
}
track_script {
chk_haproxy
}
}监控 PostgreSQL 复制
监控复制运行状况在生产中是不可协商的。 PostgreSQL 为此提供了多个内置视图,Prometheus 和 Grafana 提供了您所需的长期可见性和警报。
内置监控查询
-- Check replication status on the primary
SELECT
client_addr,
application_name,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
(sent_lsn - replay_lsn) AS replication_lag_bytes,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;
-- Check replication slot status
SELECT
slot_name,
slot_type,
active,
wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS slot_lag
FROM pg_replication_slots;
-- Check standby recovery status (run on standby)
SELECT
pg_is_in_recovery() AS is_standby,
pg_last_wal_receive_lsn() AS last_received,
pg_last_wal_replay_lsn() AS last_replayed,
pg_last_xact_replay_timestamp() AS last_replayed_timestamp,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::int AS replay_lag_seconds;
-- Monitor WAL generation rate
SELECT
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')) AS total_wal_generated,
pg_size_pretty(sum(size)) AS wal_directory_size
FROM pg_ls_waldir();
-- Check for long-running queries that could block replication
SELECT
pid,
now() - pg_stat_activity.query_start AS duration,
query,
state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 minutes'
AND state != 'idle'
ORDER BY duration DESC;Prometheus 和 Grafana 堆栈
postgres_exporter以 Prometheus 格式公开 PostgreSQL 指标。与patoni_exporter结合使用,您可以全面了解数据库性能和 HA 集群状态。
# Deploy postgres_exporter as a sidecar or standalone
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# Custom queries for postgres_exporter
# /etc/postgres_exporter/queries.yaml
pg_replication_lag:
query: |
SELECT
CASE WHEN pg_is_in_recovery() THEN
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::float
ELSE 0 END AS lag_seconds
master: true
metrics:
- lag_seconds:
usage: "GAUGE"
description: "Replication lag in seconds"
pg_replication_slots:
query: |
SELECT
slot_name,
active::int AS active,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)::float AS slot_lag_bytes
FROM pg_replication_slots
master: true
metrics:
- slot_name:
usage: "LABEL"
- active:
usage: "GAUGE"
description: "Whether the slot is active"
- slot_lag_bytes:
usage: "GAUGE"
description: "Slot lag in bytes"# PrometheusRule for PostgreSQL HA alerts
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: postgresql-ha-alerts
namespace: monitoring
spec:
groups:
- name: postgresql-replication
rules:
- alert: PostgreSQLReplicationLagHigh
expr: pg_replication_lag_seconds > 30
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL replication lag exceeds 30s on {{ $labels.instance }}"
- alert: PostgreSQLReplicationSlotInactive
expr: pg_replication_slots_active == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Replication slot {{ $labels.slot_name }} is inactive"
- alert: PostgreSQLReplicationSlotLagHigh
expr: pg_replication_slots_slot_lag_bytes > 1073741824
for: 10m
labels:
severity: warning
annotations:
summary: "Replication slot lag exceeds 1GB on {{ $labels.slot_name }}"
- alert: PatroniClusterUnhealthy
expr: patroni_cluster_members_count < 3
for: 2m
labels:
severity: critical
annotations:
summary: "Patroni cluster has fewer than 3 members"生产调谐参数
PostgreSQL 的默认配置比较保守,针对小型共享托管环境进行了调整。生产 HA 集群需要仔细调整复制、内存和 WAL 参数。下表总结了具有 NVMe 存储的 64GB RAM 服务器的最重要设置。
# postgresql.conf — Production HA tuning
# === Replication ===
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 4GB
synchronous_commit = on
synchronous_standby_names = 'ANY 1 (standby1, standby2)'
track_commit_timestamp = on
wal_log_hints = on
# === WAL ===
min_wal_size = 1GB
max_wal_size = 8GB
wal_buffers = 64MB
wal_compression = zstd
archive_mode = on
archive_timeout = 300
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min
# === Memory ===
shared_buffers = 16GB # 25% of RAM
effective_cache_size = 48GB # 75% of RAM
work_mem = 256MB # per-operation sort/hash memory
maintenance_work_mem = 2GB # for VACUUM, CREATE INDEX
huge_pages = try
# === Connections ===
max_connections = 200 # use PgBouncer for higher client counts
superuser_reserved_connections = 5
# === Query Performance ===
random_page_cost = 1.1 # SSD storage
effective_io_concurrency = 200 # NVMe SSD
default_statistics_target = 500
jit = on
# === Logging ===
log_min_duration_statement = 500 # log queries > 500ms
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0
# === Autovacuum ===
autovacuum_max_workers = 4
autovacuum_naptime = 30s
autovacuum_vacuum_cost_limit = 2000
autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02调整 Patroni DCS 参数
Patroni 的ttl、loop_wait和retry_timeout参数之间的关系直接影响故障转移速度和误报风险。较短的 TTL 意味着更快的故障转移检测,但会增加短暂网络故障期间不必要的故障转移的风险。
# Conservative (production default)
ttl: 30
loop_wait: 10
retry_timeout: 10
# Failover detection: ~30-40 seconds
# Aggressive (low-latency failover)
ttl: 15
loop_wait: 5
retry_timeout: 5
# Failover detection: ~15-20 seconds
# Warning: Higher risk of false failovers in unstable networks混沌工程和故障转移测试
未经测试的故障转移系统是无法工作的系统。 混沌工程应用受控故障来验证您的 HA 设置在实际故障条件下是否正常运行。每个 Patroni 集群都应该进行定期的故障转移演习。
故障转移测试手册
# 1. Verify cluster health before testing
patronictlctl -c /etc/patroni/patroni.yml list
+----------+---------+---------+----+-----------+
| Member | Host | Role | TL | Lag in MB |
+----------+---------+---------+----+-----------+
| node1 | 10.0.1.10| Leader | 5 | |
| node2 | 10.0.1.11| Replica | 5 | 0 |
| node3 | 10.0.1.12| Replica | 5 | 0 |
+----------+---------+---------+----+-----------+
# 2. Simulate primary crash (on node1)
sudo systemctl stop patroni
# Or more aggressive: sudo kill -9 $(pgrep -f patroni)
# 3. Monitor failover (from any node with patronictl)
watch -n 1 'patronictl -c /etc/patroni/patroni.yml list'
# 4. Verify new leader is elected (within 30-45 seconds)
# Expected: node2 or node3 promoted to Leader
# 5. Test write availability through HAProxy
PGPASSWORD=app_password psql -h haproxy-host -p 5000 -U appuser -d appdb \
-c "INSERT INTO health_check (ts) VALUES (now()) RETURNING *;"
# 6. Restart the former primary
sudo systemctl start patroni
# Patroni will use pg_rewind to rejoin as a replica
# 7. Verify the former primary rejoins as replica
patronictlctl -c /etc/patroni/patroni.yml list网络分区测试
# Simulate network partition on the primary using iptables
# Block all traffic to etcd from the primary
sudo iptables -A OUTPUT -d 10.0.2.10 -j DROP
sudo iptables -A OUTPUT -d 10.0.2.11 -j DROP
sudo iptables -A OUTPUT -d 10.0.2.12 -j DROP
# Expected behaviour:
# 1. Primary loses DCS access
# 2. Leader key TTL expires
# 3. Primary demotes itself (with watchdog, node may reboot)
# 4. Standby acquires leader lock and promotes
# 5. After clearing iptables rules, former primary rejoins as replica
# Clean up
sudo iptables -D OUTPUT -d 10.0.2.10 -j DROP
sudo iptables -D OUTPUT -d 10.0.2.11 -j DROP
sudo iptables -D OUTPUT -d 10.0.2.12 -j DROP使用 Toxiproxy
自动混沌测试# Run Toxiproxy alongside your Patroni cluster
# Create proxies for etcd and replication connections
toxiproxy-cli create etcd_proxy -l 0.0.0.0:12379 -u 10.0.2.10:2379
toxiproxy-cli create pg_repl_proxy -l 0.0.0.0:15432 -u 10.0.1.10:5432
# Add latency to etcd connections (simulates degraded network)
toxiproxy-cli toxic add etcd_proxy -t latency -a latency=500 -a jitter=200
# Add bandwidth limit to replication (simulates WAN replication)
toxiproxy-cli toxic add pg_repl_proxy -t bandwidth -a rate=1024
# Completely sever the connection (simulates network partition)
toxiproxy-cli toxic add etcd_proxy -t timeout -a timeout=0
# Monitor Patroni behaviour and verify correct failover
watch -n 2 'curl -s http://10.0.1.10:8008/patroni | python3 -m json.tool'连续验证脚本
#!/bin/bash
# continuous_ha_check.sh — Run during chaos tests to measure availability
HAPROXY_HOST="10.0.0.100"
WRITE_PORT=5000
READ_PORT=5001
DATABASE="appdb"
USER="appuser"
LOGFILE="/var/log/ha_test_$(date +%Y%m%d_%H%M%S).log"
write_count=0
write_fail=0
read_count=0
read_fail=0
while true; do
ts=$(date '+%Y-%m-%d %H:%M:%S.%3N')
# Test write path
if PGPASSWORD=app_password psql -h $HAPROXY_HOST -p $WRITE_PORT \
-U $USER -d $DATABASE -c "SELECT 1" &>/dev/null; then
((write_count++))
else
((write_fail++))
echo "$ts WRITE_FAIL total_fails=$write_fail" >> $LOGFILE
fi
# Test read path
if PGPASSWORD=app_password psql -h $HAPROXY_HOST -p $READ_PORT \
-U $USER -d $DATABASE -c "SELECT 1" &>/dev/null; then
((read_count++))
else
((read_fail++))
echo "$ts READ_FAIL total_fails=$read_fail" >> $LOGFILE
fi
total=$((write_count + write_fail))
if (( total % 100 == 0 )); then
write_avail=$(echo "scale=2; $write_count * 100 / $total" | bc)
read_total=$((read_count + read_fail))
read_avail=$(echo "scale=2; $read_count * 100 / $read_total" | bc)
echo "$ts Writes: ${write_avail}% ($write_count/$total) Reads: ${read_avail}% ($read_count/$read_total)"
fi
sleep 0.5
done高级:级联复制和延迟待机
对于大型集群,级联复制可减少主节点上的负载。并非所有备用数据库都直接从主数据库进行复制,而是某些备用数据库从其他备用数据库进行复制。这将创建一个树形拓扑,其中主数据库为两个备用数据库提供数据,而这些备用数据库为其他下游备用数据库提供数据。
# postgresql.auto.conf on a cascading standby
primary_conninfo = 'host=standby1-host port=5432 user=replicator application_name=cascade1'
primary_slot_name = 'cascade1_slot'A延迟待机故意应用带有时间延迟的 WAL 记录 — 通常为 1-4 小时。这可以防止立即复制到同步备用数据库的逻辑错误(意外删除、错误迁移)。如果发生灾难,您可以停止延迟备用上的 WAL 重放并恢复错误之前的数据。
# postgresql.conf on delayed standby
recovery_min_apply_delay = '1h'连接字符串策略
连接到 Patroni 管理的集群的应用程序应始终通过 HAProxy 进行连接,或将 PostgreSQL 的内置多主机连接字符串与target_session_attrs结合使用。这提供了客户端故障转移,而不依赖于负载均衡器。
# Multi-host connection string with target_session_attrs
# The client tries each host in order and connects to the one matching the target attribute
postgresql://appuser:password@node1:5432,node2:5432,node3:5432/appdb?target_session_attrs=read-write&sslmode=require
# For read-only connections
postgresql://appuser:password@node1:5432,node2:5432,node3:5432/appdb?target_session_attrs=prefer-standby&sslmode=require此方法非常适合无法轻松重新配置为指向 HAProxy VIP 的应用程序。 PostgreSQL 客户端库 (libpq) 透明地处理故障转移。
安全强化
生产 PostgreSQL HA 集群必须在传输和静态时强制执行加密、使用强身份验证并限制网络暴露。
# Enable TLS in postgresql.conf
ssl = on
ssl_cert_file = '/etc/postgresql/certs/server.crt'
ssl_key_file = '/etc/postgresql/certs/server.key'
ssl_ca_file = '/etc/postgresql/certs/ca.crt'
ssl_min_protocol_version = 'TLSv1.3'
# Require TLS for all connections in pg_hba.conf
hostssl replication replicator 10.0.0.0/16 scram-sha-256
hostssl all all 10.0.0.0/16 scram-sha-256
# etcd TLS
# In Patroni config
etcd3:
hosts:
- 10.0.2.10:2379
- 10.0.2.11:2379
- 10.0.2.12:2379
protocol: https
cacert: /etc/patroni/certs/etcd-ca.crt
cert: /etc/patroni/certs/etcd-client.crt
key: /etc/patroni/certs/etcd-client.keyHA 集群的备份策略
Patroni 集群的全面备份策略应包括连续 WAL 归档、定期完整备份以及完整备份之间的差异或增量备份。 pgBackRest 是生产 PostgreSQL 备份管理的推荐工具。
# Schedule backups via cron
# Full backup weekly (Sunday 2 AM)
0 2 * * 0 pgbackrest --stanza=pg-ha-cluster --type=full backup
# Differential backup daily (2 AM, Mon-Sat)
0 2 * * 1-6 pgbackrest --stanza=pg-ha-cluster --type=diff backup
# Verify backup integrity
pgbackrest --stanza=pg-ha-cluster --set=latest info
# Verify backup can be restored (dry run)
pgbackrest --stanza=pg-ha-cluster --set=latest verify
# List all backups
pgbackrest --stanza=pg-ha-cluster info
full backup: 20260412-020000F
timestamp: 2026-04-12 02:00:00 +0000
wal start/stop: 000000050000000000000040 / 000000050000000000000042
database size: 150GB, backup size: 150GB
repository size: 45GB (compressed)
diff backup: 20260412-020000F_20260413-020000D
timestamp: 2026-04-13 02:00:00 +0000
database size: 151GB, backup size: 2.1GB
repository size: 650MB (compressed)操作手册摘要
每个运行 Patroni 集群的团队都应维护一份涵盖以下场景的操作手册。记录并测试过的程序可将压力大的停机转变为常规操作。
# === Quick Reference Commands ===
# Cluster status
patronictlctl -c /etc/patroni/patroni.yml list
patronictlctl -c /etc/patroni/patroni.yml history
# Planned switchover
patronictlctl -c /etc/patroni/patroni.yml switchover --master node1 --candidate node2
# Restart PostgreSQL on a specific node (rolling restart)
patronictlctl -c /etc/patroni/patroni.yml restart pg-ha-cluster node2
# Reload PostgreSQL configuration without restart
patronictlctl -c /etc/patroni/patroni.yml reload pg-ha-cluster
# Pause automatic failover (during maintenance)
patronictlctl -c /etc/patroni/patroni.yml pause
# Resume automatic failover
patronictlctl -c /etc/patroni/patroni.yml resume
# Edit DCS configuration (applies to all nodes)
patronictlctl -c /etc/patroni/patroni.yml edit-config
# Reinitialise a failed replica
patronictlctl -c /etc/patroni/patroni.yml reinit pg-ha-cluster node3
# Check Patroni REST API directly
curl -s http://10.0.1.10:8008/patroni | python3 -m json.tool
curl -s http://10.0.1.10:8008/cluster | python3 -m json.tool性能基准测试
在投入生产之前,对您的 HA 集群进行基准测试,以确定基准性能并验证同步复制延迟对于您的工作负载来说是可以接受的。
# Benchmark with pgbench — initialise test data
pgbench -i -s 100 -h haproxy-host -p 5000 -U appuser appdb
# Run write-heavy benchmark (measures sync replication impact)
pgbench -h haproxy-host -p 5000 -U appuser -c 32 -j 8 -T 300 appdb
# Compare with async: temporarily set synchronous_commit = off
# Run read-only benchmark through read replica port
pgbench -h haproxy-host -p 5001 -U appuser -c 64 -j 16 -T 300 -S appdb
# Measure failover impact on transactions
# Run pgbench in background, then trigger a failover
pgbench -h haproxy-host -p 5000 -U appuser -c 8 -j 4 -T 600 appdb &
sleep 60 && patronictl switchover --master node1 --candidate node2 --force结论
PostgreSQL 与 Patroni 的本机高可用性不是单一工具解决方案 - 它是流式复制、分布式共识、连接路由、连接池、WAL 归档、监控和操作规则的集成系统。每层都解决特定的故障模式:流复制处理数据冗余,Patroni 处理自动故障转移协调,etcd 提供领导者选举所需的分布式共识,而无需脑裂,HAProxy 将连接路由到正确的领导者,PgBouncer 大规模管理连接开销,使用 pgBackRest 的 WAL 归档提供针对逻辑错误和灾难恢复的最后一道防线。
部署模式因环境而异 - AWS 具有 NLB 和 Route53、Azure 具有可用区和 Azure 负载均衡器、GCP 具有区域托管实例组,或裸机具有 k3s、Longhorn 和 Keepalived - 但核心架构保持不变。由 Patroni 管理的三个或更多 PostgreSQL 节点,由三节点 etcd 集群支持,前端是跟随 Patroni 运行状况检查端点的负载均衡器。
您可以做出的最重要的投资不是配置,而是测试。每月运行一次故障转移演习。注入网络分区。意外终止进程。测量恢复时间和数据丢失。构建实时显示复制滞后、WAL 生成率、连接池饱和度和 DCS 运行状况的仪表板。您从系统测试中获得的信心是将能够在第一次真正的中断中幸存下来的集群与将服务器故障转变为影响业务的事件的集群的区别所在。
PostgreSQL 为您提供所有复制原语。 Patroni 为您提供编排。 etcd 为您提供共识。您的工作是将它们正确连接在一起,根据您的工作负载调整它们,并不断验证它们。本指南为您提供了蓝图 - 现在可以充满信心地构建、测试和操作。