拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

pg_receivewal详解:WAL归档、参数与PITR恢复实践

pg_receivewal详解:WAL归档、参数与PITR恢复实践

最近在折腾 PostgreSQL 高可用和备份方案时,我又把pg_receivewal这个工具从头到尾捋了一遍。这玩意儿在 PostgreSQL 的运维体系里地位很特殊:平时存在感不高,但真到了做持续归档、搭备库、做 PITR(时间点恢复)的时候,它是绕不开的核心组件。光看名字容易懵,接收 WAL?接收什么 WAL?跟pg_basebackup什么关系?跟archive_command又有什么不同?这篇我结合自己的实操经验,把pg_receivewal的机制、参数、典型用法和踩坑记录一次讲透。不管你是刚接触 PostgreSQL 的新手,还是已经在生产环境摸爬滚打过的 DBA,这篇都能给你一些能直接拿去用的东西。

1. pg_receivewal 到底是干什么的:先把概念理清楚

1.1 从 WAL 日志归档说起

PostgreSQL 有一个非常核心的机制叫 WAL(Write-Ahead Logging),翻译成中文就是预写式日志。在 PostgreSQL 里,任何数据修改(INSERT、UPDATE、DELETE 等)不会直接去改数据文件,而是先把变更记录追加写进 WAL 日志文件里,然后再把数据页刷到磁盘。也就是说,WAL 日志在数据库崩溃恢复、在线备份、流复制中都扮演了“最可靠的记录者”的角色,它记录了数据库所有的变更历史。

这个 WAL 日志文件是分段存储的,每个文件默认 16MB,文件名由 24 个十六进制字符组成(比如000000010000000000000001),有一定的命名规则。数据库运行过程中会不断产生新的 WAL 文件,旧的会在“检查点”之后清理或回收。但是,如果你有“把 WAL 文件完整保留下来”的需求——比如要做持续归档、要做增量恢复、要搭一个有时间点恢复能力的备份系统——就必须在 WAL 文件被数据库自己覆写或删除之前,把它们另存一份到安全的地方。这个动作就叫 WAL 归档。

传统做法是在postgresql.conf里配置archive_mode = on和archive_command,让数据库在每个 WAL 文件写满、切换的时候,自动调用一条外部命令把它拷贝到归档目录或者远程对象存储。pg_receivewal走的是另一条路线:它不依赖archive_command那种“文件写完才拷”的批量方式,而是通过流复制协议直接、持续地从主库接收 WAL 数据,实时写入本地文件。这个差异非常关键,后面我会详细拆。

1.2 pg_receivewal 和 pg_basebackup 的分工

很多初学者会混淆pg_receivewal和pg_basebackup,其实这两个工具是配套的“一个拍快照、一个记流水账”的关系。

  • pg_basebackup:对数据库做一份“物理基础备份”(base backup),相当于某个时间点的全量数据快照。它可以把数据目录打成 tar 包,也能输出一个跟主库数据目录几乎一模一样的目录结构。它默认也会顺带收取备份期间产生的 WAL(通过-X stream参数),但那个 WAL 只是保证备份本身的一致性,不是给你做长期归档用的。
  • pg_receivewal:它不碰数据文件,只管“持续接收 WAL 日志”,从它启动的那个时间点开始,把主库后续产生所有 WAL 文件一直接收并保存在本地目录里。

两者配合起来就是一套很典的备份体系:pg_basebackup给你一个“基线”全量备份,pg_receivewal给你从基线时间点之后的全部增量日志。将来要恢复的时候,用基线备份恢复出一个数据目录,再重放 WAL 日志推进到任意希望的时间点,这就实现了 PITR(时间点恢复)。所以你可以把pg_receivewal理解为“专为 WAL 增量归档设计的实时抓取器”。

1.3 版本和工具对应关系

这里必须提醒一下版本问题。pg_receivewal这个名字是从 PostgreSQL 10 开始启用的,PostgreSQL 9.x 时代它叫pg_receivelog,命令行为和参数基本一致,但名字不同。如果你在 9.x 环境的文档里看到的都是pg_receivelog,不要慌,就是同一个东西的旧名。此外,pg_receivewal对应的是pg_basebackup的“兄弟工具”,它们都随 PostgreSQL 服务端一起提供,不需要额外安装其他软件包,只要你装的是完整的 PostgreSQL 发行版客户端工具,里面就带这个命令。

