news 2026/10/21 16:10:52

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 checksums

pg_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_writes=on,pg_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_rewind,root用户不行。因为整个数据目录的所有者都是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-server="host=192.168.10.20 port=5432 user=postgres dbname=postgres 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
  • 新主库B:IP 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-server="host=192.168.10.20 port=5432 user=postgres dbname=postgres password=你的密码" \ -n -P

预演的核心作用是提前暴露问题,比如连接不上、目标目录状态不对、WAL版本不匹配等。看到“pg_rewind: done.”类似的输出,说明检查通过。如果预演报错,不要跳过,先根据报错排查,等到预演干净通过再进入下一步。

第三步,正式执行pg_rewind。

pg_rewind --target-pgdata=/var/lib/pgsql/12/data \ --source-server="host=192.168.10.20 port=5432 user=postgres dbname=postgres 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指向新主库B:

primary_conninfo = 'host=192.168.10.20 port=5432 user=replicator password=你的密码 application_name=standby1'

这里有几个细节必须说清楚。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_rewind
could 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_conninfo,pg_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的输出最稳定,时间也可控。如果你的业务实在不能接受写入中断,那就选择业务低谷期操作,并且随时做好切换为全量重建的心理准备。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 16:10:32

技术图表设计规范 diagram-design:从能看懂到长期看懂

深夜,同事往协作群里丢了一张调用关系图,用来解释某个故障排查路径。单独看每个节点都讲得通,但整张图铺开后,线头从四个方向同时冲出来,我盯着看了半分钟才勉强找到主线。这种场景在很多技术团队里太常见了&#xff1…

作者头像 李华
网站建设 2026/10/21 16:09:55

Adobe Audition音频处理实战:从降噪到混音的完整指南

简介:这是一份《AdobeAudition教程[汇编].pdf》入门教程,系统汇集了Adobe Audition从安装汉化到录音、混音、降噪的核心操作,面向音频后期初学者、视频剪辑爱好者和网络主播,帮助解决人声录制效果差、不会降噪、无法合并伴奏等常见…

作者头像 李华
网站建设 2026/10/21 16:09:45

Linux进程替换:exec系列函数原理与实战

如果你写过 Linux 上的 C 程序,大概率迟早会遇到这样一个问题:fork 出来的子进程,除了复制一份父进程的地址空间之外,它仍然在继续执行父进程的代码。想让子进程去跑一个全新的程序,而不是继续“父进程的剧本”&#x…

作者头像 李华
网站建设 2026/10/21 16:09:43

PPT密码忘了怎么办?PassFab for PPT恢复工具详解

1. 为什么需要PassFab for PPT:密码恢复的真实场景与工具边界 做课件、写汇报方案、整理对外培训材料……PPT算是日常办公里最离不开的文件类型之一了。很多人习惯在交付前给PPT加上密码保护,本意是防止内容被随意修改,这个习惯本身没有问题&…

作者头像 李华
网站建设 2026/10/21 16:09:35

MySQL 5.7无窗口函数?用GROUP BY实现组内分组统计与序号生成

写报表统计的时候,我经常遇到一类需求:按部门统计员工人数,顺手把每个人在部门内的序号带出来,或者把每组前几名单独列一列。过去我习惯性写窗口函数, ROW_NUMBER() OVER(PARTITION BY ...) 一行搞定,干净…

作者头像 李华
网站建设 2026/10/21 16:09:16

如何系统化搭建个人技能体系:从拆解到迭代的全过程

这些年我折腾过不少技能,从写代码到做设计,再到带小团队和做产品规划,最后发现一个很扎心的事实:大多数人缺的不是学习意愿,而是一套把“skills”当成系统来运营的方法。你手机里存了几十个教程,收藏夹里堆…

作者头像 李华