Skip to content

Archiver 与统计信息(Archiver Process) ​

Archiver(归档进程)是 PostgreSQL 多进程架构中最后一个常驻辅助进程:当 archive_mode 打开时,它负责把写满的 WAL 段文件复制到归档目录,为基础备份与时间点恢复(PITR)提供依据。本节除了讲解归档进程本身,还会顺带梳理 PostgreSQL 的统计信息收集机制——因为 pg_stat_archiver 正是这套机制的一个典型使用者,理解它就能理解所有 pg_stat_* 视图的共性行为。

前置知识

阅读本节前,建议先了解:Checkpointer(检查点进程)

基础概念 ​

术语英文说明
归档Archiving把已写满(或已切换到)的 WAL 段文件复制到库外目录的持续过程
归档进程Archiver Process常驻辅助进程,仅在 archive_mode = on 时启动
归档模式archive_modeoff / on / always,决定是否启用 WAL 归档
归档命令archive_command一个 shell 命令,把 %p 指代的 WAL 文件送到 %f 指定的目标
归档库archive_library用 C 共享库替代 archive_command(PostgreSQL 15+)
归档超时archive_timeout超过该时长未被切换的 WAL 段也会被强制切换并归档
WAL 段WAL Segment默认 16MB 的 WAL 文件,归档以「段」为最小单位
统计信息收集器Statistics CollectorPostgreSQL 15 之前是一个独立进程,PG 15 起改为共享内存
共享内存统计Shared Memory StatisticsPG 15+ 的统计存储方式,无独立进程,开销更低
统计重置stats_reset各统计视图中的时间戳字段,标记上次重置的时刻

归档不是备份,但备份依赖归档

必须分清两个概念:

  • 归档(Archiving):持续、自动地把 WAL 段搬到别处。它保证「WAL 不会因为 pg_wal 被回收而丢失」。
  • 备份(Backup):某个时刻数据库全量状态的一份快照(基础备份,Base Backup)。

基础备份 + 归档 WAL = 可以恢复到任意时间点(PITR)。只做归档不做基础备份,等于保存了一堆「增量差异」,无从还原;只做基础备份不做归档,只能恢复到备份时刻。两者缺一不可。

另外,归档不等于复制:归档是把 WAL 送到外部存储(或对象存储),流复制(Streaming Replication)是把 WAL 实时推给备库。二者可以并用,互为补充。

归档在整个持久性链路中的位置 ​