工具版本最好跟数据库大版本保持一致,至少是“同大版本或更高版本”。比如你的库是 PostgreSQL 15,就用 PostgreSQL 15 的pg_receivewal。因为流复制协议在不同版本间可能有小改动,用旧版客户端连新版服务器偶尔会报警告或者解析失败。我自己就遇到过客户端 13 连服务器 15 在IDENTIFY_SYSTEM阶段异常的情况,后来统一工具版本就再没出过问题。

2. 工作原理:pg_receivewal 为什么能做到实时

2.1 物理复制协议与流复制通道

要理解pg_receivewal的实时性,得先说说它跟主库之间是怎么通信的。它本质上是一个“流复制客户端”,走的是 PostgreSQL 内置的物理复制协议(Physical Replication Protocol)。连接时它先给服务器发一个IDENTIFY_SYSTEM命令确认系统标识,然后发起START_REPLICATION请求。服务器就开始把 WAL 数据作为连续的数据流发送给客户端。这个通道跟 PostgreSQL 主备流复制用的是同一套机制——你在pg_stat_replication视图里能看到pg_receivewal创建的复制连接,类型是walreceiver,应用名是自己指定的。

换句话说,pg_receivewal并不是通过archive_command那种“等 WAL 落盘再调用命令拷贝”的间接方式工作,而是直接订阅了一条“日志流水线”。只要主库还在写 WAL,它就能几乎实时的收到,所以理论上 WAL 数据到达归档端的延迟可以低到秒级甚至毫秒级,这比archive_command的“WAL 轮换之后才触发”要及时得多。

2.2 复制槽(Replication Slot)的作用

使用pg_receivewal时,强烈建议配合复制槽(replication slot)使用。复制槽是 PostgreSQL 提供的一种机制,作用是确保主库在客户端没有成功接收并确认 WAL 之前,不会提前清理掉那些 WAL 文件。放在备份和归档场景里,复制槽就像一个“安全锚点”:如果网络断了,pg_receivewal暂时连接不上,主库会为你保留它需要的 WAL,不会因为检查点推进而把 WAL 删除,等网络恢复、客户端重新连上,可以从断点继续接收,不会漏日志。

这个机制怎么实现?创建一个持久复制槽(pg_create_physical_replication_slot),或者在pg_receivewal连接时用--create-slot参数自动建。如果不用复制槽,主库根本不知道有客户端正在接收 WAL,在崩溃恢复或检查点之后很可能把 WAL 清掉,那归档就会断档。断档意味着恢复时你只能恢复到断档那个时间点之前,之后的数据全没了。所以我个人一贯的做法是:凡是pg_receivewal长期运行的场景,一律建持久槽,并且用监控盯住槽的状态。注意pg_receivewal用的是物理复制槽,不是逻辑复制槽。

2.3 工作流程全链路拆解

完整的pg_receivewal工作流程可以拆成这几步:

  1. 启动连接:客户端连接到主库,身份认证通过(此时需要有REPLICATION权限的账号)。
  2. 系统标识确认:调用IDENTIFY_SYSTEM拿到主库的系统标识符和当前 WAL 位置。
  3. 请求复制:调用START_REPLICATION,指定起始位置(如果没指定且目录里有 WAL,它会自动从目录中现有最新的文件名和位置继续;也可以指定--endpos限定接收终点)。
  4. 持续接收:主库把 WAL 数据推给客户端,客户端按 16MB 分文件,边写入边 fsync。默认情况下文件写入会带上同步状态,保证断电后文件不损坏。
  5. 周期确认:客户端定期向主库反馈接收进度(写到了哪个 LSN),主库据此判断可以推进复制槽的restart_lsn。
  6. 轮换与命名:当接收数据超过当前段文件边界(WAL 段大小默认 16MB,可通过--wal-segsize指定,但注意这个参数跟主库的段大小必须一致),pg_receivewal会把当前文件关闭并创建下一个段文件,文件名沿用主库的 WAL 段命名规则。

