Skip to content

WAL Files(WAL 文件组织) ​

上一节讲了表的物理文件是「一组按 1GB 切分的文件」。WAL 也一样,但它切分的单位不是 1GB,而是 16MB,并且它的文件名不是随意的 OID,而是一串编码了时间线与 LSN 的十六进制。读懂这串文件名,就能从磁盘上直接推断出「WAL 写到哪了、有多少是老文件、能不能删」。本节讲解 WAL 文件的命名规则、生成与回收机制,以及如何用 pg_waldump 直接翻阅 WAL 内容。

前置知识

阅读本节前,建议先了解:Visibility Map(可见性映射)

基础概念 ​

WAL(Write-Ahead Log,预写式日志)在磁盘上的落点就是 PGDATA 下的 pg_wal 目录。理解这个目录,需要先统一以下术语:

术语英文说明
WALWrite-Ahead Log预写式日志,所有修改必须先写日志再写数据文件
WAL 段文件WAL Segment FileWAL 的物理文件,默认每个 16MB
段大小wal_segment_size段文件大小,只能在 initdb 时指定,默认 16MB
日志文件号Log File Number段文件名的中间 8 位,每 256 个段算一个"日志文件"
时间线Timeline段文件名最前 8 位,每次恢复结束会 +1,用于区分分支
段号Segment Number段文件名最后 8 位,在同一日志文件内的序号
回收Recycling不再需要的段文件被改名复用,而非删除
归档Archiving把写满的段文件复制到外部存储(备份用)
截断.partial未写满就被中断的段文件,恢复时会被截断到有效长度

pg_wal 目录里都有什么 ​

pg_wal 不只有段文件,还包含若干辅助文件与子目录:

text
$PGDATA/pg_wal/
├── 000000010000000000000001           # WAL 段文件(16MB)
├── 000000010000000000000002           # WAL 段文件(16MB)
├── 00000001000000000000000A.history   # 时间线历史文件(发生过恢复才有)
├── 00000001000000000000000A.00000028.backup  # 基础备份标签文件
├── 00000001000000000000000B.partial   # 未写满的段文件(崩溃/关机时产生)
└── archive_status/                    # 归档状态标记目录
    ├── 000000010000000000000001.done  # 已成功归档
    └── 000000010000000000000002.ready # 等待归档

各文件类型的职责:

文件/目录命名特征作用
段文件24 位十六进制真正存放 WAL 记录,16MB 一个
.history<文件名>.history记录时间线切换历史,恢复时用来选择分支
.backup<文件名>.<LSN 高位>.backup基础备份(pg_basebackup)的元信息标签
.partial以 .partial 结尾崩溃或快速关机留下的未完整段文件
archive_status/内含 .ready / .done归档进度标记,.done 后文件才可被回收

段文件名的三段结构 ​

24 位十六进制文件名由三段各 8 位组成,这是 WAL 文件组织最核心的规则:

text
00000001 00000000 00000001
└──┬───┘ └──┬───┘ └──┬───┘
   │        │        └── 段号(Segment Number)
   │        │            同一日志文件内的序号,16MB 段下范围 0x00 ~ 0xFF
   │        │
   │        └─────────── 日志文件号(Log File Number)
   │                     LSN 的高 32 位,等价于「第几个 4GB 区间」
   │
   └──────────────────── 时间线(Timeline ID)
                        初始为 1,每次基于备份恢复并提升后 +1

把 LSN 换算成文件名的公式如下(以默认 16MB 段为例):

text
给定 LSN = 高32位/低32位(如 0/1600000)

日志文件号 = LSN 高 32 位                    → 0x00000000
段号       = LSN 低 32 位 ÷ wal_segment_size → 0x1600000 ÷ 0x1000000 = 1
文件名     = 时间线 + 日志文件号 + 段号
           = 00000001 00000000 00000001

反推:段文件内的字节偏移 = LSN 低 32 位 mod wal_segment_size
      = 0x1600000 mod 0x1000000 = 0x600000 = 6291456

为什么是「256 个段一个日志文件」

16MB × 256 = 4GB = 2³² 字节,正好是一个 32 位无符号整数能表示的范围。因此「日志文件号」就等于 LSN 的高 32 位,「段号」就是低 32 位除以段大小。文件名的设计完全是 LSN 位域的直接映射,不需要额外查表。

核心机制 ​

WAL 段的生成与切换 ​