text
     后端提交事务
          │
          ▼
   ┌─────────────┐    write()     ┌──────────────┐
   │ WAL Buffer  │ ─────────────► │ pg_wal/*.wal │
   │ (共享内存)   │                │  (本地磁盘)   │
   └─────────────┘                └──────┬───────┘
                                         │
                          WAL 段写满后切换(Switch)
                                         │
                                         ▼
                                 ┌───────────────┐
                                 │  Archiver 进程 │
                                 └───────┬───────┘
                                         │ 执行 archive_command
                                         │ 或调用 archive_library
                                         ▼
                                 ┌───────────────┐
                                 │  归档目录/对象存储 │
                                 │  (远离本机的介质)  │
                                 └───────────────┘

归档完成后,该 WAL 段才真正"能量耗尽",可被 pg_wal 回收。
(注意:归档并不会阻止 pg_wal 回收;检查点与归档是两个独立机制。)

统计信息机制的演进(PG 15 分水岭) ​

text
PostgreSQL 14 及以前:
  ┌────────────────────┐        ┌──────────────────────┐
  │ 各后端进程          │ ─UDP─► │ stats collector 进程  │
  │ 收集本地计数        │        │ 汇总并写入磁盘        │
  └────────────────────┘        └───────────┬──────────┘
                                            │ 写文件
                                            ▼
                                   pg_stat_tmp/、pg_stat/ 目录

PostgreSQL 15 及以后(当前版本):
  ┌────────────────────┐        ┌──────────────────────┐
  │ 各后端进程          │ ─直接─► │ 共享内存统计区        │
  │ 原子更新计数器      │        │ (无独立进程)        │
  └────────────────────┘        └───────────┬──────────┘
                                            │ 定期落盘
                                            ▼
                                   pg_stat/ 目录(仅持久化)

关键变化:
  · 不再有 stats collector 进程,pg_stat_activity 中的对应 backend_type 消失
  · 统计更新是"即时的",延迟由 PG 15 前的最多 1 秒降到微秒级
  · 进程启动/退出不再引发统计抖动

核心机制 ​

Archiver 进程的工作循环 ​

Archiver 进程由 postmaster 在 archive_mode = on 时启动,逻辑非常直白:不断寻找「已经准备好归档」的 WAL 段,执行归档命令,标记结果。

c
/* 归档进程主循环(伪代码,源码在 src/backend/postmaster/pgarch.c) */
for (;;)
{
    /* 1. 清空上一轮的失败状态,等待一个「待归档」的 WAL 段出现 */
    if (pgarch_readyXlog(xlog))
    {
        /* 2. 已有等待归档的文件,直接处理,不用睡 */
    }
    else
    {
        /* 3. 没有待归档文件:按 archive_timeout 定时唤醒,否则长期休眠 */
        if (archive_timeout > 0)
            WaitLatch(..., archive_timeout * 1000L);
        else
            WaitLatch(..., 60 * 1000L);   /* 无超时则最多睡 60 秒 */

        /* 4. 顺便检查是否需要强制切换 WAL 段 */
        pgarch_ForcePendingXLog();
        continue;
    }

    /*
     * 5. 执行归档:
     *    a) 优先使用 archive_library(PG 15+)
     *    b) 否则 fork 一个子进程 sh -c 执行 archive_command
     *    命令返回 0 表示成功;非 0 或信号终止表示失败
     */
    if (pgarch_archiveXlog(xlog))
    {
        /* 6. 成功:从 pending 列表移除,更新 Statistics 共享内存 */
        pgarch_clearXlog(xlog);
        pgstat_report_archiver(xlog, true);
    }
    else
    {
        /* 7. 失败:保留在 pending 列表,记录失败信息,稍后重试 */
        pgstat_report_archiver(xlog, false);
        /* 关键:不会放弃,会一直重试直到成功 */
        pgarch_RetryXlog();
    }
}

从这个循环可以读出三条重要性质:

  1. 失败会无限重试:WAL 段只要没归档成功,就永远不会从待归档列表中消失。这既保证了不丢数据,也意味着归档目录写满或权限错误会最终撑爆 pg_wal。
  2. 归档不阻塞业务:后端提交只需要 WAL 落到本地 pg_wal,归档慢不会直接拖慢事务(但会间接通过 pg_wal 膨胀引发问题)。
  3. 归档的最小粒度是 WAL 段:一个只写了 1KB 的 WAL 段也必须整段归档(16MB)。这就是 archive_timeout 存在的意义——避免低流量实例的 WAL 段长时间不切换,导致 PITR 恢复点稀疏。

archive_command 与 archive_library ​

ini
# postgresql.conf
#
# 方式一:archive_command(经典方式,几乎所有版本都支持)
# %p = 待归档 WAL 文件的完整路径(相对于 Data 目录)
# %f = 待归档 WAL 文件名(不含路径)
# %% = 字面量百分号
archive_mode = on
archive_command = 'test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f'

# 方式二:archive_library(PostgreSQL 15+,用共享库替代 shell 命令)
# 与 archive_command 互斥,只能二选一
# archive_library = 'basic_archive'
# basic_archive.archive_directory = '/mnt/wal_archive'

# 归档超时:即使 WAL 段未写满,超过该时间也强制切换并归档
archive_timeout = 60s

# 归档命令的调试日志(PG 12+ 参数名为 log_verbosity,更早为 log_archive_verbose)
# archive_command 的 stdout/stderr 会被写入服务器日志
log_min_messages = warning