这整个链路是持续性的,如果你用systemctl或者 supervisor 把它托管成服务,它就会像一个小型“备机接收程序”一样一直跑着。区别在于备机是把 WAL 重放(replay)成数据页,而pg_receivewal只是把日志原样落盘,不做任何重放逻辑,所以它比备库轻量得多,开销也小很多。

3. 快速上手:从配置到第一次跑通

3.1 准备合适的账号和权限

pg_receivewal连接主库时需要一个“有复制权限”的账号。最简单的方式是创建一个专门的角色,比如:

CREATE ROLE wal_archive WITH LOGIN REPLICATION PASSWORD 'your_secure_password';

这里的关键是REPLICATION关键字。普通登录账号即使有SUPERUSER之外的所有权限,如果没有REPLICATION,也会被流复制协议拒之门外。此外还要保证pg_hba.conf里允许该账号以replication数据库名义连接。是的,你没看错,流复制连接用的“数据库名”实际上是replication,不是实际的业务库名。比如本机测试可以加这样一行:

host replication wal_archive 127.0.0.1/32 md5

生产环境建议限制来源 IP,并用scram-sha-256认证而不是md5,更安全。

3.2 主库参数调整

要让pg_receivewal正常工作,主库参数至少需要满足以下几项:

  • wal_level = replica或logical。默认的minimal级别不包含足够信息供 WAL 归档和流复制使用,必须提升到replica以上。
  • max_wal_senders至少要比现有备库数量 + 1 大。每个pg_receivewal连接都会占用一个 wal sender 进程,如果这个数设小了,连接会被拒绝。
  • max_replication_slots如果要用复制槽,也至少要比现有槽数量 + 1 大。注意槽数量不是“连接后自动释放”的,占着就一直占着,预留要足。
  • listen_addresses如果你要远程连接,得监听对应的网卡地址,不能只监听 localhost。

改完参数要重启或reload。wal_level、max_wal_senders是reload能生效的(wal_level在 9.5 以前需要重启,新版一般 reload 即可,但保险起见可以重启),max_replication_slots也是 reload 生效。我习惯是改完后执行SELECT pg_reload_conf();然后查pg_settings确认。

3.3 首次运行和验证效果

假设主库地址是192.168.1.100,归档目录是/wal_archive,账号是wal_archive,先手动运行一下验证:

mkdir -p /wal_archive pg_receivewal -h 192.168.1.100 -U wal_archive -D /wal_archive --slot=pg_receivewal_slot --create-slot --verbose

第一次跑加--create-slot,它会在主库上创建一个名为pg_receivewal_slot的物理复制槽,然后开始接收。如果看到类似waiting for WAL to be written at ...的日志,说明已经连上并处于等待状态。这时你可以在主库随便做一个事务,马上看/wal_archive目录——里面会持续生成新的0000000100000000...文件,每隔一会儿就会多一个。这就是实时接收的效果。

不过我建议正式投入使用前,先测试一下“从零开始、断点续传、数据完整性”这几个环节,别一上来就丢生产环境无人看管。测试方法后面会专门讲。

4. 核心参数详解:每个选项背后的设计意图

4.1 最常用的几个参数

pg_receivewal的参数没有几十个那么复杂,但每个都很重要。我按“必用”、“常用”、“进阶”三类梳理一下:

必用参数就两个:-D, --directory=DIR(指定 WAL 保存目录),-h, --host和-U, --username(指定连接目标)。没有目录它就没法干活,没指定连接目标它默认连本机 unix socket,实际生产一般都要显式写 IP 和账号。