WAL 是顺序追加写的。当写入位置走到当前段文件末尾时,就要切换到下一个段文件:

text
事务产生 WAL 记录
        │
        ▼
写入当前段文件的写入位置(write pos)
        │
        ├─ 未到段尾(16MB)→ 继续追加 ✔
        │
        ▼ 已到段尾
  切换段文件(switch)
        │
        ├─ 有可回收的旧段文件 → 改名复用(recycle),避免创建新文件
        ├─ 无可用旧文件 → 新建一个 16MB 文件
        │
        ▼
  写入位置进入新段文件,日志文件号/段号按上述公式递增

切换可以由两种情况触发:

  • 自然写满:写入位置到达段尾。
  • 主动切换:执行 pg_switch_wal(),或归档场景下 archive_timeout 到期。
sql
-- 查看当前写入到哪个段文件、偏移多少
SELECT pg_current_wal_lsn() AS current_lsn,
       pg_walfile_name(pg_current_wal_lsn()) AS current_file,
       pg_walfile_name_offset(pg_current_wal_lsn()) AS file_and_offset;
text
    current_lsn     |        current_file        |           file_and_offset
--------------------+----------------------------+--------------------------------------
 0/1600000          | 000000010000000000000001   | (000000010000000000000001,6291456)
  • current_lsn 是当前写入位点,格式为 高32位/低32位。
  • current_file 就是当前活跃的段文件名,格式规则如上推导。
  • file_and_offset 同时给出文件名与段内字节偏移,用于精确定位。

WAL 文件的生命周期 ​

一个段文件从创建到被回收,会经历几个明确状态:

text
新建 / 复用
    │
    ▼
① 活跃(current)      ← 正在被写入,绝对不能动
    │  写满后切走
    ▼
② 待归档(ready)      ← archive_status 下有 .ready,等待 archive_command 处理
    │  归档成功后改名为 .done
    ▼
③ 可回收(recycled)   ← 检查点已确认不需要,且已归档 → 改名为未来的段号复用
    │  或
    ▼
④ 被删除(removed)    ← 若 wal_recycle 关闭,则直接 unlink

删除或改名发生在检查点之后,这是关键:检查点会记录 redo 起点,redo 起点之前的所有 WAL 都不再需要(除非还要归档或还有复制槽需要)。

回收的两个门槛 ​

一个段文件能不能被回收,必须同时满足:

门槛条件由谁保证
不再需要重放该段在最近一次检查点的 redo 起点之前检查点进程(checkpointer)
归档已完成archive_status 中已是 .done(未开归档则无此要求)归档进程(archiver)
不再被复制槽绑定pg_replication_slots.restart_lsn 已越过该段WAL 发送进程(walsender)

三者只要有一个不满足,段文件就会持续堆积在 pg_wal。这也是「pg_wal 目录莫名涨到几十 GB」最常见的三个原因。

保留多少:max_wal_size 与 min_wal_size ​

max_wal_size 与 min_wal_size 控制的是给 WAL 预留的磁盘量,不是「WAL 的硬上限」:

text
min_wal_size(默认 80MB)
    · WAL 目录的"下限水位":即使很闲,也保留这么多段文件供复用
    · 避免突发写入时临时创建文件带来抖动

max_wal_size(默认 1GB)
    · 检查点的"触发参考":两个检查点之间允许累积的 WAL 量
    · 超过它会更频繁地触发检查点,从而让文件尽快进入可回收状态
    · 注意:它不阻止 WAL 继续增长,只影响检查点节奏
sql
-- 查看当前 WAL 相关配置
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN ('wal_segment_size', 'min_wal_size', 'max_wal_size',
               'wal_keep_size', 'wal_level', 'archive_mode')
ORDER BY name;
text
       name        | setting | unit |                              short_desc
-------------------+---------+------+----------------------------------------------------------------------
 archive_mode      | off     |      | Allows archiving of WAL files using archive_command.
 max_wal_size      | 1024    | MB   | Sets the WAL size that triggers a checkpoint.
 min_wal_size      | 80      | MB   | Sets the minimum size to shrink the WAL to.
 wal_keep_size     | 0       | MB   | Sets the size of WAL files held for standby servers.
 wal_level         | replica |      | Sets the level of information written to the WAL.
 wal_segment_size  | 16      | MB   | Shows the size of WAL segments.

WAL 目录的实时状态 ​

不必登到服务器上 ls,SQL 就能列出 pg_wal 内容:

sql
-- 列出 pg_wal 目录内容(需要超级用户或 pg_monitor 角色)
SELECT name, size, pg_size_pretty(size) AS pretty_size, modification
FROM pg_ls_waldir()
ORDER BY name
LIMIT 8;
text
        name                |   size   | pretty_size |      modification
----------------------------+----------+-------------+------------------------
 000000010000000000000001   | 16777216 | 16 MB       | 2026-09-20 09:12:33+08
 000000010000000000000002   | 16777216 | 16 MB       | 2026-09-20 09:41:07+08
 000000010000000000000003   | 16777216 | 16 MB       | 2026-09-20 10:05:19+08
 000000010000000000000004   | 16777216 | 16 MB       | 2026-09-20 10:33:51+08
 000000010000000000000005   | 16777216 | 16 MB       | 2026-09-20 11:02:44+08
 000000010000000000000006   | 16777216 | 16 MB       | 2026-09-20 11:20:02+08
 000000010000000000000007   | 16777216 | 16 MB       | 2026-09-20 11:47:18+08
 000000010000000000000008   | 16777216 | 16 MB       | 2026-09-20 12:01:05+08

配合聚合函数可以直接算出 WAL 目录总占用:

sql
-- WAL 目录占用总量与文件个数
SELECT count(*) AS file_count,
       pg_size_pretty(sum(size)) AS total_wal_size
FROM pg_ls_waldir();
text
 file_count | total_wal_size
------------+----------------
         64 | 1024 MB

PG14 起还提供了 pg_stat_wal 视图,用于观察 WAL 的生成速率:

sql
-- 查看 WAL 生成统计(PG14+)
SELECT wal_records, wal_fpi, wal_bytes, pg_size_pretty(wal_bytes) AS wal_bytes_pretty,
       wal_buffers_full, stats_reset
FROM pg_stat_wal;
text
 wal_records | wal_fpi | wal_bytes | wal_bytes_pretty | wal_buffers_full |          stats_reset
-------------+---------+-----------+------------------+------------------+-------------------------------
      128430 |     912 |  19874305 | 19 MB            |               37 | 2026-09-20 09:00:00.124511+08
  • wal_records 是 WAL 记录条数,wal_fpi 是全页写入(Full Page Image)次数。
  • wal_bytes 是 WAL 总字节数,这是评估写入压力的第一手数据。
  • wal_buffers_full 表示 WAL 缓冲区被写满而被迫刷盘的次数,偏大说明 wal_buffers 偏小。

相关参数 ​

参数默认值生效方式说明
wal_segment_size16MB仅 initdb段文件大小,建库后不可改
min_wal_size80MBreloadWAL 目录保留的下限水位
max_wal_size1GBreload触发检查点的 WAL 累积量参考值
wal_keep_size0reload为备库额外保留的 WAL 量(PG13+,取代 wal_keep_segments)
wal_levelreplica重启写入 WAL 的信息量:minimal / replica / logical
wal_buffers-1(自动)重启WAL 共享缓冲区大小,自动时约为 shared_buffers/32,上限 16MB
wal_compressionoffreloadWAL 全页镜像压缩:pglz / lz4 / zstd(PG15+ 支持后两者)
wal_init_zeroonreload新段文件是否预填零(on 更安全,off 更快)
wal_recycleonreload是否复用旧段文件而非删除重建
archive_modeoff重启是否开启 WAL 归档:off / on / always
archive_command空reload归档命令,如 cp %p /backup/wal/%f
archive_library空reload用共享库替代 archive_command(PG13+)
archive_timeout0reload强制切换段文件的时间上限,保证归档时效性
ini
# postgresql.conf
# WAL 目录容量与检查点节奏
min_wal_size = 256MB            # 提高下限,减少突发写入时创建文件的抖动
max_wal_size = 4GB              # 写入量大的库适当放大,降低检查点频率

# WAL 缓冲区:留 -1 让系统自动按 shared_buffers 推导
wal_buffers = -1

# WAL 压缩:PG15+ 可选 lz4 或 zstd,省磁盘也省网络
wal_compression = lz4

# 归档:archive_command 用 cp 时,路径必须已存在且 pg 用户可写
archive_mode = on
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'
archive_timeout = 300s          # 5 分钟至少切一个段,避免低写入时归档延迟

详细说明 ​

用 pg_waldump 直接阅读 WAL ​