关于 archive_command 的三条铁律:

  • 必须幂等:归档进程可能重试同一文件,命令必须能安全地重复执行。上面的 test ! -f ... && cp ... 就是标准写法——若目标已存在则跳过(视为成功)。
  • 必须同步落盘:如果目标是 NFS 或本地目录,最好配合同步写入;如果归档目标被缓存而没真正落盘,崩溃时可能丢失 WAL。
  • 禁止就地重命名/移动到已归档目录的「半成品」再改名之外的写法:标准做法是先写到临时文件再 mv 到位,避免归档不完整文件被误认为已归档。
ini
# postgresql.conf —— 先写临时文件再原子重命名(更稳健的写法)
archive_command = 'cp %p /mnt/wal_archive/%f.tmp && mv /mnt/wal_archive/%f.tmp /mnt/wal_archive/%f'

archive_library 是 PostgreSQL 15 引入的替代方案,避免每次归档都 fork 一个 shell 进程:

对比项archive_commandarchive_library
引入版本很早PostgreSQL 15+
实现形式shell 命令,每次 fork 子进程C 共享库,进程内调用
开销每次归档一次 fork + exec更低(但归档本身是低频操作)
灵活性极高,可用任意脚本受限于已编译的库
适用场景绝大多数场景需要内嵌逻辑或避免 fork 的场景
官方示例—basic_archive(内置,等价于 cp)

统计信息的收集机制 ​

PostgreSQL 的统计信息分为几大类,由不同的开关控制:

参数默认值收集内容相关视图
track_countson表/索引级的增删改计数pg_stat_all_tables、pg_stat_all_indexes
track_activitieson各会话当前正在执行的命令pg_stat_activity
track_io_timingoffI/O 操作的耗时(需要系统调用支持)pg_stat_io、pg_stat_database、pg_stat_bgwriter
track_functionsnone函数调用次数与耗时pg_stat_user_functions
track_wal_io_timingoffWAL 写入/刷盘的耗时pg_stat_wal
track_commit_timestampoff事务提交时间(需重启)pg_xact_commit_timestamp()

统计信息写入共享内存后,由 stats collector 定期落盘机制持久化(PG 15 起由后台在检查点和关闭时落盘到 pg_stat/ 目录),因此:

  • 普通视图(如 pg_stat_archiver)在实例重启后清零,只有部分累计信息会从磁盘恢复。
  • stats_reset 字段记录上一次统计被重置的时间,用于判断「这些数字是积累了多久的」。重置方式有 pg_stat_reset()(仅当前库的表统计)、pg_stat_reset_shared('archiver')(重置共享级视图)等。
sql
-- 重置归档统计,重置后 stats_reset 会被更新为当前时间
SELECT pg_stat_reset_shared('archiver');

-- 重置 I/O 统计(需要 superuser 或 pg_monitor 角色)
SELECT pg_stat_reset_shared('io');

-- 重置当前数据库的表/索引统计
SELECT pg_stat_reset();

-- 重置单个表的统计
SELECT pg_stat_reset_single_table_counters('public.orders'::regclass);

关键参数一览 ​

参数默认值生效方式说明
archive_modeoff需重启on 启用归档;always 在备库上也归档
archive_command''reload归档命令,与 archive_library 互斥
archive_library''reload归档共享库(PG 15+),与 archive_command 互斥
archive_timeout0(禁用)reload超时强制切换 WAL 段,推荐 60s~300s
wal_levelreplica需重启归档最低要求 replica;逻辑复制需 logical
log_min_messageswarningreload决定归档命令的 stdout/stderr 是否进日志

archive_mode = always 的用途

archive_mode = always 让备库也执行归档。典型场景是:主库不做归档以免增加主库 I/O,而由一台专门的备库承担归档工作。注意 always 会归档备库上所有 WAL(包括主库产生的),因此不要与主库同时归档到同一目录,否则会出现文件冲突。

详细说明 ​

pg_stat_archiver 字段逐项解读 ​

pg_stat_archiver 是实例级视图,只有一行,是判断归档健康度的核心依据。

sql
SELECT
  archived_count,                 -- 累计成功归档的 WAL 段数
  last_archived_wal,              -- 最近成功归档的 WAL 文件名
  last_archived_time,             -- 最近成功归档的时间
  failed_count,                   -- 累计归档失败的次数
  last_failed_wal,                -- 最近一次失败的 WAL 文件名
  last_failed_time,               -- 最近一次失败的时间
  stats_reset                     -- 统计重置时间