常用参数包括:

  • --slot=SLOTNAME:指定使用的复制槽名。配上--create-slot可以自动建槽;槽已经存在时不要加--create-slot,否则会报冲突。
  • --if-not-exists:配合--create-slot使用,槽存在就不报错,适合脚本幂等执行。
  • -v, --verbose:打印更详细的日志。首次调试时建议必开,正式运行时可以不开,减少日志噪音。
  • -n, --no-loop:只尝试连接一次,失败就退出。默认是无限重连。脚本里探测是否通的时候可以用。
  • -F, --fsync-interval=SECONDS:控制 fsync 的频率,默认是不传就每次写完都 fsync(安全),设了这个参数可以批量刷盘,提升磁盘吞吐。但掉电可能丢失最近几秒的数据,得自己权衡。
  • -Z, --compress=LEVEL:对接收到的 WAL 文件做压缩,目前支持gzip/lz4/zstd(具体看编译选项),这个参数非常实用,后面单列一节说。
  • -R, --create-slot和--drop-slot:一个建槽一个删槽。
  • --endpos=LSN:接收到一个指定的 LSN 后自动退出,适合“手动拉取一段 WAL”的临时用途。
  • --wal-segsize=SEGSIZE:指定接收端期望的 WAL 段大小,默认 16MB,主库也是 16MB 的话不用管。

4.2 为什么--slot几乎是必选项

我看到网上有些例子没加--slot也能跑,确实能跑,但那是“裸奔”状态。不建槽的情况下,pg_receivewal连接期间主库知道它的存在,但它断开之后,主库不会再为它保留任何 WAL,一旦断线时间较长、WAL 清理推进,恢复连接时客户端会发现自己缺了一段日志,只能报错,最好的情况是没法再从那个断点接续,只能删目录重新拉一遍。重新拉意味着之前收到的全作废,这就失去了持续归档的意义。

所以我的观点是:--slot不是可选项,是必选项。你可以把复制槽理解成“主库专门帮你把 WAL 留出来的人”,没有这个人,你走了之后没人替你占位,日志随时可能被清。但反过来,槽也有一个副作用:如果pg_receivewal长期离线,主库会因为槽的restart_lsn一直不推进而无限保留 WAL,把磁盘撑爆。因此还得配套监控,超过一定阈值告警。

4.3 关于--fsync-interval和性能权衡

WAL 本身的特性决定了它写得很频繁,pg_receivewal接收数据后如果每个 16MB 文件都立即 fsync 一次,其实不算压力太大。但如果你的归档目录在机械硬盘或者 NFS 上,高频 fsync 会比较拖后腿。设-F 30表示每 30 秒刷一次盘,接收的数据先放 OS page cache,定期落盘。缺点是 PG 是性能敏感型数据库,WAL 更是出了名“怕丢”,接受“最多丢失 30 秒 WAL”的风险一定要慎重。可选方案是:归档目录放 SSD,或者放高性能 NFS,保持默认每次都 fsync,这样最稳。我测试过在普通 SSD 上默认 fsync 完全能扛住中等写入量,没必要可怜这点 IO。

4.4 压缩参数实战经验

WAL 日志有多占空间?一个中等写入量的库,一天产生几十 GB WAL 很常见。直接存原文件,成本不小。pg_receivewal的-Z压缩正好缓解这个压力。比如:

pg_receivewal -h 192.168.1.100 -U wal_archive -D /wal_archive --slot=pg_receivewal_slot -Z 5

使用 zstd 压缩等级 5,接收到的每个 WAL 文件会带上.gz等对应后缀。压缩率我实测差不多能压到 30%-50%,CPU 开销不算大。需要注意:pg_receivewal的压缩是基于“段文件整体压缩”,不是流式逐条压缩,所以将来用pg_walsegment恢复工具时得先解压成原始文件名。PostgreSQL 自带的pg_waldump不能直接读压缩文件(除非官方文档说明支持),你得先gunzip。我自己写脚本时,在归档目录里额外存一份“解压还原”脚本,避免恢复时手忙脚乱。

5. 三大典型场景实操:持续归档、PITR 恢复和手动拉取

5.1 场景一:持续 WAL 归档,替代 archive_command

这是我用得最多的场景。以前archive_command方案有个痛点:它只在 WAL 段轮换时触发(默认 16MB 写满或archive_timeout到期),高峰时段没问题,低峰时段可能出现“归档延迟”或者归档进程卡住、堆积大量archive_command待归档文件。pg_receivewal方案从机制上避开了这个问题,它一直在收,不需要“等文件轮换到点再触发”,实时性更好。