pg_waldump 是官方提供的 WAL 解析工具,能把二进制 WAL 记录翻译成人可读的一行行文本,是排查「某个时刻到底改了什么」的终极手段:

bash
# 查看当前活跃段文件的前 10 条记录
pg_waldump -n 10 $PGDATA/pg_wal/000000010000000000000001
text
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 0/01600028, prev 0/01600000, desc: RUNNING_XACTS nextXid 745 latestCompletedXid 744 oldestRunningXid 745
rmgr: Heap        len (rec/tot):     78/    78, tx:        745, lsn: 0/01600080, prev 0/01600028, desc: INSERT off 2 flags 0x00, blkref #0: rel 1663/16384/16385 blk 0
rmgr: Btree       len (rec/tot):     72/    72, tx:        745, lsn: 0/016000D0, prev 0/01600080, desc: INSERT_LEAF off 2, blkref #0: rel 1663/16384/16386 blk 1
rmgr: Heap        len (rec/tot):     78/    78, tx:        745, lsn: 0/01600118, prev 0/016000D0, desc: INSERT off 3 flags 0x00, blkref #0: rel 1663/16384/16385 blk 0
rmgr: Transaction len (rec/tot):     34/    34, tx:        745, lsn: 0/01600168, prev 0/01600118, desc: COMMIT 2026-09-20 09:12:33.184520 CST

每一行的关键字段含义:

字段含义
rmgr资源管理器(Resource Manager),如 Heap(堆表)、Btree(B 树索引)、XLOG
len (rec/tot)记录本身的长度 / 含全页镜像后的总长度
tx产生该记录的事务 ID
lsn该记录自身的 LSN,即其结束位置
prev同事务上一条记录的 LSN,可用来串联同一事务的记录链
desc记录描述,如 INSERT off 2 表示在页内第 2 个槽位插入
blkref被修改的数据块,格式 rel 表空间OID/数据库OID/关系OID blk 页号

blkref 的 rel 1663/16384/16385 与表文件的路径完全对应:1663 是 pg_default 表空间 OID,16384 是数据库目录名,16385 就是 relfilenode:

bash
# 由 blkref 反查实际文件路径
ls -lh $PGDATA/base/16384/16385

常用选项:

bash
# 只看 Heap(堆表)相关的记录
pg_waldump --rmgr=Heap -n 20 $PGDATA/pg_wal/000000010000000000000001

# 按 LSN 区间过滤(查看某次操作产生的记录)
pg_waldump --start='0/1600028' --end='0/1600168' $PGDATA/pg_wal/000000010000000000000001

# 统计各资源管理器的记录条数与占比
pg_waldump --stats $PGDATA/pg_wal/000000010000000000000001
text
Type        N        (%)       Record size        (%)      FPIs  (%)   ...
Heap      4820     62.11         456320       58.42      120  42.1
Btree     1980     25.51         214560       27.47       88  30.9
Transaction 640     8.25          21760        2.78        0   0.0
Standby    320      4.13           9600        1.23        0   0.0

归档进度的观察 ​

归档是否跟上,直接看 pg_stat_archiver:

sql
-- 观察归档进程状态(含失败重试与积压判断)
SELECT archived_count,
       failed_count,
       pg_walfile_name(last_archived_wal) AS last_archived,
       pg_walfile_name(last_failed_wal)   AS last_failed,
       last_archived_time,
       stats_reset
FROM pg_stat_archiver;
text
 archived_count | failed_count |        last_archived        | last_failed |   last_archived_time
----------------+--------------+-----------------------------+-------------+------------------------
            186 |            2 | 0000000100000000000000BA    | 0000000100.. | 2026-09-20 12:01:05+08

若 failed_count 持续增长,段文件就会卡在 .ready 状态无法回收,pg_wal 会持续膨胀:

sql
-- 还有多少段文件在等待归档
SELECT count(*) AS pending_archive
FROM pg_ls_dir('pg_wal/archive_status') AS f
WHERE f LIKE '%.ready';
text
 pending_archive
-----------------
             412

WAL 与数据文件的写入顺序 ​

WAL 的顺序写与数据文件的随机写是两套独立 I/O,这正是 PostgreSQL 依靠 WAL 获得性能的来源:

text
事务提交
   │
   ① 先在 WAL 缓冲区生成 COMMIT 记录
   │
   ② 提交时把 WAL 缓冲区 fsync 到 pg_wal(顺序写)
   │      └─ 只要这一步成功,事务就算持久化 ✔
   │
   ③ 数据页仍在 shared_buffers 里变"脏"(尚未落盘)
   │
   ④ 由 bgwriter / checkpointer 在稍后用随机写刷回数据文件
          └─ 即使此刻断电,重启后也能用 WAL 重放恢复

所以「pg_wal 里的段文件比数据文件新」是正常且必须的,绝不会出现数据比日志新的情况。

实战示例 ​

示例 1:验证 LSN 与段文件名的换算关系 ​

创建测试表并触发写入,然后对比 LSN 与文件名:

sql
-- 1) 建表并插入数据,产生 WAL
CREATE TABLE wal_demo (
  id    bigserial PRIMARY KEY,
  note  text
);

INSERT INTO wal_demo (note)
SELECT 'wal record ' || g
FROM generate_series(1, 5000) AS g;

-- 2) 看当前写入位点与对应文件
SELECT pg_current_wal_lsn() AS cur_lsn,
       pg_walfile_name(pg_current_wal_lsn()) AS cur_file;

-- 3) 用 pg_wal_lsn_diff 观察这次写入产生了多少 WAL 字节
SELECT pg_size_pretty(
         pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0')::bigint
       ) AS wal_since_start;

-- 4) 列出 WAL 目录,观察文件是否增加
SELECT count(*) AS file_count, pg_size_pretty(sum(size)) AS total
FROM pg_ls_waldir();
text
 cur_lsn   |           cur_file           | wal_since_start | file_count | total
-----------+------------------------------+-----------------+------------+---------
 0/1A2B4C8 | 000000010000000000000001      | 8192 kB         |         12 | 192 MB

结果解读:0/1A2B4C8 的高位是 0,低位 0x1A2B4C8 除以 16MB(0x1000000)得 1,所以落在段号 00000001 的文件里;这不代表只用了 1 个段,file_count = 12 说明已有 12 个段文件(含复用保留的)。注意 cur_file 会随写入增长而变化,一旦低位超过 0xFFFFFF 就切到 00000002。

示例 2:追踪一次更新到底写了什么 ​

从 WAL 层面看清一次 UPDATE 的完整代价:

sql
-- 1) 记录起点 LSN
SELECT pg_current_wal_lsn() AS start_lsn;

-- 2) 执行一次更新
UPDATE wal_demo SET note = note || ' [updated]' WHERE id = 42;

-- 3) 记录终点 LSN,并算出这次更新产生了多少 WAL
SELECT pg_current_wal_lsn() AS end_lsn,
       pg_size_pretty(
         pg_wal_lsn_diff(pg_current_wal_lsn(), '0/1A2B000')
       ) AS wal_generated;

更实用的做法是拿到区间后交给 pg_waldump 精确过滤:

bash
# 先记录起点与终点 LSN,再用区间过滤
# 假设起点 0/1A2B000,终点 0/1A2B4C8
pg_waldump --start='0/1A2B000' --end='0/1A2B4C8' \
  $PGDATA/pg_wal/000000010000000000000001
text
rmgr: Heap        len (rec/tot):     78/    78, tx:        812, lsn: 0/1A2B050, prev 0/1A2B000, desc: HOT_UPDATE off 20 xid 812 flags 0x20 ; new off 35 xid 812 flags 0x00
rmgr: Heap        len (rec/tot):     78/    78, tx:        812, lsn: 0/1A2B0A8, prev 0/1A2B050, desc: HOT_UPDATE off 21 xid 812 flags 0x20 ; new off 36 xid 812 flags 0x00
rmgr: Transaction len (rec/tot):     34/    34, tx:        812, lsn: 0/1A2B4C8, prev 0/1A2B0A8, desc: COMMIT 2026-09-20 12:20:11.302914 CST

结果解读:

  • 看到 HOT_UPDATE 说明这次更新走了 HOT(Heap-Only Tuple) 优化——更新后的新版本被放到同页,索引无需改动,代价显著降低。
  • 每条 HOT_UPDATE 都带 new off 35 指向新版本槽位,说明行版本是「新槽位 + 旧槽位标记过期」的方式实现的。
  • 只有一条 COMMIT 记录,说明整条 UPDATE 语句是一个事务,WAL 写入具备原子性。
  • 如果 note 列有索引改动,这里会出现 Btree 的 INSERT_LEAF / DELETE 记录,WAL 量会显著增加。