FROM pg_stat_archiver;
text
-[ RECORD 1 ]------+------------------------------
archived_count     | 18423
last_archived_wal  | 0000000100000002000000A3
last_archived_time | 2026-09-22 10:32:05.117+08
failed_count       | 3
last_failed_wal    | 00000001000000020000007E
last_failed_time   | 2026-09-22 09:14:22.882+08
stats_reset        | 2026-09-01 00:00:00.104+08

判读要点:

现象含义处理方向
archived_count 持续增长归档在正常工作无需处理
archived_count 长时间不变,last_archived_time 很旧归档停滞检查 archive_command、归档目录空间/权限
failed_count > 0 且持续增长归档反复失败看 PG 日志中归档命令的 stderr
last_failed_wal 比 last_archived_wal 旧有段卡住,后续也无法推进高危:pg_wal 会持续膨胀
failed_count 有少量且不再增长曾经瞬时失败后自愈记录并观察,通常无害
stats_reset 很近统计被重置过,看 archived_count 要结合时间用「归档速率」而非绝对值判断

用「归档速率」判断健康度比看绝对值更可靠:

sql
-- 判断归档是否在推进:两次采样对比 last_archived_time
SELECT
  last_archived_wal,
  last_archived_time,
  now() - last_archived_time AS 距上次成功归档的时长,
  CASE
    WHEN last_archived_time IS NULL THEN '从未归档'
    WHEN now() - last_archived_time > interval '10 minutes' THEN '⚠ 归档可能停滞'
    ELSE 'OK'
  END AS 状态
FROM pg_stat_archiver;

归档失败的常见原因与排查链路 ​

归档失败永远不会自动放弃,所以一旦持续失败,后果是 pg_wal 不断增长直到写满磁盘、实例停机。排查链路如下:

bash
# 1. 先看归档统计:失败是否在持续增长
psql -c "SELECT failed_count, last_failed_wal, last_failed_time FROM pg_stat_archiver;"

# 2. 关键一步:看 PostgreSQL 服务器日志中归档命令输出的错误
#    归档命令的 stdout/stderr 会被写进服务器日志
grep -i "archive" /var/log/postgresql/postgresql-18-main.log | tail -n 50

# 3. 手动验证归档命令是否可用(把命令里的 %p/%f 换成真实值)
#    例:archive_command = 'test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f'
sudo -u postgres test -w /mnt/wal_archive && echo "目录可写"

# 4. 检查归档目标所在分区的剩余空间
df -h /mnt/wal_archive

# 5. 检查是否已有同名文件导致冲突(归档目标被多个实例共用)
ls -l /mnt/wal_archive | grep 00000001000000020000007E

# 6. 检查 pg_wal 膨胀情况,评估紧急程度
psql -c "SELECT count(*) AS wal_files, pg_size_pretty(sum(size)) AS total FROM pg_ls_waldir();"

出现频率最高的五类原因:

原因典型日志/现象修复方式
归档目录所在磁盘写满日志出现 No space left on device清理空间、扩容,或临时换归档目标
权限不足Permission denied修正目标目录属主/权限(属主应为 postgres)
NFS/网络存储挂载失效Stale file handle、超时修复挂载,必要时临时切换归档目录
多个实例归档到同一目录文件名冲突、test ! -f 恒为假每个实例使用独立子目录
命令写错(%p/%f 用反、路径不存在)归档一直失败且 failed_count 持续增长用 archive_command 手工验证后修正

紧急情况的临时处置:如果归档目标短期无法修复,可以先改 archive_command 到一个可写的临时目录(reload 即刻生效,无需重启),把 WAL 抢救出来,待正式存储恢复后再补归档。不要简单地把 archive_mode 改成 off——那会造成 WAL 段被回收、归档链断裂、PITR 能力永久损坏。

统计信息的收集延迟、重置与持久化 ​

理解这套机制可以避免误读所有 pg_stat_* 视图:

sql
-- 查看统计相关开关的当前值
SELECT name, setting, unit, context
FROM pg_settings
WHERE name IN ('track_counts', 'track_activities', 'track_io_timing',
               'track_functions', 'track_wal_io_timing', 'stats_temp_directory')
ORDER BY name;

三条实用结论:

  1. 统计值有轻微延迟。PG 15 起延迟几乎可忽略(无 stats collector 进程),但并发极高时计数器更新仍可能在极短时间内滞后。做快照对比时,间隔取秒级即可。
  2. 视图中的 stats_reset 必须看。它解释了「为什么数字比预期小」,也提醒你「这个累计值是从何时开始的」。做容量规划时应结合 stats_reset 换算成速率。
  3. PG 15 前的老版本有 stats collector 进程。如果你的实例是 PG 14 及以前,pg_stat_activity 里会看到一个 backend_type = 'stats collector' 的进程;PG 15+ 已不存在,不要照搬老版本监控脚本去检查它是否存活。
sql
-- PG 15+ 检查辅助进程清单:已无 stats collector
SELECT pid, backend_type, backend_start
FROM pg_stat_activity
WHERE backend_type IN ('checkpointer', 'background writer', 'walwriter',
                       'autovacuum launcher', 'archiver', 'logical replication launcher')
ORDER BY backend_type;
text
-[ RECORD 1 ]+---------------------------
pid          | 1284
backend_type | archiver
backend_start| 2026-09-20 03:00:01.882+08
-[ RECORD 2 ]+---------------------------
pid          | 1282
backend_type | autovacuum launcher
backend_start| 2026-09-20 03:00:01.881+08
-[ RECORD 3 ]+---------------------------
pid          | 1279
backend_type | background writer
backend_start| 2026-09-20 03:00:01.879+08
-[ RECORD 4 ]+---------------------------
pid          | 1278
backend_type | checkpointer
backend_start| 2026-09-20 03:00:01.879+08
-[ RECORD 5 ]+---------------------------
pid          | 1280
backend_type | walwriter
backend_start| 2026-09-20 03:00:01.880+08
(注意:这里没有 "stats collector" —— PostgreSQL 15 起统计信息改由共享内存承载)

实战示例 ​

示例 1:一键归档健康检查 ​

sql
-- 归档健康度综合检查:一次性给出关键结论
SELECT
  archived_count                                   AS 成功归档数,
  failed_count                                     AS 失败次数,
  last_archived_wal                                AS 最近归档的WAL,
  now() - last_archived_time                       AS 距上次成功归档,
  COALESCE(last_failed_wal, '(无)')                AS 最近失败的WAL,
  CASE
    WHEN last_archived_time IS NULL                THEN '严重:从未成功归档'
    WHEN now() - last_archived_time > interval '30 minutes' THEN '严重:归档停滞'
    WHEN last_failed_time > last_archived_time     THEN '警告:最近有失败,需查看日志'
    ELSE '正常'
  END                                              AS 健康状态,
  stats_reset                                      AS 统计起点
FROM pg_stat_archiver;
text
 成功归档数 | 失败次数 | 最近归档的WAL              | 距上次成功归档 | 最近失败的WAL              | 健康状态 | 统计起点
------------+----------+----------------------------+----------------+----------------------------+----------+----------------------------
      18423 |        3 | 0000000100000002000000A3   | 00:01:12       | 00000001000000020000007E   | 正常     | 2026-09-01 00:00:00.104+08

示例 2:对照归档与 pg_wal 占用,判断是否卡住 ​

sql
-- 归档停滞时,pg_wal 会迅速膨胀;这个查询把两者放在一起看
SELECT
  a.archived_count,
  a.last_archived_wal,
  now() - a.last_archived_time                   AS 归档停滞时长,
  w.wal_files,
  pg_size_pretty(w.total_size)                   AS pg_wal占用,
  c.checkpoints_timed + c.checkpoints_req        AS 累计检查点数
FROM pg_stat_archiver a
CROSS JOIN (
  SELECT count(*) AS wal_files, sum(size) AS total_size FROM pg_ls_waldir()
) w
CROSS JOIN pg_stat_checkpointer c;   -- PostgreSQL 17+