具体部署方式:

  1. 在备份机或独立存储机上建好目录,给足磁盘空间。
  2. 写一个 systemd service 或者用 supervisor 托管pg_receivewal进程,设置自动重启。
  3. 配置好pg_hba.conf和复制槽,首次启动加--create-slot。
  4. 添加监控:检查进程是否存在、pg_stat_replication里对应槽的restart_lsn是否落后主库太多、归档目录文件是否持续增加。
  5. 定期做pg_basebackup作为基线备份,两者结合形成“基线 + 增量 WAL”的完整恢复链。

对archive_command还有一点碎碎念:即使你用pg_receivewal,旧的archive_command逻辑也可以保留,两条线并行。很多团队是“两条腿走路”——archive_command把 WAL 传到异地对象存储,pg_receivewal负责本地实时归档。这样对方不论哪个链路挂了,还有另一个兜底。缺点是维护成本翻倍,看团队取舍。

5.2 场景二:配合 pg_basebackup 做时间点恢复

这是pg_receivewal最有价值的实战场景。完整步骤我贴出来:

第一步,做基线备份:

pg_basebackup -h 192.168.1.100 -U backup_user -D /backup/base_$(date +%Y%m%d) -X stream -P

-X stream的意思是备份过程中产生的 WAL 通过流复制方式一并接收,不用archive_command去补。备份结束后,目录里可能有一个backup_label文件(也可能没有,取决于版本),记录了备份起点的 LSN。

第二步,保证基线备份完成之后,pg_receivewal一直在跑,把后续所有 WAL 都保存在/wal_archive。

第三步,模拟故障恢复:准备一台新机器,假设要用/backup/base_20240520这个基线恢复到某个时间点:

cp -r /backup/base_20240520 /var/lib/postgresql/16/data chown -R postgres:postgres /var/lib/postgresql/16/data # 清理掉默认的 postgresql.conf 里不需要的配置,或用新机模板

然后把pg_receivewal收到的 WAL 放到新机器的pg_wal目录,或者用restore_command来抓取:

在postgresql.conf里配置:

restore_command = 'cp /wal_archive/%f %p'

再创建recovery.signal(PostgreSQL 12+)或recovery.conf(旧版本),然后启动实例。PG 会先重放基线里的数据,再按照recovery_target_time或recovery_target_lsn把 WAL 一个个应用到指定位置。等到你说“就到这吧”的时候,把recovery.signal删掉,实例转为正常状态,恢复完成。

实操中踩过的一个大坑是 WAL 文件的命名和目录组织。pg_receivewal默认把所有0000000100000000000000xx文件平铺在一个目录里,文件多了后(上万上十万个)目录扫描会变慢,部分工具处理大量文件也可能卡。我后来改造了目录结构,自己写脚本把“段文件名前 8 位”作为子目录分组,比如00000001/、00000002/,配合恢复时restore_command拼接路径。这样便于管理,也避免单目录文件数过多。

5.3 场景三:手动拉取指定 WAL 段

不是所有场景都要让pg_receivewal24 小时跑着。有时我们只是临时需要补一段 WAL,比如损坏了一个归档文件、或者备库追日志差一点。这时可以像这样手动拉:

pg_receivewal -h 192.168.1.100 -U wal_archive -D /tmp/backfill_wal \ --slot=temp_slot --create-slot --endpos=0/16B2C28

这里--endpos指定一个 LSN,让它在拉取到该位置后自动退出。这种方式的便利之处在于不需要整库重新备份,也不需要算“要哪些文件”,只要给一个终点 LSN,它自动收完就停。唯一的条件是:主库上对应的 WAL 还没被清掉。如果你的主库wal_keep_size配了足够大,或者槽一直在,就没问题。

我还遇到过一种情况:客户端报“requested WAL segment has already been removed”。这时你得判断pg_receivewal需要的起始 LSN 太靠后(太老)了,主库已经没有那一段日志。要么重新做一份基线备份,要么从冷备份恢复。别硬刚,除非你能从其他归档源找回来。

6. 常见问题与排查技巧实录