示例 3:制造并观察段文件切换 ​

用 pg_switch_wal() 主动切段,观察文件与归档状态:

sql
-- 1) 切段前的 LSN 与文件
SELECT pg_current_wal_lsn() AS before_lsn,
       pg_walfile_name(pg_current_wal_lsn()) AS before_file;

-- 2) 主动切段(该动作本身也会写 WAL)
SELECT pg_switch_wal() AS switch_at;

-- 3) 切段后的 LSN 与文件
SELECT pg_current_wal_lsn() AS after_lsn,
       pg_walfile_name(pg_current_wal_lsn()) AS after_file;

-- 4) 确认已归档的段文件数量
SELECT archived_count, failed_count FROM pg_stat_archiver;
text
 before_lsn |          before_file          | switch_at | after_lsn  |           after_file
------------+-------------------------------+-----------+------------+-------------------------------
 0/1A2B4C8  | 000000010000000000000001      | 0/1A2B4E0 | 0/1A2B4E0  | 000000010000000000000002

结果解读:

  • pg_switch_wal() 返回的 switch_at 就是新段的起始 LSN,紧接在原 LSN 之后。
  • after_file 的段号从 01 变为 02,说明新写入会落到新段文件。
  • 若 archive_mode = on,此时新出现的段文件会先在 archive_status 生成 .ready,归档成功后变为 .done,然后才可能被回收。
  • 频繁调用 pg_switch_wal() 会让 pg_wal 里出现大量只写了几字节的段文件,不要把它当作日常操作。

注意事项 ​

  • wal_segment_size 建库后无法修改,只能通过 initdb --wal-segsize 指定。事后想改必须重建实例或重建备库。
  • 不要手工删除 pg_wal 里的文件,哪怕磁盘告急。删掉正在使用或尚未归档的段文件会导致实例无法启动。
bash
# 错误!会破坏崩溃恢复能力,直接导致实例无法启动
# rm -f $PGDATA/pg_wal/000000010000000000000001

# 正确:先查清是谁占着 WAL,再对症处理
sql
-- 查清 WAL 膨胀的原因:活跃复制槽(最常见的元凶)
SELECT slot_name, slot_type, active, restart_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots
ORDER BY restart_lsn;
  • max_wal_size 不是硬上限,它只影响检查点频率。一个长事务、一个不推进的复制槽、一个归档失败的 .ready 文件,都能让 pg_wal 远超 max_wal_size。
  • wal_level = minimal 会跳过部分 WAL 写入,性能最好,但此时无法做时间点恢复(PITR),也不支持复制。生产库不要用。
  • archive_command 必须保证幂等且可靠。归档失败会让段文件持续堆积;归档成功但返回了非零退出码,则会导致重复归档与磁盘浪费。
  • .partial 文件不必惊慌,它会在恢复时被自动截断到有效长度(wal_init_zero = on 时还会被预填零)。
  • wal_recycle = off 会不断删除旧文件并创建新文件,在某些文件系统上反而更快,但会失去复用带来的元数据开销节省。

最佳实践 ​

  • 把 pg_wal 目录监控纳入基础告警:文件数或总大小持续增长,一定是复制槽、归档或长事务出了问题。
  • 为 max_wal_size 预留足够的磁盘余量:按 max_wal_size 的 2~3 倍评估 pg_wal 可能达到的峰值空间。
  • 写入量大的库适当放大 max_wal_size(如 4~8GB),检查点更少、性能更稳;对小库保持默认即可。
  • 开启 wal_compression(推荐 lz4):能显著减少 WAL 量,对全页写入(FPI)尤其有效。
  • 生产库始终用 wal_level = replica 或更高,不要为了省一点 I/O 牺牲可恢复性。
  • 复制槽用完立刻删除,这是 pg_wal 失控最典型的根因。
  • 归档目录与 pg_wal 放在不同磁盘,避免两套顺序写互相争抢 I/O。
  • 用 pg_waldump 解决争议:当有人问「这条数据到底是什么时候被改的」,WAL 是唯一可信的账本。
  • 定期校验归档完整性(pg_verifybackup / pg_waldump 抽查),归档断了却以为没事,是恢复时才发现的最惨场景。
  • pg_switch_wal() 只在需要立即归档时使用(如备份前的切换、低写入库配合 archive_timeout),不要轮询调用。

下一节

继续学习:Tablespace(表空间)

参考链接 ​