Skip to content

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_sizepg_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_countbigint成功归档的 WAL 段总数
last_archived_waltext最近成功归档的 WAL 段文件名
last_archived_timetimestamptz最近一次归档成功的时间
failed_countbigint归档失败的总次数
last_failed_waltext最近一次失败的 WAL 段文件名
last_failed_timetimestamptz最近一次失败的时间
stats_resettimestamptz统计计数器上次重置的时间

注意 failed_count 是累计的,因此它不为 0 并不一定意味着当前有问题——可能是历史上一闪而过的网络抖动。真正需要关注的是:

  • failed_count 是否在持续增长;
  • last_failed_wal 是否与 last_archived_wal 相同(说明同一个段反复失败);
  • last_archived_time 是否明显落后于当前时间。

语法与用法 ​

最小可用配置 ​

conf
# 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

修改后的生效检查 ​

sql
-- 查看参数当前值(注意 source 表示它来自哪个配置文件)
SELECT name, setting, pending_restart, source
FROM pg_settings
WHERE name IN ('wal_level', 'archive_mode', 'archive_command', 'archive_library', 'archive_timeout');
text
      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 相差的字节数
sql
-- 主动切段并立即触发归档(例如在做基础备份前后)
SELECT pg_switch_wal();
text
 pg_switch_wal
---------------
 0/1A9B1C8
(1 row)

重置归档统计 ​

sql
SELECT pg_stat_reset_shared('archiver');
text
 pg_stat_reset_shared
----------------------

(1 row)

重置之后,archived_count 与 failed_count 都归零,stats_reset 更新为当前时间。重置前应先记录当前值,便于对比。重置本身不影响归档功能,只是清零计数器。

详细说明 ​

归档进程的工作方式 ​

归档是由后台的 archiver 进程完成的,它不与用户事务耦合,而是按自己的节奏工作。理解它的工作循环有助于判断「为什么会慢」「为什么失败会堆积」:

text
循环:
  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,服务端重试;
  • 服务端在判定成功之前崩溃重启,重启后重新归档同一段;
  • 归档目标在写入后被回滚(例如网络存储的短暂回退)。

因此归档命令必须幂等。官方给出的范式:

bash
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,严格来说并不符合这个要求,只是它足够简单、且触发场景罕见(仅在崩溃重启后重归档时出现),因此被官方作为示例给出。

一个语义更严谨的写法是:

bash
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。原子改名避免了「复制中断留下半截文件、却已被判定为完成」的隐患。

对比一下不推荐的直接覆盖写法:

bash
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简单直连,缺少续传
对象存储 CLIaws s3 cp %p s3://bucket/wal/%f云上常见,需关注退出码

用 rsync 或 scp 时要注意:这类命令在目标不可达时可能长时间挂起,而不是快速失败。挂起会导致归档停滞、pg_wal 膨胀。因此通常需要配合超时机制,例如:

bash
timeout 60 rsync -a %p archive@host:/wal/%f

timeout 命令在超时后返回非 0,PostgreSQL 会记录失败并稍后重试,这比无限挂起安全得多。

归档与 pg_wal 膨胀 ​

这是归档失败最真实、也最容易被忽视的后果。

WAL 段的回收需要满足若干条件,其中一条是「已成功归档」。如果归档失败,段就无法回收,pg_wal 就会持续增长。增长是缓慢而持续的,因此不会立刻触发任何告警——直到磁盘用尽。

text
归档正常:                         归档失败:
pg_wal 大小 ──── 波动但稳定         pg_wal 大小 ──── 单调上升
     │                                   │
     └── 段回收与新增平衡                  └── 只增不减,直到磁盘满

如果 pg_wal 写满,PostgreSQL 会发生 PANIC 级别错误并关闭。这是生产事故等级的事件,因为:

  • 数据库不可用;
  • 恢复需要先腾出空间,而腾出空间又需要归档成功或放弃某些 WAL;
  • 处置过程本身有数据丢失风险。

因此监控 pg_wal 目录大小是归档运维的必备项。

归档失败的五类原因 ​

归档失败的原因大体可以归为五类,排查时按这个顺序过一遍,命中率很高。

类别典型表现排查手段
磁盘空间不足归档目录或 pg_wal 所在分区满df -h
权限问题归档目录属主/权限不对,写不进去ls -ld、检查运行用户
挂载点异常NFS/远端挂载掉线或只读mount、df
命令本身错误占位符写错、路径不存在、脚本 bug手工执行归档命令
目标端限流/网络对象存储限流、网络抖动查看命令的 stderr、目标端日志

关键排查思路:把 PostgreSQL 日志里记录的失败命令,原样手工执行一遍,看返回码与错误信息。这比猜测快得多。

bash
# 从日志中找到类似这样的行:
# 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 就能重建数据),因此归档目录必须严格保护:

bash
# 创建归档目录并收紧权限
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_commandarchive_library
引入版本早期版本PostgreSQL 15
实现方式外部命令动态共享库
性能每次归档 fork 一个进程无进程开销,更快
部署复杂度低(写脚本即可)高(需编译部署 so)
逻辑灵活性极高(任意 shell)受限于库的接口
是否可热改是(reload)是(reload)

绝大多数场景用 archive_command 就够了。只有当归档频率非常高(例如每天成千上万个段)、且 fork 开销成为瓶颈时,archive_library 才体现出价值。

归档延迟的度量 ​

「归档是否及时」可以用两个量来度量:

  1. 当前 WAL 位置与最近归档段的距离:
sql
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;
text
  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:37
  1. pg_wal 中未归档段的个数:
bash
ls -1 $PGDATA/pg_wal/ | grep -cE '^[0-9A-F]{24}$'
text
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 有副本」,但恢复还需要一个起点。因此归档配置必须与基础备份计划配套:

bash
# 一份典型的基础备份 + 归档配合流程
# 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 一并打包进备份,这样即使归档短暂滞后,备份本身也是自洽的。

实战示例 ​

示例一:启用归档的完整步骤 ​

bash
# 1. 准备归档目录(权限 0700,属主为 postgres)
sudo mkdir -p /mnt/wal_archive
sudo chown postgres:postgres /mnt/wal_archive
sudo chmod 0700 /mnt/wal_archive
conf
# 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
bash
# 3. 重启(wal_level 与 archive_mode 需要重启)
sudo systemctl restart postgresql-18
sql
-- 4. 验证生效
SELECT name, setting, pending_restart FROM pg_settings
WHERE name IN ('wal_level', 'archive_mode', 'archive_command');
text
      name       |              setting               | pending_restart
-----------------+------------------------------------+-----------------
 archive_command | test -f /mnt/wal_archive/%f && exi | f
 archive_mode    | on                                 | f
 wal_level       | replica                            | f
(3 rows)
sql
-- 5. 主动切段,触发一次归档
SELECT pg_switch_wal();
bash
# 6. 确认归档目录里出现了文件
ls -l /mnt/wal_archive/
text
total 16384
-rw------- 1 postgres postgres 16777216 Sep 28 10:05 0000000100000000000000B5

示例二:排查归档失败 ​

sql
-- 发现失败
SELECT failed_count, last_failed_wal, last_failed_time, last_archived_wal
FROM pg_stat_archiver;
text
 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 一直归档不成功。

bash
# 检查磁盘
df -h /mnt/wal_archive
text
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1       200G  200G     0 100% /mnt/wal_archive

找到原因:归档分区满了。清理旧归档或扩容后,归档会自动重试成功。

bash
# 清理 30 天前的归档(注意:必须先确认这些 WAL 已不再被任何恢复目标需要)
find /mnt/wal_archive -name '0000*' -mtime +30 -delete
sql
-- 观察恢复
SELECT failed_count, last_archived_wal, last_archived_time FROM pg_stat_archiver;
text
 failed_count |    last_archived_wal      |  last_archived_time
--------------+---------------------------+----------------------
            7 | 0000000100000000000000C1  | 2026-09-28 10:12:05+08
(1 row)

last_archived_wal 已经推进到 ...C1,说明积压的段全部成功归档。

示例三:监控归档延迟 ​

sql
-- 归档延迟监控 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;
text
 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 大小做兜底告警 ​

sql
-- 统计 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}$';
text
 wal_segments | total_size
--------------+------------
           37 | 592 MB
(1 row)
text
阈值建议:
  正常水位:20 ~ 60 个段(约 320MB ~ 960MB)
  告警阈值:超过 200 个段(约 3.2GB)
  紧急阈值:超过 500 个段(约 8GB)

这些阈值必须结合 max_wal_size、wal_keep_size、复制槽数量做调整,不能照搬。

示例五:手工补归档 ​

当某段归档失败但文件仍在 pg_wal 中时,可以手工补归档(前提是确认归档目录确实缺这个文件):

bash
# 假设失败的段是 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,也就不会被归档恢复。备份方案里必须单独覆盖配置文件(例如把它们放在一个会被常规文件备份覆盖的位置)。

最佳实践 ​

  1. 归档命令先写成脚本,再引用脚本。 直接把复杂逻辑塞进 archive_command 字符串里,容易因引号转义出错。写一个 /usr/local/bin/pg_archive_wal.sh 脚本,archive_command = '/usr/local/bin/pg_archive_wal.sh %p %f',可测试、可复用、可版本管理。

  2. 归档脚本必须显式 exit 0 / exit 1。 脚本最后一条命令的返回码会成为整个脚本的返回码,容易与预期不符。所有成功路径都显式 exit 0,所有失败路径都显式 exit 1。

  3. 归档到本地缓存目录 + 独立进程同步异地。 让 archive_command 只做本地(或近端网络存储)的高速复制,再由独立的同步进程(如 rsync 定时任务、对象存储同步工具)把归档搬运到异地。这样归档路径短、成功率高,异地由独立链路保障。

  4. 对 pg_stat_archiver 与 pg_wal 建立双重监控。 前者反映「归档是否成功」,后者反映「堆积是否失控」。两者结合才能在故障早期发现问题。

  5. failed_count 要关注增量而非绝对值。 累计值有历史残留,用「最近 10 分钟失败次数」这类派生指标做告警更准确。

  6. 为低写入库设置合理的 archive_timeout。 建议 300 秒(5 分钟)左右,既限制了 RPO 上界,又不会产生过多半满段。

  7. 每次变更归档配置后立即做一次验证归档。 执行 pg_switch_wal() 并确认归档目录出现新文件,同时确认 failed_count 没有增长。

  8. 把归档命令手工执行一遍作为上线检查项。 用真实参数手工跑一次,观察返回码与归档结果,避免「语法错误但恰好返回 0」的隐患。

  9. 归档保留策略要满足最远恢复目标。 归档不能只保留最近几天的,必须覆盖「最远可能需要的恢复时间点 + 基础备份的保留周期」。

  10. 归档目录的容量要留足余量。 按 WAL 生成速率估算,并预留至少 1.5 倍余量应对写入峰值与清理延迟。

  11. 升级 PostgreSQL 大版本时重新审视归档配置。 archive_library 等参数的行为在版本间可能变化,升级后要重新做一次归档验证。

  12. 归档的异地副本要定期做可用性抽查。 不仅看文件在不在,还要抽查几个段能否被正常读取与解析,避免「文件在但内容坏了」。

参考链接 ​