如果 pg_wal占用 达到 max_wal_size 的两三倍且仍在增长,同时归档停滞时长很大,说明归档卡住已经阻止了 WAL 段回收,必须立即处理,否则实例会因磁盘写满而停机。

sql
-- 找出最老的待归档 WAL:定位到底卡在哪一段之后
SELECT name, size, modification
FROM pg_ls_waldir()
ORDER BY modification
LIMIT 10;
text
        name           |  size   |      modification
-----------------------+---------+------------------------
 00000001000000020000007E | 16777216 | 2026-09-22 09:14:20+08
 00000001000000020000007F | 16777216 | 2026-09-22 09:15:01+08
 000000010000000200000080 | 16777216 | 2026-09-22 09:15:48+08
...

与 pg_stat_archiver.last_failed_wal 对比:若最老的段号恰好等于(或紧随)last_failed_wal,即可确信是这一段归档失败堵住了整条流水线。

示例 3:测量归档速率,做容量规划 ​

sql
-- 归档速率测量:请间隔 5 分钟执行两次,比较 archived_count 的增量
SELECT
  archived_count,
  last_archived_time,
  stats_reset
FROM pg_stat_archiver;
bash
# 用一段脚本自动采样两次,算出 WAL 归档速率(MB/s)
psql -At -c "SELECT archived_count FROM pg_stat_archiver;" > /tmp/a1
sleep 300
psql -At -c "SELECT archived_count FROM pg_stat_archiver;" > /tmp/a2
# 每个 WAL 段默认 16MB,故速率 ≈ (a2 - a1) * 16 / 300 MB/s
awk 'NR==FNR{a=$1; next} {printf "归档速率 ≈ %.2f MB/s\n", ($1-a)*16/300}' /tmp/a1 /tmp/a2

# 更直接的算法:用 LSN 差值算出真实 WAL 生成速率
psql -At -c "SELECT pg_current_wal_lsn();" > /tmp/l1
sleep 300
psql -At -c "SELECT pg_current_wal_lsn();" > /tmp/l2
psql -At -c "SELECT pg_size_pretty(pg_wal_lsn_diff('$(cat /tmp/l2)', '$(cat /tmp/l1)')) || ' / 5min';"

这些数字直接决定归档目录需要多大:归档存储容量 ≈ WAL 生成速率 × 保留天数。例如 10 MB/s 的生成速率,保留 7 天需要约 6 TB。

示例 4:归档开启前后的确认与验证 ​

sql
-- 确认归档相关配置是否已生效
SELECT name, setting, source
FROM pg_settings
WHERE name IN ('archive_mode', 'archive_command', 'archive_library', 'archive_timeout', 'wal_level')
ORDER BY name;
text
      name       |             setting              |  source
-----------------+----------------------------------+----------
 archive_command | test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f | configuration file
 archive_library |                                  | default
 archive_mode    | on                               | configuration file
 archive_timeout | 60s                              | configuration file
 wal_level       | replica                          | configuration file
bash
# 强制切换一个 WAL 段,立刻验证归档链路是否走得通
psql -c "SELECT pg_switch_wal();"     # PostgreSQL 10+(PG 9.6 及以前为 pg_switch_xlog)

# 等待几秒后,确认刚才切换的段已出现在归档目录
ls -l /mnt/wal_archive/ | tail -n 5

# 再回看统计,archived_count 应当 +1
psql -c "SELECT archived_count, last_archived_wal, last_archived_time FROM pg_stat_archiver;"

示例 5:配合基础备份完成一次 PITR 演练 ​

bash
# 1. 开始基础备份(PostgreSQL 15+ 使用 pg_backup_start / pg_backup_stop)
#    PostgreSQL 14 及以前为 pg_start_backup / pg_stop_backup
psql -c "SELECT pg_backup_start('pitr-drill', fast => true);"

# 2. 用物理复制工具打包 PGDATA(此处演示 rsync;生产推荐 pg_basebackup)
rsync -a --exclude=pg_wal /var/lib/postgresql/18/main/ /mnt/backup/base_$(date +%F)/

