WAL 归档配置(archive_mode / archive_command)
要让 PITR 真正可用,第一步是把 WAL 归档链路建起来。归档链路由三个参数决定:wal_level 决定「WAL 里记录了多少信息」,archive_mode 决定「是否启用归档」,archive_command(或 archive_library)决定「归档到哪里、怎么做」。三者里任何一个配错,归档都不会真正生效。
这一页的重点是把归档配置讲透:每个参数的合法取值、生效方式(是否需要重启)、占位符的含义、幂等写法、失败排查、以及归档落后时 pg_wal 为什么会膨胀。配置本身只有几行,但配置背后的语义决定了故障时的行为,因此值得逐条展开。
需要特别提醒的一点:归档命令的正确性无法通过「有没有报错」来判断。PostgreSQL 只依据归档命令的退出码判定成败,命令即使写错但恰好返回 0,也会被当作归档成功,WAL 段会被回收,历史就此丢失。这类问题往往在真正需要恢复时才暴露,所以本页会反复强调「验证」这件事。
前置知识
建议先阅读:PITR 原理
基础概念
| 参数 | 作用 | 生效方式 | 建议值 |
|---|---|---|---|
wal_level | 决定 WAL 记录的信息量 | 需要重启 | replica |
archive_mode | 是否开启归档 | 需要重启 | on |
archive_command | 归档时执行的 shell 命令 | reload 生效 | 幂等的复制命令 |
archive_library | 归档时加载的共享库(PG15+) | reload 生效 | 通常留空 |
archive_timeout | 强制切换 WAL 段的超时 | reload 生效 | 300(5 分钟) |
wal_keep_size | pg_wal 中至少保留的 WAL 量 | reload 生效 | 视复制场景而定 |
wal_level
wal_level 决定 WAL 中写入的信息级别,取值有三个:
| 取值 | 记录内容 | 是否支持归档 / 复制 |
|---|---|---|
minimal | 仅记录崩溃恢复所需的最少信息 | 不支持 PITR,不支持流复制 |
replica | 记录足够归档与物理复制的信息 | 支持 |
logical | 在 replica 基础上增加逻辑解码所需信息 | 支持,并可做逻辑复制 |
wal_level 是必须重启才能修改的参数。如果当前是 minimal,那么:
- 归档与流复制都无法工作;
- 某些操作完全不写 WAL,例如
CREATE TABLE ... AS、CREATE INDEX(非并发)、CLUSTER、TRUNCATE、REFRESH MATERIALIZED VIEW(不带CONCURRENTLY)等; - 因此,从
minimal切到replica后,此前做的物理备份与 WAL 在混用时可能不一致,正确做法是切换后重新做一次基础备份。
还有一个容易踩的坑:minimal 模式下,如果 max_wal_senders 大于 0,服务无法启动。这是为了避免「本以为归档/复制可用,实际 WAL 信息不足」的隐性风险。
archive_mode
archive_mode 有三个取值:
| 取值 | 行为 | 适用场景 |
|---|---|---|
off | 不归档 | 开发环境或明确不需要 PITR 的场景 |
on | 归档,且备库升主后继续归档 | 常规生产环境 |
always | 归档主库与备库的 WAL | 归档链式串联(archive cascading)等特殊场景 |
on 与 always 的区别在备库上才体现:
on:备库自己不归档(因为主库已经在归档了,备库没必要重复归档);但如果备库被提升为主库,归档会立刻开始工作。always:备库也归档自己重放过的 WAL。这在「archive cascading」(备库把 WAL 归档给下游备库)或「备库承担唯一归档节点」的架构下才需要。
archive_mode 同样是重启生效的参数。这意味着你不能临时「打开归档」来救急,归档链路必须在事前就建设好。
archive_command
archive_command 是一个 shell 命令字符串,PostgreSQL 在需要归档某个 WAL 段时调用它。命令里可以使用三个占位符:
| 占位符 | 含义 |
|---|---|
%p | 要归档的 WAL 段路径(相对于数据目录,如 pg_wal/000...B5) |
%f | 该 WAL 段的文件名(不含路径) |
%% | 一个字面量百分号 |
判定规则非常简单:退出码为 0 = 成功,非 0 = 失败。失败时 PostgreSQL 会在稍后重试同一个段,并且在重试成功之前该段不会被回收。官方特别指出,这里必须是「当且仅当成功时返回 0」。
%p 展开后是相对于数据目录的路径(例如 pg_wal/0000000100000000000000B5),因为归档命令的工作目录就是数据目录。%f 则是纯文件名。
还有一个容易忽略的边界:如果归档命令被信号终止(服务关闭时的 SIGTERM 除外),或 shell 返回大于 125 的退出码(例如命令不存在),archiver 进程会中止并被 postmaster 重启;这种情况下失败不会记入 pg_stat_archiver。也就是说,「视图里看不到失败,但归档确实没进展」是可能发生的。
archive_timeout
如果数据库写入量很小,一个 WAL 段可能要很久才写满,那么归档的时延就会很大——灾难发生时,最近这段未被归档的 WAL 就面临丢失风险。archive_timeout 用来给这个时延设一个上界:
- 只要经过了
archive_timeout秒,即使当前 WAL 段没写满,也强制切换并归档。 - 单位是秒,默认
0(表示不强制切换)。 - 官方建议的值在一分钟左右比较合理;设置过小(例如几秒)会导致大量「只写了一点就被截断」的半满 WAL 段,浪费存储与归档带宽。
archive_library
从 PostgreSQL 15 开始,新增了 archive_library 参数,允许使用动态加载的共享库来实现归档,替代 archive_command 的外部进程调用。它与 archive_command 互斥:
| 配置状态 | 行为 |
|---|---|
| 两者都为空 | 不归档(即使 archive_mode = on) |
只设 archive_command | 用外部命令归档 |
只设 archive_library | 用共享库归档 |
| 两者都设置 | 服务端报错,无法启动 |
archive_library 的优势在于免去每次归档 fork 一个进程的开销;代价是需要编译、部署并维护一个共享库,且库本身的行为同样要满足「幂等、返回 0/1 表示成败」的契约。
pg_stat_archiver
归档状态由系统视图 pg_stat_archiver 暴露,它是归档监控的入口:
| 字段 | 类型 | 含义 |
|---|---|---|
archived_count | bigint | 成功归档的 WAL 段总数 |
last_archived_wal | text | 最近成功归档的 WAL 段文件名 |
last_archived_time | timestamptz | 最近一次归档成功的时间 |
failed_count | bigint | 归档失败的总次数 |
last_failed_wal | text | 最近一次失败的 WAL 段文件名 |
last_failed_time | timestamptz | 最近一次失败的时间 |
stats_reset | timestamptz | 统计计数器上次重置的时间 |
注意 failed_count 是累计的,因此它不为 0 并不一定意味着当前有问题——可能是历史上一闪而过的网络抖动。真正需要关注的是:
failed_count是否在持续增长;last_failed_wal是否与last_archived_wal相同(说明同一个段反复失败);last_archived_time是否明显落后于当前时间。
语法与用法
最小可用配置
# postgresql.conf
# WAL 记录级别:归档与复制的最低要求(需重启)
wal_level = replica
# 开启归档(需重启)
archive_mode = on
# 归档命令(reload 生效即可)
archive_command = 'test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f'
# 低写入量场景下给归档时延设上界(reload 生效)
archive_timeout = 300修改后的生效检查
-- 查看参数当前值(注意 source 表示它来自哪个配置文件)
SELECT name, setting, pending_restart, source
FROM pg_settings
WHERE name IN ('wal_level', 'archive_mode', 'archive_command', 'archive_library', 'archive_timeout'); name | setting | pending_restart | source
-----------------+------------------------------------+-----------------+--------------------
archive_command | test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f | f | configuration file
archive_library | | f | default
archive_mode | on | f | configuration file
archive_timeout | 300 | f | configuration file
wal_level | replica | f | configuration file
(5 rows)pending_restart = t 表示该项改动需要重启才能生效。修改 wal_level 或 archive_mode 后如果看到它,说明还没有真正生效。
归档相关的操作函数
除了被动等待 WAL 段写满,也可以主动触发归档:
| 函数 | 作用 |
|---|---|
pg_switch_wal() | 强制切换到新的 WAL 段,使当前段可被归档 |
pg_current_wal_lsn() | 当前写入的 WAL 位置 |
pg_walfile_name(lsn) | 指定 LSN 所属的 WAL 段文件名 |
pg_walfile_name_offset(lsn) | 段文件名与段内偏移 |
pg_wal_lsn_diff(lsn1, lsn2) | 两个 LSN 相差的字节数 |
-- 主动切段并立即触发归档(例如在做基础备份前后)
SELECT pg_switch_wal(); pg_switch_wal
---------------
0/1A9B1C8
(1 row)重置归档统计
SELECT pg_stat_reset_shared('archiver'); pg_stat_reset_shared
----------------------
(1 row)重置之后,archived_count 与 failed_count 都归零,stats_reset 更新为当前时间。重置前应先记录当前值,便于对比。重置本身不影响归档功能,只是清零计数器。
详细说明
归档进程的工作方式
归档是由后台的 archiver 进程完成的,它不与用户事务耦合,而是按自己的节奏工作。理解它的工作循环有助于判断「为什么会慢」「为什么失败会堆积」:
循环:
1. 找出「已写满 / 已被切换」且尚未归档的最早 WAL 段
2. 执行 archive_command(或调用 archive_library)
3. 若退出码为 0:
- 记录到 pg_stat_archiver(archived_count +1)
- 该段变为「可回收」状态
否则:
- 记录失败(failed_count +1)
- 保留该段,稍后重试
4. 短暂等待(或收到唤醒信号)后回到步骤 1几个重要推论:
- 归档是串行的。前一个段归档失败会阻塞后面的段(因为归档必须按顺序进行),因此一个段卡的越久,堆积的越多。
- 归档不影响事务提交。归档慢不会让写入变慢,它只影响「可回收的 WAL 段数量」与「PITR 可恢复的时间上限」。
- 归档不会自动放弃。失败就一直重试,直到成功,代价是
pg_wal里的段越积越多。
归档命令的幂等性
归档命令被重复调用是正常现象,而不是异常。可能的重复来源:
- 命令实际成功,但返回码因包装脚本问题变成了非 0,服务端重试;
- 服务端在判定成功之前崩溃重启,重启后重新归档同一段;
- 归档目标在写入后被回滚(例如网络存储的短暂回退)。
因此归档命令必须幂等。官方给出的范式:
test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f语义拆解:
| 部分 | 作用 |
|---|---|
test ! -f /mnt/wal_archive/%f | 判断目标文件是否不存在 |
&& | 只有前一步成功(即文件不存在)才执行下一步 |
cp %p /mnt/wal_archive/%f | 执行复制 |
需要理解这个范式的语义边界:当目标文件已存在时,test ! -f 返回非 0,整个命令也返回非 0。按照「非 0 即失败」的规则,PostgreSQL 会认为归档失败并稍后重试。
官方对此的说明是:归档命令或库遇到已存在的目标文件时,如果该文件内容与待归档的 WAL 一致、且已完全落盘,应当返回 0;如果内容不一致,则必须返回非 0(避免覆盖正确的归档)。上面的 test ! -f && cp 写法在「文件已存在且内容相同」时返回非 0,严格来说并不符合这个要求,只是它足够简单、且触发场景罕见(仅在崩溃重启后重归档时出现),因此被官方作为示例给出。
一个语义更严谨的写法是:
if [ -f /mnt/wal_archive/%f ]; then
exit 0
fi
cp %p /mnt/wal_archive/%f.tmp && mv /mnt/wal_archive/%f.tmp /mnt/wal_archive/%f这样:目标已存在 → 直接返回 0;目标不存在 → 先复制到 .tmp,再原子改名,返回 0。原子改名避免了「复制中断留下半截文件、却已被判定为完成」的隐患。
对比一下不推荐的直接覆盖写法:
cp %p /mnt/wal_archive/%f它有两个问题:一是复制中断会留下半截文件;二是它会覆盖已存在的归档,一旦管理员误把两个实例指向同一个归档目录,就可能用后一个实例的 WAL 覆盖前一个实例的档案,破坏归档完整性。官方明确建议归档命令/库应拒绝覆盖已存在的归档文件。
归档到远端的方式
archive_command 的价值在于它是一个任意的 shell 命令,因此归档目标可以是本地目录、NFS、对象存储、远端主机等。常见做法:
| 目标 | 命令示例 | 说明 |
|---|---|---|
| 本地目录 | cp %p /mnt/wal_archive/%f | 简单,但不抗磁盘故障 |
| NFS / 网络存储 | cp %p /mnt/wal_archive/%f | 依赖挂载点稳定性,需处理挂起 |
rsync 到远端 | rsync -a %p archive@host:/wal/%f | 可做压缩与断点续传 |
scp 到远端 | scp %p archive@host:/wal/%f | 简单直连,缺少续传 |
| 对象存储 CLI | aws s3 cp %p s3://bucket/wal/%f | 云上常见,需关注退出码 |
用 rsync 或 scp 时要注意:这类命令在目标不可达时可能长时间挂起,而不是快速失败。挂起会导致归档停滞、pg_wal 膨胀。因此通常需要配合超时机制,例如:
timeout 60 rsync -a %p archive@host:/wal/%ftimeout 命令在超时后返回非 0,PostgreSQL 会记录失败并稍后重试,这比无限挂起安全得多。
归档与 pg_wal 膨胀
这是归档失败最真实、也最容易被忽视的后果。
WAL 段的回收需要满足若干条件,其中一条是「已成功归档」。如果归档失败,段就无法回收,pg_wal 就会持续增长。增长是缓慢而持续的,因此不会立刻触发任何告警——直到磁盘用尽。
归档正常: 归档失败:
pg_wal 大小 ──── 波动但稳定 pg_wal 大小 ──── 单调上升
│ │
└── 段回收与新增平衡 └── 只增不减,直到磁盘满如果 pg_wal 写满,PostgreSQL 会发生 PANIC 级别错误并关闭。这是生产事故等级的事件,因为:
- 数据库不可用;
- 恢复需要先腾出空间,而腾出空间又需要归档成功或放弃某些 WAL;
- 处置过程本身有数据丢失风险。
因此监控 pg_wal 目录大小是归档运维的必备项。
归档失败的五类原因
归档失败的原因大体可以归为五类,排查时按这个顺序过一遍,命中率很高。
| 类别 | 典型表现 | 排查手段 |
|---|---|---|
| 磁盘空间不足 | 归档目录或 pg_wal 所在分区满 | df -h |
| 权限问题 | 归档目录属主/权限不对,写不进去 | ls -ld、检查运行用户 |
| 挂载点异常 | NFS/远端挂载掉线或只读 | mount、df |
| 命令本身错误 | 占位符写错、路径不存在、脚本 bug | 手工执行归档命令 |
| 目标端限流/网络 | 对象存储限流、网络抖动 | 查看命令的 stderr、目标端日志 |
关键排查思路:把 PostgreSQL 日志里记录的失败命令,原样手工执行一遍,看返回码与错误信息。这比猜测快得多。
# 从日志中找到类似这样的行:
# LOG: archive command failed with exit code 1
# DETAIL: The failed archive command was: cp pg_wal/0000000100000000000000B3 /mnt/wal_archive/0000000100000000000000B3
# 手工执行一遍,观察输出
cp pg_wal/0000000100000000000000B3 /mnt/wal_archive/0000000100000000000000B3
echo $?归档目录的权限与安全
WAL 归档文件的内容等价于数据库内容的明文(重放 WAL 就能重建数据),因此归档目录必须严格保护:
# 创建归档目录并收紧权限
mkdir -p /mnt/wal_archive
chown postgres:postgres /mnt/wal_archive
chmod 0700 /mnt/wal_archive要点:
- 属主必须是 PostgreSQL 运行用户,否则归档写入失败;
- 权限应为
0700,避免同机其他用户读取; - 归档目录不应与数据目录共用同一块磁盘(磁盘故障会同时带走二者);
- 如果要把归档搬到异地,应由独立于 PostgreSQL 的进程来做,而不是让
archive_command直接同步到很远的异地(网络延迟会拖垮归档)。
archive_command 与 archive_library 的选择
| 维度 | archive_command | archive_library |
|---|---|---|
| 引入版本 | 早期版本 | PostgreSQL 15 |
| 实现方式 | 外部命令 | 动态共享库 |
| 性能 | 每次归档 fork 一个进程 | 无进程开销,更快 |
| 部署复杂度 | 低(写脚本即可) | 高(需编译部署 so) |
| 逻辑灵活性 | 极高(任意 shell) | 受限于库的接口 |
| 是否可热改 | 是(reload) | 是(reload) |
绝大多数场景用 archive_command 就够了。只有当归档频率非常高(例如每天成千上万个段)、且 fork 开销成为瓶颈时,archive_library 才体现出价值。
归档延迟的度量
「归档是否及时」可以用两个量来度量:
- 当前 WAL 位置与最近归档段的距离:
SELECT pg_current_wal_lsn() AS current_lsn,
pg_walfile_name(pg_current_wal_lsn()) AS current_seg,
last_archived_wal,
last_archived_time,
now() - last_archived_time AS archive_lag
FROM pg_stat_archiver; current_lsn | current_seg | last_archived_wal | last_archived_time | archive_lag
---------------+----------------------------+---------------------------+----------------------+-------------
0/1A9B1C8 | 0000000100000000000000B5 | 0000000100000000000000B4 | 2026-09-28 10:02:11+08 | 00:01:37pg_wal中未归档段的个数:
ls -1 $PGDATA/pg_wal/ | grep -cE '^[0-9A-F]{24}$'37正常情况下这个数字应该稳定在几十个以内(取决于 wal_keep_size、检查点频率、复制槽)。如果持续增长,就说明归档跟不上。
归档与流复制的 WAL 保留竞争
除了归档,还有多个因素会阻止 WAL 段被回收:
| 保留原因 | 相关参数 / 机制 |
|---|---|
| 归档未完成 | archive_command / archive_library |
| 备库需要 | wal_keep_size、复制槽(pg_replication_slots) |
| 检查点尚未推进 | max_wal_size、检查点频率 |
其中复制槽是最容易造成「意外膨胀」的一项:一个失效的物理复制槽会不断把 restart_lsn 前后的 WAL 全部钉住,导致 pg_wal 无限增长。因此排查 pg_wal 膨胀时,除了看归档,也要看复制槽(相关细节见后续《复制槽(Replication Slot)》一页)。
归档与基础备份的配合
归档只解决「WAL 有副本」,但恢复还需要一个起点。因此归档配置必须与基础备份计划配套:
# 一份典型的基础备份 + 归档配合流程
# 1. 确认归档健康
psql -c "SELECT failed_count, last_archived_time FROM pg_stat_archiver;"
# 2. 做一次基础备份(具体参数见基础备份章节)
pg_basebackup -D /backup/base_20260928 -F t -z -P -X stream
# 3. 备份结束后立即切段,确保备份起点之前的部分都进入归档
psql -c "SELECT pg_switch_wal();"pg_basebackup 的 -X stream 选项会自动把备份期间需要的 WAL 一并打包进备份,这样即使归档短暂滞后,备份本身也是自洽的。
实战示例
示例一:启用归档的完整步骤
# 1. 准备归档目录(权限 0700,属主为 postgres)
sudo mkdir -p /mnt/wal_archive
sudo chown postgres:postgres /mnt/wal_archive
sudo chmod 0700 /mnt/wal_archive# 2. 修改 postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'test -f /mnt/wal_archive/%f && exit 0; cp %p /mnt/wal_archive/%f.tmp && mv /mnt/wal_archive/%f.tmp /mnt/wal_archive/%f'
archive_timeout = 300# 3. 重启(wal_level 与 archive_mode 需要重启)
sudo systemctl restart postgresql-18-- 4. 验证生效
SELECT name, setting, pending_restart FROM pg_settings
WHERE name IN ('wal_level', 'archive_mode', 'archive_command'); name | setting | pending_restart
-----------------+------------------------------------+-----------------
archive_command | test -f /mnt/wal_archive/%f && exi | f
archive_mode | on | f
wal_level | replica | f
(3 rows)-- 5. 主动切段,触发一次归档
SELECT pg_switch_wal();# 6. 确认归档目录里出现了文件
ls -l /mnt/wal_archive/total 16384
-rw------- 1 postgres postgres 16777216 Sep 28 10:05 0000000100000000000000B5示例二:排查归档失败
-- 发现失败
SELECT failed_count, last_failed_wal, last_failed_time, last_archived_wal
FROM pg_stat_archiver; failed_count | last_failed_wal | last_failed_time | last_archived_wal
--------------+-------------------------+----------------------+--------------------------
7 | 0000000100000000000000B6 | 2026-09-28 10:07:33+08 | 0000000100000000000000B5
(7 rows)last_failed_wal 比 last_archived_wal 恰好大一号,说明 ...B6 一直归档不成功。
# 检查磁盘
df -h /mnt/wal_archiveFilesystem Size Used Avail Use% Mounted on
/dev/sdb1 200G 200G 0 100% /mnt/wal_archive找到原因:归档分区满了。清理旧归档或扩容后,归档会自动重试成功。
# 清理 30 天前的归档(注意:必须先确认这些 WAL 已不再被任何恢复目标需要)
find /mnt/wal_archive -name '0000*' -mtime +30 -delete-- 观察恢复
SELECT failed_count, last_archived_wal, last_archived_time FROM pg_stat_archiver; failed_count | last_archived_wal | last_archived_time
--------------+---------------------------+----------------------
7 | 0000000100000000000000C1 | 2026-09-28 10:12:05+08
(1 row)last_archived_wal 已经推进到 ...C1,说明积压的段全部成功归档。
示例三:监控归档延迟
-- 归档延迟监控 SQL:适合放进监控系统定时采样
SELECT
archived_count,
failed_count,
last_archived_wal,
last_archived_time,
now() - last_archived_time AS archive_lag,
CASE
WHEN failed_count > 0 AND now() - last_failed_time < interval '10 minutes'
THEN 'WARN: recent archive failure'
WHEN last_archived_time IS NULL
THEN 'WARN: never archived'
WHEN now() - last_archived_time > interval '15 minutes'
THEN 'WARN: archive lag too high'
ELSE 'OK'
END AS status
FROM pg_stat_archiver; archived_count | failed_count | last_archived_wal | last_archived_time | archive_lag | status
----------------+--------------+---------------------------+----------------------+-------------+--------
1042 | 7 | 0000000100000000000000C1 | 2026-09-28 10:12:05+08 | 00:00:41 | OK
(1 row)示例四:用 pg_wal 大小做兜底告警
-- 统计 pg_wal 中 WAL 段文件的总大小
SELECT count(*) AS wal_segments,
pg_size_pretty(sum(size)) AS total_size
FROM pg_ls_waldir()
WHERE name ~ '^[0-9A-F]{24}$'; wal_segments | total_size
--------------+------------
37 | 592 MB
(1 row)阈值建议:
正常水位:20 ~ 60 个段(约 320MB ~ 960MB)
告警阈值:超过 200 个段(约 3.2GB)
紧急阈值:超过 500 个段(约 8GB)这些阈值必须结合 max_wal_size、wal_keep_size、复制槽数量做调整,不能照搬。
示例五:手工补归档
当某段归档失败但文件仍在 pg_wal 中时,可以手工补归档(前提是确认归档目录确实缺这个文件):
# 假设失败的段是 0000000100000000000000B6
# 1. 确认该段仍存在于 pg_wal
ls -l /var/lib/postgresql/18/main/pg_wal/0000000100000000000000B6
# 2. 手工执行归档命令(与 archive_command 一致)
test -f /mnt/wal_archive/0000000100000000000000B6 && exit 0
cp /var/lib/postgresql/18/main/pg_wal/0000000100000000000000B6 /mnt/wal_archive/0000000100000000000000B6.tmp
mv /mnt/wal_archive/0000000100000000000000B6.tmp /mnt/wal_archive/0000000100000000000000B6
# 3. 让 PostgreSQL 重新尝试归档(它会发现目标已存在,判定成功)注意:不要手工删除 pg_wal 里的段来「解决」归档失败,那会造成 WAL 断档,反而让 PITR 彻底不可用。
注意事项
archive_command 里的路径要写绝对路径。 归档命令的工作目录不是固定的,相对路径会导致行为不确定。同时 %p 和 %f 都由 PostgreSQL 展开,不要把两者混用(例如用 %f 去 pg_wal 中取文件会失败)。
占位符展开后的命令要能被 shell 正确执行。 命令是交给 shell 跑的,因此 >、|、&&、引号等都会生效。这也意味着路径中如果含有空格或特殊字符,需要自行加引号。
归档失败不会阻塞事务,但会阻塞 WAL 回收。 不要因为「业务没受影响」就忽视归档告警。归档问题的表现延迟很大,等到磁盘满时才处理成本极高。
archive_timeout 会产生「半满段」。 被超时强制切换的段只包含少量数据,但文件大小仍是完整的段大小(默认 16MB)。因此 archive_timeout 设置过小会显著浪费存储。
不要用会读取标准输入的命令。 归档进程没有可交互的终端,任何等待 stdin 的命令会永久挂起。
archive_library 与 archive_command 互斥。 升级到 PG15+ 后如果同时设置了两者,服务会因为配置冲突而无法启动。排查启动失败时,这一项值得优先检查。
归档目录不要与数据目录、pg_wal 放在同一文件系统。 一旦该文件系统损坏,数据库与归档会一起丢失,PITR 的价值归零。
修改 archive_mode 后必须重启,且重启会中断连接。 生产环境应把这类变更纳入变更窗口,不要临时执行。
归档文件要防止被篡改。 归档目录如果可被外部进程写入,攻击者可以伪造或替换 WAL 段。应限制访问权限,并在可能的情况下对归档做校验(例如记录校验和)。
临时停止归档的正确做法是把 archive_command 设为空字符串。 即 archive_command = ''(配合 reload 生效)。此时 WAL 会继续在 pg_wal 中堆积,直到恢复归档命令。不要通过设置 archive_mode = off 来「暂停归档」——那需要重启,而且会丢失「备库升主后自动归档」的行为。
归档命令不要长时间挂起。 官方指出归档命令的速度并不重要,只要跟得上 WAL 的平均生成速率即可,落后一点也不会影响正常运转。但如果它挂起(而不是快速失败),就会阻塞整条归档队列,后果比失败更糟。远端命令建议加超时(如 timeout 60 ...)。
归档不包含配置文件的变更。 postgresql.conf、pg_hba.conf、pg_ident.conf 是通过手工编辑而非 SQL 修改的,因此不会被写入 WAL,也就不会被归档恢复。备份方案里必须单独覆盖配置文件(例如把它们放在一个会被常规文件备份覆盖的位置)。
最佳实践
归档命令先写成脚本,再引用脚本。 直接把复杂逻辑塞进
archive_command字符串里,容易因引号转义出错。写一个/usr/local/bin/pg_archive_wal.sh脚本,archive_command = '/usr/local/bin/pg_archive_wal.sh %p %f',可测试、可复用、可版本管理。归档脚本必须显式
exit 0/exit 1。 脚本最后一条命令的返回码会成为整个脚本的返回码,容易与预期不符。所有成功路径都显式exit 0,所有失败路径都显式exit 1。归档到本地缓存目录 + 独立进程同步异地。 让
archive_command只做本地(或近端网络存储)的高速复制,再由独立的同步进程(如rsync定时任务、对象存储同步工具)把归档搬运到异地。这样归档路径短、成功率高,异地由独立链路保障。对
pg_stat_archiver与pg_wal建立双重监控。 前者反映「归档是否成功」,后者反映「堆积是否失控」。两者结合才能在故障早期发现问题。failed_count要关注增量而非绝对值。 累计值有历史残留,用「最近 10 分钟失败次数」这类派生指标做告警更准确。为低写入库设置合理的
archive_timeout。 建议 300 秒(5 分钟)左右,既限制了 RPO 上界,又不会产生过多半满段。每次变更归档配置后立即做一次验证归档。 执行
pg_switch_wal()并确认归档目录出现新文件,同时确认failed_count没有增长。把归档命令手工执行一遍作为上线检查项。 用真实参数手工跑一次,观察返回码与归档结果,避免「语法错误但恰好返回 0」的隐患。
归档保留策略要满足最远恢复目标。 归档不能只保留最近几天的,必须覆盖「最远可能需要的恢复时间点 + 基础备份的保留周期」。
归档目录的容量要留足余量。 按 WAL 生成速率估算,并预留至少 1.5 倍余量应对写入峰值与清理延迟。
升级 PostgreSQL 大版本时重新审视归档配置。
archive_library等参数的行为在版本间可能变化,升级后要重新做一次归档验证。归档的异地副本要定期做可用性抽查。 不仅看文件在不在,还要抽查几个段能否被正常读取与解析,避免「文件在但内容坏了」。
参考链接
- PostgreSQL 官方文档:Continuous Archiving and Point-in-Time Recovery (PITR)
- PostgreSQL 官方文档:Write Ahead Log — Configuration Parameters
- PostgreSQL 官方文档:Monitoring Database Activity — pg_stat_archiver
- PostgreSQL 官方文档:System Administration Functions — Backup Control Functions
- PostgreSQL 官方文档:pg_waldump
- PostgreSQL 官方文档:pg_basebackup
- PostgreSQL 官方文档:Recovery Configuration
- PostgreSQL 官方文档:Log-Shipping Standby Servers