6.1 连接与权限类问题

症状:连不上,日志报FATAL: no pg_hba.conf entry for replication connection from host ...原因:pg_hba.conf里没允许“replication”这种连接类型。注意这里的 database 字段要写replication,不是业务库名。 解决:按前面第 3.1 节的示例加行,然后 reload。

症状:报FATAL: must be superuser or replication role to use replication protocol原因:账号没有REPLICATION属性。 解决:执行ALTER ROLE wal_archive WITH REPLICATION;或建号时就带上。

还有一个很常见的坑:认证方式写的peer,而你用 TCP 连接(-h 127.0.0.1),peer认证只适用于本地 unix socket,TCP 连接必须用md5/scram-sha-256/trust等方式。我刚开始测试时就是被这个卡了半天,一直提示认证失败,后来把pg_hba.conf改成scram-sha-256就好了。

6.2 复制槽状态和磁盘增长问题

复制槽把 WAL 保住,本来是好事,但配套监控跟不上就会变成灾难。我处理过一次事故:一个pg_receivewal进程因为服务器重启没有自动拉起(systemd 没配好),槽的restart_lsn卡在老位置,主库持续产生新 WAL 又不敢清理,几天后主库磁盘爆满,数据库直接进入只读/拒绝写入状态。恢复过程很狼狈:先临时把槽删掉(SELECT pg_drop_replication_slot(...)),释放 WAL,给主库喘气,然后再修好pg_receivewal的守护进程,重新建槽从头收。

从此之后我给自己立了规矩:

  • 任何持续运行的pg_receivewal必须托管在 systemd/supervisor 下,配Restart=always。
  • 监控必须包含:进程存活、复制槽的restart_lsn与主库当前pg_current_wal_lsn()的差值、归档目录整体大小、主库pg_wal目录占用率。
  • 如果pg_receivewal长时间离线,宁可直接把旧槽删掉、重新建槽补一份基础备份,也别让主库 WAL 无限堆积。

6.3 文件不完整、校验和损坏问题

理论上pg_receivewal每次写完 WAL 文件都会做同步,断电后出现半个文件的可能性很小。但如果你用了-F调整 fsync 间隔,或者归档目录本身是弱一致性的网络盘,就可能出现“文件大小对但内容没刷完”的情况。此时用pg_waldump去读文件,大概率会报invalid WAL file header之类的错误。

我的排查方法是:

  1. 先看文件大小是不是正好 16MB(或你设置的段大小)的整数倍。不整,说明还没写完,多半是进程退出或磁盘满了。
  2. 用pg_waldump /wal_archive/000000010000000000000038试读,如果能读出记录,说明文件没坏。
  3. 如果确认文件坏了,别修,直接从源头重新拉这段 WAL。删掉坏文件,让主库重发;或者从另一份归档副本拷贝覆盖。

另外提醒一句:千万别手动往pg_receivewal正在写的目录里塞同名的 WAL 文件,它会认为已经是完整段然后跳过,覆盖逻辑处理得不好会导致文件错乱。我吃过这个亏,从此规范了“归档目录只进不出”的原则,任何手动干预都要先停服务。

6.4 与 archive_command 混用时需要注意的顺序问题

如果你同时开archive_command和pg_receivewal,可能在恢复时遇到“同一个 WAL 文件,从两个渠道找到,内容却不一致”的诡异问题。正常来说两边都是同一份数据,不会不一致。但有特殊情况:如果你改过 WAL 段大小,或者数据库做过pg_resetwal(PG13 以前叫pg_resetxlog),WAL 文件名和内容可能发生跳变。这种情况下,恢复时尽量以pg_receivewal接收的 WAL 为主,因为它是通过流复制协议收到的、保证与主库一致。archive_command拷贝的文件如果是从不完备的备份机上拿来的,也可能缺尾部记录。经验做法:恢复链路上只从一套归档源取 WAL,不要混着查。

7. 从几个维度看 pg_receivewal 的定位和选型

7.1 pg_receivewal 与 pg_basebackup 之外的其他方案对比

表格化对比能帮你快速决策。我整理了一个简易对照:

方案实时性是否需要额外组件恢复友好度适用场景
pg_receivewal高,持续流式接收无,PG 自带高,文件即 WAL,天然支持 PITR本地实时归档、增量备份、配合 PITR
archive_command低,段轮换才触发无中,文件完整但可能延迟远程归档、对象存储归档
备库(standby)高,持续重放无高,但需要防护备库被误改读写分离、HA 场景
第三方备份工具(如 barman、pgBackRest)高需部署高企业级、多实例、集中管理

pg_receivewal最大的优势是“轻、原生、可控”,你不必为此引入复杂的备份框架。缺点是它只管 WAL,不管基础备份——你需要自己调度pg_basebackup定期做基线,还要处理 WAL 的压缩、轮转、过期清理等“操心活”。而pgBackRest这类工具把这些都封装好了,甚至支持并行备份、增量备份、加密、对象存储,适合规模大、人力少的团队。但如果你只想要“能快速落地、不过度设计”的备份机制,pg_receivewal绝对是非常好的选择。

7.2 看重写保护和安全性:连接安全与权限最小化

我见过一些团队把pg_receivewal的账号配成了超级用户,这非常危险。pg_receivewal只需要REPLICATION权限,不需要读写业务表的权限。给它最小权限,即使这个账号被拖库,影响面也控制在“能被复制协议连上、收 WAL”这个范围内,不至于直接篡改数据。同理,pg_hba.conf里配replication连接时,尽量限制源 IP,别用0.0.0.0/0。

另外一点:pg_receivewal连接时的默认数据库名不是业务库,是replication。很多人会在pg_hba.conf里写host replication postgres 10.0.0.0/8 md5,然后业务上还是习惯用-d postgres去连,结果发现连不上,其实是因为数据库名匹配不上。这属于典型的小白踩坑,我在这里再强调一遍。

7.3 日常监控:怎么判断 pg_receivewal 是否健康

监控项我总结成清单,任何一个触发都要重视:

  • 进程是否存在:pgrep -fa pg_receivewal,systemd 的话systemctl status pg-receivewal。
  • 连接是否存活:在主库执行:
SELECT pid, application_name, client_addr, state, sent_lsn, replay_lsn FROM pg_stat_replication;

state为streaming表示正常。如果你的pg_receivewal设置了应用名(可以通过application_name参数配置),可以精确区分是哪个实例。

  • 延迟是否过大:对比pg_current_wal_lsn()和该连接的sent_lsn,差值超过了某个阈值(比如 256MB),需要关注网络或目标盘写入速度。
  • 归档目录增长:定期记录目录大小,如果长时间不增长,可能是 WAL 源头低峰,但也可能连接已经断了。
  • 复制槽restart_lsn:查看落后量,防止主库 WAL 堆积。

我习惯写一个简单的 shell 脚本,每分钟把关键指标打点,或者用 Prometheus 的pg_stat_replicationexporter 抓取。对于只有一两个实例的中小型团队,用 shell + cron 就够用,没必要一上来就上重型监控。

8. 工具细节与进阶使用技巧

8.1 解决“程序自动退出后 WAL 断点续传”的问题

pg_receivewal一旦中断重启,它怎么知道从哪里继续接收?答案是:看-D目录里已有的 WAL 文件名。启动时它会扫描目录,找到最大的 WAL 段文件名,然后从那个段文件的末尾开始请求。如果你首次启动时目录是空的,而主库早已运行很久,没有复制槽的话它只能从当前 WAL 位置开始接收,之前的历史 WAL 不会补给你——那不是它的职责,历史 WAL 需要靠archive_command或手动拉取。这里建议首次启动前先做一次pg_basebackup基线备份,这样“起点”就对齐了。

还有一个大家容易忽略的细节:pg_receivewal启动时如果目录里已经有文件,但里面恰好有一个“残缺”文件(比如上次进程异常退出最后一段没写完),它可能会从该文件中间开始写,或者在恢复重连时认为该文件不完整而重新请求。我在实践中发现:不要自己改这些残缺文件,直接删掉最后的半截文件再启动更干净。因为 WAL 是连续编号的,删掉最后一段残缺文件,它重新拉到的内容跟主库一致,之前的完整文件不受影响。

