尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

pg_receivewal实战:PostgreSQL持续WAL归档与PITR恢复详解

pg_receivewal实战:PostgreSQL持续WAL归档与PITR恢复详解 PostgreSQL 的 WAL 归档很多同学一上手就是配archive_command出了问题再去折腾脚本、权限和目录。但archive_command有一个天然的粒度问题它只有在 16MB 的 WAL 段切换之后才触发而且是在数据库服务器本地执行。pg_receivewal给出了另一条路——它像一个没什么副作用的备库连接用流复制协议从主库持续接收 WAL并直接写到本地目录。我最早是被一个“归档命令失败导致主库 WAL 堆积”的问题逼着切到pg_receivewal的后来发现它才是做持续归档、PITR、甚至异地增量备份时最顺手的工具。这篇文章会把这个工具的定位、部署、参数、恢复演练和常见坑完整过一遍。PG 10 之前它叫pg_receivexlog10 之后改名pg_receivewal15 以后压缩参数还支持了多种算法17 里依然是主力功能。适合正在读 PostgreSQL 文档但不知道从哪下手的 DBA也适合已经配过archive_command想进一步降低备份窗口的同学。1. pg_receivewal是什么连续归档不是简单替代archive_command1.1 一个客户端而不是服务器端钩子archive_command的本质是主库在 WAL 段切换后由后端进程调用一个外部命令把整个 16MB 文件搬到某个地方。它依赖服务器本地的 shell、外部存储和命令执行权限一旦命令写得不严谨或者存储抖动主库就会反复重试pg_wal目录容易憋出好几个 G。pg_receivewal则是客户端工具它用流复制协议连上主库后主库会启动一个 walsender 进程不断把 WAL 数据推过来。你要做的只是指定一个目录-D它会把收到的 WAL 写成文件。换句话说它不需要在主库配置文件里写任何“归档命令”也不等 16MB 段切换完全结束才开始动作而是边收边写实时性比archive_command高一个量级。我第一次用的时候有个误解以为这是pg_basebackup的替代品。其实不是。pg_basebackup负责做全量基础备份pg_receivewal负责持续接收增量 WAL两者合起来才能形成完整的“基础备份 归档日志”体系。你可以把它理解成一个“专职搬运工”数据库只需要开着 walsender 让它拿数据就行。1.2 什么时候应该选pg_receivewal下面这张表可以比较直观地看出两者差异对比项archive_commandpg_receivewal触发时机WAL 段切换后流复制协议持续传输运行位置数据库服务器本地任意能连到主库的客户端是否需要配置主库归档命令需要 archive_mode 和 archive_command不需要但需要 wal_level 和 walsender实时粒度16MB 整段可以拿到当前未写完的段断连风险归档命令失败会堆积 pg_wal无复制槽时可能被主库回收 WAL典型场景传统备份脚本、兼容旧体系低延迟持续归档、异地增量、PITR实际项目里我见过有人为了用archive_command把 WAL 传到异地在 shell 脚本里写了一堆scp、重试、锁文件逻辑最后 SSH 抖动一次就全乱了。换成pg_receivewal之后传输协议本身有状态、有校验、有重连机制运维面小很多。它不是“更高级的 archive_command”而是“更接近备库同步机制的一种归档方式”。2. 从零配置连续WAL归档权限、复制槽和第一条命令2.1 wal_level、max_wal_senders和权限设置要使用pg_receivewal主库不需要开archive_mode但必须保证基础配置满足流复制要求wal_level replica max_wal_senders 8 max_replication_slots 8wal_level默认在 PG 13 之后的版本里已经足够但如果你是从老版本升级上来的库最好确认一下。max_wal_senders和max_replication_slots属于需要重启实例的参数修改后记得重启。接着创建一个专门用于复制的账号CREATE ROLE repuser LOGIN REPLICATION PASSWORD strong_password;REPLICATION权限非常关键没有它客户端只能普通连接不能启动 walsender。注意这个权限不等于超级用户日常归档就给它最小权限。pg_hba.conf里也需要放行 replication 连接host replication repuser 192.168.1.0/24 scram-sha-256这里容易踩坑很多人把数据库字段写成all感觉能连上但复制连接走的是独立的 replication 通道必须显式写成replication这一行。改完 reload 即可SELECT pg_reload_conf();2.2 创建物理复制槽没有槽就谈不上“保护”只运行pg_receivewal不创建复制槽断线超过一定时间主库的 WAL 文件可能已经被回收恢复时就会断档。复制槽的作用是让主库保留一个restart_lsn只有接收方确认拿到了某个位置这个位置之前的 WAL 才会被清理。手动创建物理复制槽SELECT * FROM pg_create_physical_replication_slot(wal_slot);也可以用工具自己建pg_receivewal -h primary_host -U repuser -D /pg_archive/wal_archive \ --slotwal_slot --create-slot物理复制槽不是自动删除的。如果 pg_receivewal 长期不运行主库会一直保留 WAL直到磁盘被撑爆。所以复制槽既是对数据的保护也是运维的监控点后面会专门讲怎么盯它。2.3 第一条pg_receivewal命令与文件命名启动命令大概是这样的mkdir -p /pg_archive/wal_archive chown postgres:postgres /pg_archive/wal_archive pg_receivewal -h primary_host -p 5432 -U repuser \ -D /pg_archive/wal_archive \ --slotwal_slot \ -v -P \ --fsync-interval5先解释几个参数-DWAL 落盘目录可以只指向一个空目录工具会在里面维护文件。--slot指定使用哪个物理复制槽。-v verbose 模式看连接日志很有用。-P显示接收进度方便第一次验证是否真的在传数据。--fsync-interval5每隔 5 秒做一次 fsync。默认是 10 秒追求更安全可以调小。正常启动后目录里会出现类似000000010000000000000001.partial的文件。.partial代表这只是当前正在接收的、还没写完的 WAL 段。主库每切换一个 16MB 段pg_receivewal 就会把上一个.partial改成正式文件名比如000000010000000000000001。2.4 用pg_basebackup搭配出一个可恢复的完整基线单纯有 WAL 归档不能恢复你还需要一个全量基础备份。最稳的启动顺序是先启动 pg_receivewal再做 pg_basebackup。因为 pg_receivewal 启动得早主库从那个时候开始的所有 WAL 都会被捕捉到基础备份的起始 LSN 一定落在它已经接收的范围之内。基础备份命令pg_basebackup -h primary_host -U repuser \ -D /backup/base_20250101 \ -Ft -z -P -X fetch-Ft表示输出 tar 格式-z是压缩-X fetch表示备份过程中产生的 WAL 也一并拿到基础备份里。这样即使某个瞬间 pg_receivewal 正好断线基础备份本身也足够让它恢复到一致点后面再由归档目录里的 WAL 继续追。所以完整的链条是设置流复制参数和账号。创建物理复制槽。启动 pg_receivewal 并确认.partial文件在增长。执行 pg_basebackup。后续所有 WAL 都持续进入归档目录。从这个角度看pg_receivewal 不是替代全量备份而是让全量备份之后的每一秒都有据可查。3. 关键参数拆解压缩、落盘同步和状态观测3.1 -Z压缩本地放得下CPU扛得住WAL 增长量在某些业务里非常可观一个 16MB 的段切得很快磁盘消耗自然就大。pg_receivewal提供了压缩参数-Zpg_receivewal -h primary_host -U repuser -D /pg_archive/wal_archive \ --slotwal_slot -Z gzip:9老版本里-Z后面直接跟 0 到 9表示 gzip 压缩等级。新版本支持更精确的gzip:9、lz4、zstd等形式。使用压缩后落盘文件会带上.gz或对应的压缩后缀恢复的时候restore_command也要跟着改。压不压缩取决于你的瓶颈。如果归档目录是普通磁盘且空间充裕不压也可以如果走网络传输或者目录较小压缩很划算。但压缩会消耗 CPU主库负载很高时要先做压测不要一上来就gzip:9。我自己的习惯是开gzip:1或者lz4压缩比和 CPU 消耗比较平衡。有一点需要提前知道压缩是在 pg_receivewal 收到 WAL 之后做的不是主库帮你压好再发过来。所以压缩消耗的是这台接收机自己的 CPU。3.2 fsync-interval与--synchronous到底多实时才算数WAL 从主库传过来先进入接收端的操作系统缓存不一定立刻落盘。--fsync-interval控制的是每隔多少秒强制刷盘。默认 10 秒意味着极端情况下接收端进程 crash可能丢失最后近 10 秒已经收到但还没落盘的 WAL。对普通归档来说可以接受但如果你把它当成“异地容灾”的一部分建议调成 5 秒甚至 1 秒。--synchronous则是更严格的模式。开启后pg_receivewal 收到 WAL 会尽快落盘并在确认消息中体现“已经刷盘”的状态。这个参数一般配合主库的同步复制配置使用目的是把 pg_receivewal 当成一个同步接收端主库提交事务时至少要等远端这个进程把 WAL 写稳。我并不是建议所有人都开--synchronous。它的代价是主库每次提交都可能被远端的磁盘 fsync 拖慢跨机房场景尤其明显。普通归档任务用默认或者--fsync-interval5足够了。3.3 从主库视角看slot和接收进度配好了 pg_receivewal一定要学会在主库上看它到底干没干活SELECT slot_name, slot_type, active, restart_lsn, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes FROM pg_replication_slots WHERE slot_name wal_slot;active为真说明有客户端正连着这个槽。restart_lsn会随着 WAL 被接收而向前推进lag_bytes代表主库当前 WAL 位置和槽保留位置之间的差距。正常情况下这个差值不会太大因为 pg_receivewal 是实时接收的。如果active为假但restart_lsn还很旧说明进程掉了主库还在为它保留大量 WAL。这种状态必须告警否则主库磁盘迟早被撑满。也可以用系统视图看 walsender 的信息SELECT application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication WHERE application_name LIKE wal%;在实际环境里pg_receivewal 的application_name通常显示为walreceiver或者命令本身的标识具体以 PostgreSQL 版本为准。重点是看write_lsn和flush_lsn是不是在增长增长说明接收正常不动说明进程可能卡住了。4. 恢复演练与排错.partial、restore_command和常见坑4.1 把归档目录变成恢复目录当你要恢复一台新实例时逻辑其实很直接先把基础备份放回数据目录再让 PostgreSQL 通过restore_command从归档目录拿 WAL。没有压缩的情况下postgresql.conf里这样配restore_command cp /pg_archive/wal_archive/%f %p这个配置的含义是数据库需要 WAL 段%f时就去归档目录里找同名文件放到%p指定的位置。配合 PG 12 的standby.signal文件就可以进入持续恢复状态touch /var/lib/postgresql/16/main/standby.signal pg_ctl start -D /var/lib/postgresql/16/main如果你用了-Z gzip压缩归档目录里是.gz文件cp就不行了要改成restore_command gunzip -c /pg_archive/wal_archive/%f.gz %p4.2 时间点恢复测试模板能不能恢复只有演练过才知道。时间点恢复是我每季度必做一次的测试。先在基础备份的postgresql.conf里设置恢复目标recovery_target_time 2025-01-01 00:30:0008 recovery_target_inclusive true启动实例查看日志里是否出现了LOG: recovery stopping at 2025-01-01 00:30:0008如果日志显示找不到 WAL那基本就是归档目录路径或者restore_command写错了。用pg_waldump也可以检查某个 WAL 段里的记录范围和recovery_target_lsn对一下能定位是段缺失还是目标 LSN 设置不合理。4.3 从startup日志倒着排查一条故障链路举一个我处理过的典型问题pg_receivewal 进程一直显示在跑但是恢复时发现归档目录缺失中段 WAL。先看 pg_receivewal 日志里面会出现FATAL: no pg_hba.conf entry for replication connection from host 192.168.1.50, user repuser很多人会去检查pg_hba.conf的普通连接配置但问题往往出在数据库字段没写成replication。正确的行是host replication repuser 192.168.1.50/32 scram-sha-256再一种情况是 pg_receivewal 显示连接了但目录里只有一个文件不再增长。这时去主库查SELECT slot_name, active, restart_lsn, pg_current_wal_lsn() FROM pg_replication_slots WHERE slot_name wal_slot;如果active true但restart_lsn长时间不变看看 pg_receivewal 是不是卡在权限提示上等密码输入。用 systemd 跑服务时很容易遇到终端里能交互输入密码放到服务里就卡住了。解决办法是给 repuser 配~/.pgpass或者在连接串里带上密码。4.4 断线重连、槽位和目录冲突的处理pg_receivewal 默认会尝试重连-n参数则是“遇到连接失败直接退出”。作为长期服务我通常不加-n让 systemd 配合Restartalways来拉起。如果加了--create-slot第二次启动时因为槽已经存在会报错所以第一次建槽成功后后续启动去掉--create-slot就好。目录冲突也是一个隐蔽问题。如果-D目录里已经存在旧时间线或者不连续的 WALpg_receivewal 可能拒绝写入或者产生unexpected timeline之类的报错。遇到这种情况不建议贸然清空目录先把旧目录改名归档再开一个新目录重新接收。因为旧 WAL 里可能还有恢复时需要的段删之前一定要确认基础备份和恢复点用不到它。还有一点要记住.partial是正在写的段不是坏文件。恢复时如果目标时间点非常接近当前你可能需要主库执行SELECT pg_switch_wal();让当前段完整切换出去pg_receivewal 才会把.partial变成正式文件。否则想恢复到“最后一秒”是接不上的。5. 实测心得我的部署策略和监控清单5.1 一个值得抄的systemd unit生产环境里我不会把 pg_receivewal 挂在 nohup 下而是交给 systemd 管理。一个典型的 unit 文件长这样[Unit] DescriptionPostgreSQL WAL Receiver Afternetwork-online.target Wantsnetwork-online.target [Service] Userpostgres Grouppostgres ExecStart/usr/lib/postgresql/15/bin/pg_receivewal \ -h primary_host \ -U repuser \ -D /pg_archive/wal_archive \ --slotwal_slot \ --fsync-interval5 \ -v Restartalways RestartSec5 [Install] WantedBymulti-user.target这里没有--create-slot因为槽已经提前手动建好了。如果第一次部署想省事可以单独执行一次带--create-slot的命令再启动 systemd 服务。.pgpass也要配好我通常会写到 postgres 用户的家目录下并设权限为 600。5.2 每天应该看的几个指标pg_receivewal 一旦稳定运行平时好像没什么存在感但出事就是大事。我会用监控工具盯这几个点pg_replication_slots.active是否为真restart_lsn是否在推进。归档目录里最新 WAL 文件的修改时间超过 10 分钟没变化就要告警。主库pg_wal目录大小出现异常增长先查是不是 pg_receivewal 断了。归档目录磁盘使用率保留策略没做好时很容易被 WAL 塞满。推荐写一个简单脚本每分钟抓一次ls -lt /pg_archive/wal_archive/*.partial | head -1如果这个.partial文件的修改时间一直很旧说明接收进程虽然连着但数据没在流动比直接看进程是否存在更准确。5.3 关于“它是不是备份”的最终结论很多项目组把 pg_receivewal 当成“实时备份”这个说法不够严谨。它只是把 WAL 持续搬到了另一个目录不等于一份可用于快速整实例恢复的完整备份。如果没有定期pg_basebackup单靠 pg_receivewal 只能恢复到一个基础备份点之后的连续状态而那个基础备份点本身要单独维护。所以我的最终策略都是双轨制每周或者每天做一次pg_basebackup作为全量基线pg_receivewal 持续归档两个星期以上的 WAL。恢复时先拿最近的全量备份再连续应用归档日志。这套组合从 PG 10 到 PG 17 都能跑既满足了数据安全又不会让恢复链路复杂到失控。在我的实际运维经验里pg_receivewal 最让人省心的点不是它功能多而是它把“持续归档”这件事变成了一个可监控、可重连、有状态的标准进程。你只需要给它一个复制槽、一个目录和一套告警剩下的就是定期做恢复演练。如果你想把手动归档改成更可靠的方案从部署这个工具开始是投入产出比最高的选择。
返回列表