# 3. 结束基础备份,取得本次备份的 WAL 位置(备份标签)
psql -At -c "SELECT pg_backup_stop();" > /mnt/backup/base_$(date +%F)/backup_label_info.txt

# 4. 记录当前的 WAL 位置,作为演练目标
psql -At -c "SELECT pg_current_wal_lsn();" | tee /mnt/backup/base_$(date +%F)/pitr_target.txt

# 5. 校验归档是否覆盖到了目标位置(归档目录里应存在足以重放到目标的 WAL)
psql -c "SELECT last_archived_wal, last_archived_time FROM pg_stat_archiver;"

PITR 能成立的前提是:基础备份时刻之后产生的所有 WAL 段都已成功归档。因此演练时必须核对 pitr_target.txt 中的 LSN 所对应的 WAL 段确实存在于归档目录中——这正是 pg_stat_archiver 存在的意义。

注意事项 ​

  • archive_mode 需要重启才生效,archive_command / archive_library / archive_timeout 只需 reload。
  • 不要同时设置 archive_command 与 archive_library,二者互斥,同时设置会导致启动或运行时报错。
  • archive_command 返回非 0 即视为失败,即使文件实际已经复制成功。命令的退出码语义必须严格正确。
  • 归档目标绝不能与 pg_wal 或 PGDATA 在同一块磁盘——否则磁盘损坏会同时丢失数据与归档,归档失去意义。
  • archive_timeout 过大(如 1h)会让 PITR 恢复点粒度变粗,最大可丢失该时长内的数据。
  • 统计视图的累计值不是永久可靠的:实例崩溃重启后部分统计可能清零,stats_reset 只能标识「本次统计的起点」。
  • pg_stat_archiver 只有一行,且不受 track_counts 控制——即使 track_counts = off,归档统计仍然可用。
  • 不要在 archive_command 中做耗时操作(如压缩超大文件、调用远端 API 且无超时),会拖慢归档流水线。
ini
# postgresql.conf —— 不推荐:归档到与自己同一块盘
# archive_command = 'cp %p /var/lib/postgresql/18/main/wal_archive/%f'   # 不推荐:磁盘损坏即全丢

# postgresql.conf —— 不推荐:命令非幂等,重试会失败
# archive_command = 'mv %p /mnt/wal_archive/%f'   # 不推荐:重试时源文件已不存在,返回非 0

# postgresql.conf —— 不推荐:archive_timeout 过长
# archive_timeout = 1h   # 不推荐:PITR 粒度最粗达 1 小时,RPO 不可接受
sql
-- 不推荐:把归档失败的临时处置做成常态
-- SELECT pg_stat_reset_shared('archiver');   -- 不推荐:重置统计会掩盖故障痕迹,不解决归档问题

最佳实践 ​

  • 先做归档健康基线:部署完成后用 pg_switch_wal() + pg_stat_archiver 立即验证链路打通。
  • archive_command 必须幂等:用 test ! -f ... && cp ... 或「临时文件 + 原子重命名」的写法。
  • 每个实例使用独立归档子目录:避免多实例归档文件名冲突。
  • 设置合理的 archive_timeout:推荐 60s~300s,兼顾 WAL 段切换及时性与归档额外开销。
  • 归档目标独立于数据库磁盘:优先对象存储或独立的网络存储,并确认写入是真正持久化的。
  • 监控两项指标:failed_count 的增量、now() - last_archived_time。把它们接入告警。
  • 同时监控 pg_wal 目录大小:这是归档故障的「体温计」,是故障的第二道防线。
  • 定期做 PITR 演练:只归档不演练等于没做备份,演练才能验证归档链是否真正可用。
  • 区分「归档统计」与「表统计」的重置方式:前者用 pg_stat_reset_shared('archiver'),后者用 pg_stat_reset()。
  • PG 15+ 可考虑 archive_library:在归档频率高、希望减少 fork 开销的场景下收益明显。
  • 统计口径要带 stats_reset 一起看:脱离起点的累计值无法用于容量规划。

下一节

继续学习:Table Files(表文件)

参考链接 ​