8.2 配合 systemd 做成一个标准服务

如果你已经决定让它常驻,强烈建议写一个 systemd unit,而不是用 nohup 挂着。一个参考配置:

[Unit] Description=PostgreSQL WAL Receiver After=network.target [Service] User=postgres Group=postgres ExecStart=/usr/lib/postgresql/16/bin/pg_receivewal -h 192.168.1.100 -U wal_archive -D /wal_archive --slot=pg_receivewal_slot Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

把Restart=always加上,进程意外退出 5 秒后自动拉起,基本能做到无需人工干预。注意 systemd 默认可能不给服务进程设置 PATH,所以ExecStart里最好写绝对路径。

8.3 清理和轮转:防止归档目录无限膨胀

只要有持续写入,pg_receivewal的目录就会无限膨胀。我的策略是“按时间保留 + 按量保留”双保险:

  • 每天凌晨把早于 N 天且已被下次基线备份覆盖的 WAL 清理掉。比如你每周日做基线,那么上一周基线之前的 WAL 都可以删。
  • 脚本定期统计目录大小,超过阈值(如 500GB)触发告警或自动清理最老文件,但清理前必须确认不需要 PITR 到那么早了。
  • 用pg_archivecleanup这样的工具也可以清理旧的 WAL 文件,它会读取目录下的backup_history文件判断哪些可以删。

清理脚本我写得非常保守:删之前双重判断,只删“文件是合法 WAL 段名”的文件,绝不删其他内容,防止把别的文件误删。

8.4 WAL 段大小与内存映射的坑

pg_receivewal的--wal-segsize默认值在主库也是默认 16MB 时没问题。但如果主库在初始化时用了--wal-segsize=64(PostgreSQL 11 起支持 1、2、4、8、16、32、64MB),你客户端指定--wal-segsize=16去连,它可能会以错误的段大小来切分文件,导致文件数量和命名对不上。我在测试环境就被坑过一次:主库段大小 64MB,我忘了传参数,pg_receivewal收出来的文件全是 16MB 一段,结果恢复时restore_command怎么都找不到匹配的段文件。

解决办法很简单:连接时显式加上--wal-segsize=64或者干脆不传(它会从服务器返回的IDENTIFY_SYSTEM响应里读取?不同版本行为略不同,保险起见还是显式传)。最佳实践是:在你的运维文档里写清楚主库 WAL 段大小,并同步在pg_receivewal的 systemd unit 参数里写明,避免换人交接时踩坑。

9. 结尾:一个运维老兵的使用体会

写到最后,分享点儿我个人的真实体会。pg_receivewal是个小工具,但它映射出的是一套“先备份逻辑,后工具选型”的思路。很多数据库事故不是工具不够强,而是我们没把链路的每一环想清楚。比如复制槽的设计,一方面保护了 WAL 不丢,另一方面又可能因为无人值守把主库拖垮——工具给你一张安全网,你也得给自己上一道保险。我的经验是,任何自动化备份方案,最终都要落到“监控”两个字上:进程挂了要报警,槽落后要报警,磁盘满了要报警,人可以不常在,但监控必须常在。

如果你准备在生产环境上pg_receivewal,我建议你按这个顺序走一遍:先在测试环境配通权限和网络,跑一天看文件增长是否符合预期;再模拟一次断网重连,验证断点续传;然后做一次完整的pg_basebackup+ WAL 恢复演练,确认自己能在一个干净的系统上把业务捞回来;最后再接入监控和告警。这套流程走完,你心里对pg_receivewal的信心会完全不同。

最后送一个小技巧:把主库的pg_stat_replication和pg_replication_slots两个视图查出来的结果,作为你日常巡检的“体检表”。pg_receivewal是否健康、是否能跟上主库进度、槽有没有堆积风险,这几项指标一目了然。学会了这个工具,你就等于给 PostgreSQL 上了一道非常关键的保险——而这恰恰是很多“跑得挺欢”的库所缺失的一块。希望这篇能帮你在实际工作中少踩几个坑。

返回列表