PostgreSQL流复制备库搭建实战:基于pg_rman物理备份方案 PostgreSQL使用pg_rman物理备份搭建流复制备库实战笔记1. 内容整体设计与思路拆解1.1 为什么用 pg_rman 而不是 pg_basebackup 搭备库很多PostgreSQL初学者第一次搭建流复制备库第一反应都是掏出pg_basebackup这也是官方文档里最常出现的方案。pg_basebackup确实简单一条命令就能拉一份基础备份出来然后配上standby.signal就能跑起来。但实际用到生产环境尤其是数据量比较大、主库压力又比较高的场景pg_basebackup有几个很现实的问题整个备份过程是流式传输对网络带宽和主库IO的冲击比较大备份过程中如果主库有频繁的checkpoint备库在恢复阶段要重放的WAL日志量会明显增加另外pg_basebackup不能天然地做增量备份你没法用它实现每天夜里只传变化的部分这种策略。pg_rman的思路完全不同。它本质上是一个基于块级全量备份加WAL归档增量备份的物理备份工具由日本NTT公司开发后来被整合进了PostgreSQL生态。用它搭流复制备库核心思路是先用pg_rman在主库上做一次全量备份这个全量备份本身就是一份完整的数据库簇然后把这份备份拷贝到目标机器上通过pg_rman的restore命令把它恢复成一个独立的数据目录最后在这个恢复出来的数据目录里创建standby.signal文件PostgreSQL 12之前的版本是recovery.conf配上主库的连接信息启动实例后就能自动从主库拉取缺失的WAL日志完成流复制备库的搭建。这个方案最大的优势在于两条链路是分开的备份走的是pg_rman自己的备份通道可以走本地磁盘也可以配合远程挂载而复制走的是PostgreSQL原生的流复制协议。在生产环境里这意味着你可以先在一个合适的低峰窗口完成备份和拷贝然后再让备库通过流复制追平数据。即便主库磁盘空间紧张、网络带宽有限只要归档日志是完整的备库就能利用这些归档补齐从备份点到当前时点的所有WAL最终并入库。1.2 这套方案的适用场景和核心收益我实际在几个项目里用过这套方案总结下来它特别适合下面几类场景主库已经跑了一段时间数据量在几百GB甚至TB级别现在要新加一个备库。这时候如果用pg_basebackup整个备份期间主库的WAL生成速度会叠加传输带宽压力耗时和数据量成正比而用pg_rman的增量备份策略你甚至可以只拿最近一次全量备份再加当天的WAL归档就够恢复速度会快不少。对主库IO敏感的业务。pg_rman在做全量备份时虽然也会产生IO占用但它的备份过程是块级限速的你可以通过参数控制备份速度比如--compress配合限速对业务的冲击比流式备份更容易控场。需要在多个备库之间复用同一份基础备份。pg_rman的全量备份文件拷到不同机器上每台机器各自执行restore即可不需要反复主库拉取备份这在批量搭建只读从库、做读写分离扩展时非常实用。当然pg_rman也不是没有门槛。它需要和主库版本匹配、需要配置归档模式、需要管理备份目录的磁盘空间。但凡是生产库本来就该把归档打开所以这点代价其实不算额外负担。下面我会完整走一遍从环境准备到备库验证的全过程把每个步骤的注意事项和坑都讲清楚。2. 环境规划与核心原理准备2.1 测试环境与版本选择为了把整个流程说清楚我这里用一个最小化的双机环境来演示。实际生产环境里机器配置、目录规划会复杂一些但原理完全一致。我用的环境如下主库机器192.168.1.10PostgreSQL 14.7数据目录/pgdata/14/data归档目录/pgdata/14/archive备份目录/pgdata/14/backup。备库机器192.168.1.11PostgreSQL 14.7版本必须和主库一致数据目录/pgdata/14/data恢复配置目录同主库结构。系统均为CentOS 7.9PostgreSQL通过官方YUM源安装pg_rman版本1.2.15。提示pg_rman的版本一定要和PostgreSQL小版本尽量匹配。比如PostgreSQL 14建议用pg_rman 1.2.14以上版本旧版pg_rman在处理14.x某些新特性时可能会有兼容问题。安装前可以到pg_rman的GitHub Release页面确认一下对应版本的构建方式。主备库的IP、端口、数据目录这些规划好之后建议直接写进一个备忘文件后面每一步配置都要对上。我最开始搭的时候吃过亏备库的连接信息写错一个端口花了半小时排查结果就是配置文件名看错了。2.2 主库基础配置归档、监听与复制账号pg_rman本身不强制要求主库开启归档但如果你想用增量备份或者想实现备份之后再补WAL的恢复能力归档就必须开。搭建流复制备库更是离不开归档备库在追平主库的过程中一旦流复制连接中断比如网络抖动备库会转而读取归档目录里的WAL文件来补齐GAP归档没开或归档目录不完整备库就只能一直卡住。主库的postgresql.conf里需要调整的核心参数如下# 监听配置 listen_addresses 0.0.0.0 port 5432 # 归档配置 wal_level replica archive_mode on archive_command cp %p /pgdata/14/archive/%f archive_timeout 300 # 复制相关 max_wal_senders 10 wal_keep_size 1024 hot_standby on参数含义我简单解释一下wal_level replica是流复制的最低要求如果是9.6以前的版本对应的是wal_level hot_standby现在统一叫replica即可。这个参数决定了WAL日志里会记录足够的信息备库才能解析并重放。archive_command把每个WAL文件从pg_wal目录拷贝到归档目录。注意%p是源文件完整路径%f是文件名这是PostgreSQL预留的占位符不能写错。archive_timeout 300表示最多5秒强制切换一次WAL防止长时间没有事务产生时WAL一直不切换导致备库迟迟拿不到新日志。这个值可以按业务调整一般300秒够用。max_wal_senders 10是允许同时多少个备库或备份工具连接主库拉取WAL。每增加一个standby至少需要1个slot或1个sender备库多的话这个值要相应调大。wal_keep_size 1024表示主库的pg_wal目录里至少保留1024MB的WAL防止备库短时间断连后需要回主库找旧WAL时找不到。如果配置了复制槽replication slot的话这个参数可以适当调小。还有一个复制账号必须创建不能直接用超级用户连流复制接口安全性和权限控制都不好做CREATE ROLE replica LOGIN REPLICATION PASSWORD replica_pass;pg_hba.conf里加上对应的访问规则# 允许备库机器以replica账号连接流复制 host replication replica 192.168.1.11/32 md5这里有个很典型的坑很多人在pg_hba.conf里只加了普通数据库连接规则忘了加replication这个数据库特殊条目结果备库启动时报could not receive data from WAL stream: FATAL: no pg_hba.conf entry。其实replication在pg_hba里是作为一种特殊的数据库名来处理的必须单独配一条。配完之后重启主库用下面这条命令验证一下能不能正常连过去psql -h 192.168.1.10 -U replica -d postgres -c IDENTIFY_SYSTEM如果返回了系统标识和WAL位置信息说明复制通道已经通了。3. pg_rman安装与物理备份实操3.1 pg_rman的编译安装与初始化pg_rman可以从源码编译也可以用部分发行版的预编译包。用源码编译其实非常快依赖也比较少。我这边是在主库机器上编译一份然后直接把二进制拷贝到备库机器这样能保证两边版本完全一样。编译步骤如下# 下载源码这里以1.2.15为例 wget https://github.com/ossc-db/pg_rman/archive/refs/tags/v1.2.15.tar.gz tar zxvf v1.2.15.tar.gz cd pg_rman-1.2.15 # 编译安装注意PG_CONFIG的路径要指向你的pg_config make PG_CONFIG/usr/pgsql-14/bin/pg_config make install PG_CONFIG/usr/pgsql-14/bin/pg_config # 验证版本 pg_rman --version编译过程中如果报缺少libpq-fe.h之类的头文件说明postgresql-devel包没装全先执行yum install postgresql14-devel补上再编。装好pg_rman之后要做一次初始化指定备份目录等基础信息mkdir -p /pgdata/14/backup chown postgres:postgres /pgdata/14/backup # 切换到postgres用户执行 pg_rman init -B /pgdata/14/backup-B参数指定的是pg_rman的备份目录它会在里面创建backup、arclog、srvlog等子目录分别存放备份元数据、归档日志副本和服务器日志。这一步做完后pg_rman就能通过读取主库的postgresql.conf等配置知道数据目录在哪里。3.2 全量备份与备份校验pg_rman的全量备份命令非常简洁pg_rman backup -B /pgdata/14/backup -b full -C -Z none -h 127.0.0.1 -U replica参数说明-b full表示全量备份。pg_rman支持full、incremental、archive三种备份模式全量备份是所有后续恢复和增量备份的基础。-C表示备份完成后立即做一次校验校验的内容包括备份文件是否完整、WAL段是否齐全、备份目录结构是否正确。-Z none表示不压缩。对于备份目录空间充足、想要恢复速度更快的场景不压缩是更好的选择。如果磁盘紧张也可以改用-Z zlib或-Z zstd但恢复时会有额外的解压开销。-h 127.0.0.1 -U replica让pg_rman通过复制协议连接主库获取备份期间的WAL信息。其实pg_rman备份时会自动调用pg_start_backup/pg_stop_backup正常情况下不需要额外指定连接参数但指定一下能确保它找到正确的连接入口。备份完成后执行下面的命令查看备份状态pg_rman show -B /pgdata/14/backup输出类似这样 StartTime Mode Arc Type Size TLI Status 2025-01-12 02:00:01 FULL YES FULL 12GB 1 OK重点看两个字段Arc为YES表示归档日志也一并被备份管理起来了。Status为OK表示备份校验通过。如果状态是RUNNING或者CORRUPT那就要小心了先看看日志排查别急着把备份往备库拉。3.3 备库机器的备份拷贝与pg_rman配置在全量备份完成、状态OK之后把整个备份目录从主库拷贝到备库。拷贝方式可以是scp、rsync甚至挂载NFS直接共享目录。我习惯用rsync因为断点续传和增量同步都方便rsync -avP --delete /pgdata/14/backup/ postgres192.168.1.11:/pgdata/14/backup/拷贝完成后在备库机器上也执行一次pg_rman初始化其实初始化主要是生成目录结构和pg_rman.ini目录拷过来之后理论上直接就能识别但保险起见我还是会跑一次init让pg_rman把配置刷干净pg_rman init -B /pgdata/14/backup另外备库机器上也需要安装PostgreSQL 14的软件包数据目录的属主设为postgres用户。这里有一个必须注意的地方备份文件里的数据目录路径和备库机器上的路径如果不一样restore的时候要指定-T参数做路径映射。3.4 表空间路径映射与restore恢复pg_rman的restore命令可以把备份恢复到指定的数据目录。如果主库和备库的目录结构完全一致那restore会非常省心pg_rman restore -B /pgdata/14/backup -D /pgdata/14/data如果主备库的路径不一致比如主库的数据目录是/data/pgdata备库是/pgdata/14/data那需要给pg_rman指定-T参数做路径映射格式是-T 主库实际路径备库目标路径。多个表空间就写多个-Tpg_rman restore -B /pgdata/14/backup -D /pgdata/14/data \ -T /data/pgdata/pgdata/14/data \ -T /data/tbs_user/pgdata/14/tbs_userrestore完成后pg_rman会在数据目录里生成一个recovery.signal文件PostgreSQL 12的机制同时还有一个pg_rman自己的恢复标记文件表示这份数据处于恢复模式。此时还不能直接启动备库还需要做一些配置变更。注意restore操作是在停止PostgreSQL进程的情况下执行的。如果备库机器上有残留的postmaster进程先停干净否则restore过程中文件冲突极容易把备份目录搞坏。4. 流复制备库搭建与状态验证4.1 生成standby.signal并配置主库连接PostgreSQL 12之后的版本备库模式的开关不再是recovery.conf而是数据目录下的standby.signal文件。只要这个文件存在实例启动后就会以standby模式运行从主库流式获取WAL。在备库数据目录里执行touch /pgdata/14/data/standby.signal chown postgres:postgres /pgdata/14/data/standby.signal然后编辑备库的postgresql.conf把主库连接信息写进去。PostgreSQL 12开始主库连接串放在primary_conninfo参数里primary_conninfo host192.168.1.10 port5432 userreplica passwordreplica_pass application_namestandby01 primary_slot_name standby01_slot这里我建议再创建一个物理复制槽replication slot来保证备库不断流。具体做法是在主库上提前创建SELECT * FROM pg_create_physical_replication_slot(standby01_slot);用了复制槽之后主库会一直保留备库尚未消费的WAL文件即使备库长时间断连也不会因为WAL被清理而导致备库必须重新全量备份。代价是如果备库长期离线主库的pg_wal目录会持续膨胀。所以在设置复制槽的同时最好配套一个监控比如每小时检查一次复制延迟超过阈值就告警。备库还有几个关键参数建议确认hot_standby onhot_standby开启后备库才能对外提供只读查询服务。默认值本来也是on但如果是手工恢复出来的数据目录配置可能被修改过最好显式确认一下。4.2 启动备库并追平数据所有配置都就绪后启动备库pg_ctl -D /pgdata/14/data start启动日志会输出类似下面的关键信息LOG: starting point-in-time recovery to XXXX LOG: restored log file 000000010000000000000001 from archive LOG: entering standby mode LOG: redo starts at 0/20000028 LOG: consistent recovery state reached at 0/20000100 LOG: database system is ready to accept read only connections看到entering standby mode和database system is ready to accept read only connections就基本成功了。此时备库已经从全量备份恢复到备份点然后通过归档日志和流复制继续追赶主库的最新数据。为了确认追平主库分别在主备库上执行-- 主库 SELECT pg_current_wal_lsn(); -- 备库 SELECT pg_last_wal_replay_lsn(); SELECT pg_is_in_recovery();备库追平之后pg_last_wal_replay_lsn()会不断接近主库的pg_current_wal_lsn()如果长时间不涨说明复制可能中断了。更直观的查询是用pg_stat_replication视图-- 在主库上执行 SELECT application_name, state, sync_state, replay_lsn, replay_lag FROM pg_stat_replication;state字段是streamingreplay_lag是一个间隔类型表示备库落后主库多少时间。正常情况下应该是几十毫秒到几秒之间。4.3 常用状态验证命令汇总为了方便日常巡检我把这套环境里常用的验证SQL和命令整理成了一张速查表检查目标执行位置命令/SQL主库当前WAL位置主库SELECT pg_current_wal_lsn();备库回放位置备库SELECT pg_last_wal_replay_lsn();是否处于恢复模式备库SELECT pg_is_in_recovery();复制连接状态主库SELECT * FROM pg_stat_replication;提前复制槽状态主库SELECT * FROM pg_replication_slots;备库WAL接收进程备库SELECT * FROM pg_stat_wal_receiver;主备延迟大小主库SELECT replay_lag FROM pg_stat_replication;日常我会写一个简单的Shell脚本定期抓取这些指标延迟超过阈值就推送告警。这里特别提醒一下pg_stat_replication只能看到主库视角的信息如果想确认备库实际收到的WAL已经应用到了哪里必须去备库查pg_last_wal_replay_lsn()。5. 常见问题与排查技巧实录5.1 备库启动报错对照表搭建备库过程中我遇到过不少报错这里挑几个典型场景整理出来大家可以直接对号入座。1. FATAL: could not receive data from WAL stream: FATAL: no pg_hba.conf entry for replication connection from host 192.168.1.11, user replica这个报错非常经典原因就是前面提到的pg_hba.conf里没有添加针对replication的访问规则。注意不是普通的host all all ...那条而是要单独加一条host replication replica ...。2. FATAL: standby signal file found, but primary_conninfo is not specified这个是在PostgreSQL 12版本里常见的配置遗漏。standby.signal存在的情况下主库连接信息必须通过primary_conninfo参数提供不能只靠配置归档命令去推断。检查备库的postgresql.conf里有没有正确设置primary_conninfo。3. LOG: restored log file 000000010000000000000002 from archive ... LOG: recovery stopping after processing recovery target这个不是报错是恢复到达了指定恢复点之后正常停止。如果出现这个输出说明数据目录里可能还残留了recovery_target相关的配置或者是pg_rman restore的时候自动生成了recovery.signal并设置了recovery target。此时需要清理掉恢复目标配置确保备库可以进入持续的standby模式。4. pg_rman: ERROR: backup is not taken yet执行restore的时候如果提示备份还没生成先检查pg_rman show的输出确认备份状态另外也要确认-B参数指向的备份目录是否包含之前的全量备份。有时候备份目录被清空或移动了位置这个报错就会跑出来。5. 主库磁盘被WAL撑满这种情况多半是因为复制槽创建了但备库长时间没连接主库要保留备库所需的WAL导致pg_wal疯涨。我踩过一次之后给系统加了一个定期清理的定时任务会检查pg_replication_slots里active字段为false的复制槽超过一定时间就告警避免磁盘被拖垮。5.2 延迟排查的重要思路备库延迟高是一个比较复杂的排查项可能是网络问题可能是主库WAL生成太快也可能是备库的磁盘IO完全跟不上。我先给出一套基本排查顺序先看备库的pg_stat_wal_receiver视图确认接收进程是否正常工作。如果status是streaminglatest_end_lsn在前进说明WAL传输没问题问题出在重放端。再看备库的pg_last_wal_replay_lsn()是否明显落后于pg_stat_wal_receiver里的latest_end_lsn。如果接收端和回放端有明显差距说明备库重放能力不足。这时候要检查备库的磁盘IO、CPU使用率尤其是比较差的机械硬盘上如果并发很多只读查询重放进程可能被挤到后面。还要注意主库的archive_timeout设置。如果业务写入很小WAL长时间不切换备库replay_lag也会显示增长但这其实是假象备库只是在等新的WAL到达。所以排查延迟时一定要结合主库当前的WAL写入情况综合判断。5.3 备份恢复后的数据目录清理pg_rman restore之后数据目录里可能会残留主库的一些运行时文件比如postmaster.pid、pg_stat_tmp目录里的临时统计信息。这些文件在备库启动前最好清理掉否则可能导致启动异常。我一般是这样处理的rm -f /pgdata/14/data/postmaster.pid rm -rf /pgdata/14/data/pg_stat_tmp/*还有pg_wal目录由于备份过程中会拷贝部分WALrestore之后这个目录里可能有旧的WAL文件。保留它们也不会有大问题PostgreSQL在恢复时会自动处理但为了干净可以只保留最新的几个文件其余清理掉避免备库首启时误用旧日志。5.4 从备库回退为普通主库的特殊操作有时候我们搭备库只是为了做数据恢复或者测试并不需要它一直维持备库状态。要从备库切换回独立主库需要删除standby.signal文件然后重启实例。但注意这个操作是破坏性的一旦转为普通主库就再也无法无缝追平原来的主库了。所以执行前要想清楚rm -f /pgdata/14/data/standby.signal pg_ctl -D /pgdata/14/data restart如果只是想临时测试备库的数据建议直接启动一个临时实例绑定不同端口不要污染备库角色。6. 一些经验和技巧分享6.1 备份保留策略全量加增量怎么配pg_rman搭备库的核心是先全量物理备份再追WAL。备份保留策略建议至少保留最近一次全量备份外加全量备份时间点之后的所有归档日志。我在生产库上的习惯是每周日凌晨执行一次全量备份。每天凌晨执行一次增量备份基于最近一次全量备份。归档日志保留最近7天防止备库长时间离线后还能通过归档补日志。pg_rman管理这些策略非常简单# 每周全量 pg_rman backup -B /pgdata/14/backup -b full -C -Z none # 每日增量 pg_rman backup -B /pgdata/14/backup -b incremental -C -Z none # 清理7天前的备份 pg_rman delete -B /pgdata/14/backup -A 7 -d -C这里-A 7表示删除7天前的备份数据-d表示同时删除过期的归档日志-C表示清理后执行校验。这套策略跑起来后备份目录的磁盘占用需要提前评估特别是全量备份比较大时保留两份全量备份的空间是必须的。6.2 通过pg_rman制作特殊恢复点除了搭备库pg_rman在时间点恢复PITR上的表现也很优秀。比如你在主库上误删了一张业务表可以通过pg_rman的recovery target time恢复到一个误操作前的时刻。不过搭备库用不到这个特性这里不展开但建议大家了解一下pg_rman的show -B输出和restore --recovery-target-time参数关键时刻能救命。6.3 自动化脚本的骨架参考我把pg_rman全量备份加备库恢复的常用命令封装成了一个简单的脚本结构大概是这样#!/bin/bash # 主库全量备份示例脚本 BACKUP_DIR/pgdata/14/backup PGDATA/pgdata/14/data PG_USERpostgres # 1. 执行全量备份并校验 pg_rman backup -B $BACKUP_DIR -b full -C -Z none if [ $? -ne 0 ]; then echo [ERROR] pg_rman full backup failed exit 1 fi # 2. 查看备份结果 pg_rman show -B $BACKUP_DIR # 3. 同步备份目录到备库节点 rsync -avP --delete $BACKUP_DIR/ postgres192.168.1.11:$BACKUP_DIR/在实际环境中备份开始之前最好check一下主库的WAL归档状态确认archive_command返回正常pg_stat_archiver里的last_archived_time是最近的时间否则备份期间的WAL没成功归档的话恢复时就可能出现GAP。6.4 备库扩展只读负载均衡和故障切换备库搭建完成只是第一步。生产环境里流复制备库通常还承担着只读查询分流和高可用切换的责任。如果是承接分析型查询建议在备库上再开hot_standby_feedback on避免备库上的长事务导致主库VACUUM把备库需要的元组清理掉进而引发查询冲突。hot_standby_feedback开启之后主库的VACUUM会有一定的阻塞风险所以这个参数要结合业务评估。如果备库只跑几秒内的短查询不开启也没问题。故障切换的话常见做法是用repmgr或Patroni来管理。pg_rman在这里的角色是基础备份提供者Patroni初始化备库时也可以选择用pg_rman的备份做基础数据具体配置项大家按各自的编排工具来调整。总之pg_rman打底再加上一套高可用管理软件整个主备架构就比较完善了。7. 写在最后的实操体会整套流程走下来我最想单独拎出来说的体会是pg_rman方案的成败往往不取决于备份和restore命令本身而是取决于你对WAL归档链路和目录路径映射的理解。很多人第一次在这套方案上卡住都是因为归档没有真正生效或者restore时数据目录路径没有对齐。建议大家在正式操作之前先用一套小数据量的测试环境完整演练一遍确认pg_rman show状态为OK、备库启动后replay_lag稳定收敛再去动生产库。另外物理备份工具的使用习惯也很重要。pg_rman的backup命令执行期间会在主库做检查点对IO还是有一定影响尽量避开业务高峰期。备份完成后一定要执行-C校验不要跳过校验过了再谈后续的拷备和恢复。如果备份本身是坏的后面所有步骤都会白费。最后再分享一个小技巧备份目录里的pg_rman.ini会记录每次备份的起始LSN和结束LSN查看备份元数据时直接看这个文件比解析show输出的结果更快。如果哪次搭建备库总觉得差一点没追上优先去这个文件里看备份起始LSN和主库当前LSN的差距能快速定位是备份太老还是网络同步太慢。搭建流复制备库并不复杂但每一步都有细节。把pg_rman这一套玩熟了以后不管是扩容只读节点、做备份恢复演练还是配合高可用方案做切换都会顺手很多。