PostgreSQL主备切换后快速同步:pg_rewind原理与实操指南

📅 发布时间:2026/10/21 16:11:03
PostgreSQL主备切换后快速同步:pg_rewind原理与实操指南
主备切换做完业务恢复了真正的麻烦才刚开始。旧主库还在机房里躺着要么重新加入集群当备库要么彻底重建。如果你在PostgreSQL 12环境里用pg_basebackup全量重建一个几TB的实例从网络传输到数据校验几个小时甚至一整天就这么没了。而pg_rewind这个工具恰恰就是专门解决这个问题的它不拷贝全量数据只把旧主库落后于新主库的那些数据块找出来同步过去通常十几秒到几分钟就能完成。这篇文章我会把pg_rewind的原理、前置条件、完整操作步骤和实际踩坑经验一次讲透适合正在维护PostgreSQL高可用集群的DBA也适合刚接触PG复制机制、想搞明白主备切换背后逻辑的开发者。1. 为什么要用pg_rewind主备切换后的数据同步难题1.1 传统重建备库方式的痛点先还原一个最典型的场景。某天凌晨主库所在服务器硬件告警数据库实例直接宕掉监控系统把备库提升为新主库业务恢复。白天上班你面对的任务是把修好的旧主库重新变成新主库的备库。这时候一个很现实的问题摆在面前数据怎么同步最简单的方案是pg_basebackup全量备份恢复。它的逻辑清晰、步骤简单用新主库的一份全量物理备份把旧主库整个覆盖掉就行了。但问题在于“全量”两个字。数据量小的时候无所谓一旦实例到了几个TB的规模全量备份就意味着要把所有数据文件重新传输一遍这个时间窗口里集群处于完全没有备库的状态。换句话说如果这段时间新主库再出毛病整个集群就没有任何退路了。稍微懂行的人可能会说用rsync按文件增量同步行不行rsync确实比全量拷贝快但它的工作方式对PostgreSQL的数据目录并不友好。PostgreSQL的数据文件是动辄几十GB的单个二进制大文件rsync要完整扫描文件内容做块级比较这个过程开销很大。而且最要命的是如果同步过程中数据库还有写入rsync拿到的文件状态可能是不一致的根本不能直接作为数据库数据目录使用。所以rsync可以用于归档类文件不适合做数据库实例的在线同步。pg_rewind的思路完全绕开了上面这些坑。它不是去比较文件也不做全量传输而是借助WAL日志精准定位旧主库上哪些数据块在时间线分叉之后被修改过然后只同步这些差异块。这个机制让同步耗时从“取决于数据总量”变成“取决于分叉后的修改量”在绝大多数切换场景下都能把小时级操作压缩到分钟级甚至秒级。1.2 pg_rewind的工作原理与核心优势要理解pg_rewind得先理解PostgreSQL的时间线机制。主备切换发生的时候备库被提升为新主库它会在当前WAL位置创建一个新的时间线标识之后的写入都基于新时间线继续。旧主库如果之前也接受过一些写入那么它和新主库在WAL序列上就产生了分叉。这个分叉点就是pg_rewind整个操作的基准。pg_rewind的做法是这样它连接新主库读取新主库当前的数据文件状态和WAL历史同时读取旧主库数据目录里的pg_control信息算出自分叉点以来、旧主库的数据目录中哪些数据块被修改过。然后它把这些“落后”的数据块直接从新主库复制过来覆盖旧主库上对应的陈旧内容。最后再把新主库的WAL历史文件、时间线信息等元数据同步过来完成整个数据目录的“回退与对齐”。这个过程有个核心前提pg_rewind必须能够判断“某个数据块自分叉点以来发生了哪些变化”而这个判断完全依赖WAL日志和页面上的LSN标记。只要WAL记录包含了足够的信息pg_rewind就能精准找到每个需要覆盖的页面。这就是为什么官方文档和所有教程都反复强调必须开启full_page_writes以及wal_log_hints或data checksums之一没有这些开关WAL记录里缺少判断页面完整状态的依据pg_rewind就无法安全运行。打个生活化的比方全量重建相当于把整本书重新抄一遍pg_rewind则只需要知道这本书在某个章节之后有哪些页被改过把这几页撕掉换上新内容。前提是你得有一本内容完整的底本——也就是新主库的数据目录还得知道到底改过哪几页——也就是完整的WAL记录。这套机制带来的收益在数据量越大时越明显所以凡是管过大型PG实例的人几乎都会把pg_rewind当作家底工具。2. 用pg_rewind前必须满足的硬性条件与关键参数2.1 三个关键开关full_page_writes、wal_log_hints与data checksumspg_rewind之所以能定位差异块靠的是读取WAL记录里每个页面的状态。具体来说数据库在运行中修改任何数据页面时都会生成对应的WAL记录页面头部会记录最后一次修改该页面的LSN。pg_rewind拿到分叉点之后会扫描WAL找出所有LSN大于分叉点的页面这些页面就是需要从源端同步的候选页面。这里就有一个隐含要求WAL记录里必须能看到页面的完整变更信息。如果WAL只记录了页面中某一行数据的差异而没有完整的页面状态pg_rewind就无法判断这个页面在分叉点之后是否被动过。于是就有了三个关键参数。第一个是full_page_writes它默认就是on。它的作用是在每次检查点之后第一次修改某个页面的WAL记录会包含整个页面的内容快照。这个机制原本是为了崩溃恢复时避免页面“半更新”状态导致的数据损坏恰好也给pg_rewind提供了可靠的页面判断依据。第二个是wal_log_hints。PostgreSQL在标记元组可见性时会修改所谓的hint bits这些修改默认不会写入WAL。但hint bits的变更同样会改变数据页面的状态如果不记录在WAL里pg_rewind就可能在扫描时漏掉某个实际被修改过的页面。所以pg_rewind要求wal_log_hints必须是on或者整个实例启用了data checksums。第三个是data checksums。这个参数在initdb阶段决定initdb -k会启用数据校验和。如果启用了checksums每个页面都有校验和pg_rewind可以用它来精确判断页面是否被修改此时即使wal_log_hints保持默认的off也可以正常工作。换句话说wal_log_hints和data checksums只需要满足其中一个再加上full_page_writesonpg_rewind才有安全运行的前提。实际操作中我建议在搭建集群的最初就把wal_log_hints设为on。因为它只是一个控制WAL记录的开关对性能的影响在绝大多数场景下可以忽略但它让你在后续任何一次故障切换时都拥有使用pg_rewind的能力省去临时改配置、重启实例的麻烦。2.2 环境准备、权限与连接要求除了参数开关pg_rewind运行前后还有几个硬性要求任何一个不满足都会直接终止执行。第一旧主库必须彻底停止而且要确保所有postgres进程都退出。所谓“干净关闭”指的是实例已经完成了关闭检查点或者已经完成了崩溃恢复但没有重新启动。如果你只是关掉了主进程但还有残留的postgres子进程占着数据目录pg_rewind会拒绝执行并报出那句非常经典的错误target server must be shut down cleanly。第二必须用postgres操作系统用户来运行pg_rewindroot用户不行。因为整个数据目录的所有者都是postgres用root执行会在权限和文件属主上搞出很多隐患pg_rewind本身也会强制拒绝。我自己见过有人用sudo直接跑结果一堆文件属主变成root后续实例启动直接失败。第三连接新主库的连接串所使用的用户必须具有超级用户权限或者等效权限。很多教程喜欢用replicator这个复制用户来建连接串但replicator通常只具备REPLICATION权限不够pg_rewind使用。更稳妥的做法是直接用postgres超级用户连接并提前在pg_hba.conf里放行同时也要允许来自旧主库IP的复制连接因为pg_rewind内部会用到复制相关的系统函数。第四pg_rewind的二进制版本必须与目标数据目录的主版本一致。PostgreSQL 12的实例最好就用PG12安装包自带的pg_rewind。不同大版本之间即使能执行也可能因为文件格式的细微差异导致数据目录损坏。2.3 pg_rewind命令格式与参数详解先看一条完整的命令长什么样pg_rewind --target-pgdata/var/lib/pgsql/12/data \ --source-serverhost192.168.10.20 port5432 userpostgres dbnamepostgres password你的密码 \ -P--target-pgdata指定旧主库的数据目录这是要被覆盖修改的一方。--source-server指定新主库的连接串格式与libpq连接串一样。由于这里走的是普通TCP连接所以密码可以写进连接串里或者设置PGPASSWORD环境变量。-P是指显示进度。pg_rewind在执行过程中会输出大量与WAL扫描和文件同步相关的信息不加-P的话看着就像卡住了实际上它在后台默默干了很多活。-n或--dry-run是预演模式。这个参数只做检查不实际修改数据目录。它会告诉你“如果可以执行pg_rewind将会发生什么”但不会动任何文件。生产环境操作前一定要先跑一遍预演确认没有致命的配置问题再正式执行。--restore-target-wal这个参数在PG12里可以指定从归档恢复目标端的WAL段文件。如果你的实例配置了归档并且旧主库已经有部分WAL段被归档到了共享存储用这个参数可以减少从源端拉取WAL的负担。不过使用它之前需要确认归档路径可读、WAL段完整否则会适得其反。要特别提醒的是PG12的pg_rewind还没有--config-file参数这是PG13才加入的。如果你在网上找教程看到有人用--config-file指定postgresql.conf直接照搬在PG12环境下就会报“unrecognized option”。这种版本差异导致的命令解析失败在实际运维中并不少见。还有一个容易被忽视的点pg_rewind执行时会在目标数据目录里创建临时目录来存放中间文件如果目标目录所在分区空间不足执行会在中途失败。经验值上至少保留相当于主库数据量5%到10%的空闲空间才比较安全。3. PostgreSQL 12实操全流程从故障切换到备库重建3.1 模拟故障场景与环境信息我以一个非常典型的双节点集群来演示完整流程。假设某公司的一套系统使用两台服务器搭建主备复制主库A和备库B。某天A故障B被提升为新主库业务恢复。第二天A修复完成需要把它重新加入集群作为B的备库。环境信息如下主库A旧主库IP 192.168.10.10数据目录 /var/lib/pgsql/12/data新主库BIP 192.168.10.20数据目录 /var/lib/pgsql/12/data数据库版本PostgreSQL 12.x复制用户replicator动手之前先确认B上的配置满足pg_rewind的前置条件。用postgres用户登录B执行SHOW full_page_writes; SHOW wal_log_hints; SHOW data_checksums;如果wal_log_hints是off且data_checksums是off那就要先改postgresql.conf把wal_log_hints设为on然后重启B。这一步必须在执行切换之前就准备好否则到需要pg_rewind的时候临时改配置会额外增加一次实例重启在高可用环境里是很大的负担。如果你当初initdb的时候没有加-k参数启用data checksums那现在唯一的路径就是打开wal_log_hints并重启没有捷径。还要顺带确认B上pg_hba.conf允许A的IP以超级用户身份连接host all postgres 192.168.10.10/32 md5 host replication all 192.168.10.10/32 md5这两条规则前面一条用于pg_rewind建立普通连接后面一条是为了后续A作为备库时建立复制连接。顺序上要放在任意拒绝规则之前否则会被前置规则拦截。3.2 完整操作步骤与命令演示第一步在旧主库A上停止数据库服务。systemctl stop postgresql-12如果你用的是源码编译的实例则执行pg_ctl -D /var/lib/pgsql/12/data stop -m fast这里推荐使用fast模式关闭也就是快速关闭。它会主动做一次检查点让数据目录处于一致性状态pg_rewind能够正常读取关闭信息。关闭之后用ps命令确认所有postgres进程已经退出ps aux | grep postgres这一步绝对不要省略。我在实际运维中遇到过systemctl stop执行完但某个后台连接进程还挂着的情况直接跑pg_rewind就会遇到shut down cleanly的错误。第二步在A上执行pg_rewind预演。pg_rewind --target-pgdata/var/lib/pgsql/12/data \ --source-serverhost192.168.10.20 port5432 userpostgres dbnamepostgres password你的密码 \ -n -P预演的核心作用是提前暴露问题比如连接不上、目标目录状态不对、WAL版本不匹配等。看到“pg_rewind: done.”类似的输出说明检查通过。如果预演报错不要跳过先根据报错排查等到预演干净通过再进入下一步。第三步正式执行pg_rewind。pg_rewind --target-pgdata/var/lib/pgsql/12/data \ --source-serverhost192.168.10.20 port5432 userpostgres dbnamepostgres password你的密码 \ -P执行过程中pg_rewind会依次输出解析目标数据目录状态、连接源端、读取源端时间线、计算分叉点、扫描目标目录中的差异块、从源端复制对应数据块、同步WAL历史文件。这个过程的长短取决于分叉后修改的数据量和数据库总大小没有直接关系。如果A和B在切换前数据基本一致整个过程可能只要几十秒。第四步把A配置成B的备库。pg_rewind执行完不会自动配置备库身份需要手工操作。在PG12中创建standby.signal文件即表示以备库模式启动touch /var/lib/pgsql/12/data/standby.signal然后修改postgresql.conf设置primary_conninfo指向新主库Bprimary_conninfo host192.168.10.20 port5432 userreplicator password你的密码 application_namestandby1这里有几个细节必须说清楚。application_name可以自定义建议设置一个有辨识度的名字方便之后在新主库的pg_stat_replication里区分不同备库。replicator用户需要在B上具有REPLICATION权限并且B的pg_hba.conf已经放行了A的复制连接。还有一个非常隐蔽的坑如果A在提升为主库之前postgresql.conf里就有一段指向原主库的primary_conninfo残留配置pg_rewind不会动它你必须手动改掉否则A启动后会尝试连接已经不存在的旧主库地址一直在那里重试看起来就像复制卡住了一样。第五步启动A并验证复制状态。systemctl start postgresql-12启动完成后在A上执行SELECT pg_is_in_recovery();返回t说明它已经以备库身份运行。接着看数据追平情况SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();这两个函数返回的是当前接收到的WAL LSN和已经重放完成的WAL LSN。如果它们和B上当前WAL写入位置的差距在持续缩小说明复制在正常进行。也可以直接到B上查看SELECT client_addr, state, sync_state, application_name FROM pg_stat_replication;正常情况下能看到来自A的复制连接state是streaming。3.3 切换后的业务影响验证复制状态正常不等于业务数据没问题。我习惯在切换验证环节加一个小测试在新主库B上建一张临时表写入几条记录等几秒然后在旧主库A上查询这张表确认数据已经同步过来。-- 在新主库B上执行 CREATE TABLE public.rewind_check (id int primary key, note text); INSERT INTO public.rewind_check VALUES (1, after failover); -- 在旧主库A上执行 SELECT * FROM public.rewind_check;如果查到了记录说明数据链路是真的通了而不是仅仅停留在“看起来streaming”的状态。有时候primary_conninfo里的用户密码配置错误pg_stat_replication里也能看到尝试连接的记录但数据就是不同步。这种静态检查的误导性很强只有通过业务数据的动态验证才最可靠。这个测试表用完记得删掉避免污染业务库。另外一个建议是把这一整套验证流程固化成脚本每次切换后都自动执行一遍省得临时在命令行里敲。4. 常见问题与排查技巧实录4.1 高频报错与处理方案汇总pg_rewind的报错信息大多比较直白但真正麻烦的是同一个表面报错背后的多种可能原因。我把实际运维中遇到过的高频问题整理成表格方便对照排查。错误信息可能原因处理方式target server must be shut down cleanly旧主库进程未完全退出或实例仍在运行确认停止服务清除残留进程后重试could not connect to source server连接串错误、网络不通、pg_hba.conf未放行先用psql手动连接源端排除基础网络问题source system still in recovery新主库尚未提升成功或还在恢复状态在新主库执行pg_ctl promote确认其可写target data directory has wrong versionpg_rewind版本与数据目录版本不一致使用与数据库大版本匹配的pg_rewindcould not open file pg_wal/xxxxWAL文件缺失或不可读检查归档完整性确认wal_log_hints配置permission denied以root运行pg_rewind或目录权限错误切换到postgres系统用户后重跑no space left on device目标目录分区空间不足清理磁盘或扩容后再执行这些错误里最让我印象深刻的是source system still in recovery。它在一种隐蔽场景下会出现新主库B被提升后如果你没有等它完全结束恢复状态就立刻执行pg_rewind某些网络延迟或加载较慢的节点上B实际上还在只读恢复模式pg_rewind连接上去后会发现源端还在recovery直接拒绝工作。解决方法是等B的日志输出出现“database system is ready to accept connections”之后再操作或者执行pg_ctl promote之后确认一下。4.2 几个容易忽略的坑与我的排查经验第一个坑旧主库在immediate关闭之后又启动过一次。有些运维兄弟在旧主库宕机之后习惯性先拉起来看一眼日志、确认一下状态然后又停掉结果这一起一停之间实例完成了一轮崩溃恢复产生了一个新的检查点。pg_rewind再去读它的状态时会发现目标库的WAL信息已经和源端对不上了要么报错要么无法安全计算分叉点。我的经验法则是一旦决定走pg_rewind路线旧主库停掉之后就别再启动它了。要检查什么提前想清楚一次性做完。第二个坑忘记检查postgresql.auto.conf里的残留配置。PG12中很多复制相关参数会被ALTER SYSTEM写入postgresql.auto.conf。如果旧主库曾经作为备库配置过primary_conninfopg_rewind并不会清理这个文件启动时这些旧配置可能和新配置产生冲突。最典型的症状是你明明在postgresql.conf里改了primary_conninfo但实例启动后连接的还是旧地址。排查了很久才发现是postgresql.auto.conf里的设置优先级更高。所以每次pg_rewind前后务必要查看postgresql.auto.conf的内容把不再需要的配置项手动清理掉。第三个坑把pg_basebackup的习惯带到pg_rewind里。有朋友试图给pg_rewind加-R参数来生成配置文件结果直接报“unrecognized option”。这其实是混淆了工具的职责。pg_basebackup是物理备份工具-R的作用是自动生成备库配置pg_rewind是时间线对齐工具它只负责把数据目录状态对齐到源端。备库身份的建立在PG12中就是创建standby.signal文件并设置primary_conninfo没有命令参数可以代劳。第四个坑WAL归档不完整导致pg_rewind无法读取所需WAL段。pg_rewind在计算差异块时需要扫描分叉点之后的WAL记录。如果主库没有配置归档或者归档的WAL段已经丢失而旧主库落后的距离又比较远pg_rewind就无法找到完整的WAL历史最终只能放弃。这种情况下再继续挣扎也没有意义老老实实改用pg_basebackup做全量重建。提前配置好归档任务、设置合理的wal_keep_segments参数或者使用复制槽才是避免这个局面的根本手段。4.3 PostgreSQL 12特有的行为差异PG12的pg_rewind在功能上已经相当成熟但和PG13以后的版本相比还是有几个值得注意的差异。差异点是--config-file参数不存在。PG13开始才支持用--config-file显式指定postgresql.conf让pg_rewind能获取更多自动配置信息。在PG12里你不需要也不能传这个参数命令更简洁但相应的一些自动化的配置能力也弱一些。差异点是权限模型。在PG12中pg_rewind连接源端基本默认要求超级用户权限校验比较简单粗暴。到PG14之后权限模型改为更细粒度的控制甚至可以配置专门的用户来执行pg_rewind。所以在PG12环境里连接串直接用postgres超级用户最省事别想着用普通用户来规避安全风险——你反而会碰到一堆权限报错。差异点是WAL保留策略。PG12主推的是wal_keep_segments参数和复制槽PG13引入了wal_keep_size两者的语义不同。如果你习惯在PG13里配置wal_keep_size照搬到PG12会得到“unrecognized configuration parameter”的报错。在PG12中要保留更多WAL段给pg_rewind兜底用的是wal_keep_segments把它调大一些即可代价是占用更多的pg_wal目录空间。我在一个实际项目里就遇到过因为WAL段被复用而无法执行pg_rewind的情况。当时某系统的备库因为网络抖动落后主库很多而主库的wal_keep_segments设得偏小复制槽也没有创建WAL段被循环覆盖掉了。等主库宕机、备库提升之后再对旧主库执行pg_rewind它明确提示缺少分叉点之后的WAL数据。最后没办法只能重新做一个全量备份来搭建备库白白搭进去好几个小时的同步时间。从那之后我在所有高可用集群里都要求需要保留至少足够覆盖异常滞后场景的WAL段或者直接创建复制槽让WAL不会被提前清理。最后分享一个小技巧。pg_rewind本身是幂等的也就是说如果第一次执行因为某些原因失败在排除问题之后可以重复执行。但重复执行也有前提期间源端新主库B的数据不能被大量修改否则分叉点会变化扫描范围也会扩大每次执行的时间都会更久。所以我一般建议正式执行前先把应用写入停掉或者接受一个短暂的只读窗口让源端WAL停下来这样pg_rewind的输出最稳定时间也可控。如果你的业务实在不能接受写入中断那就选择业务低谷期操作并且随时做好切换为全量重建的心理准备。