跳至内容
27.3 WAL、检查点与写入平滑

27.3 WAL、检查点与写入平滑

写事务的磁盘路径不是“把 table row 写到文件后提交”。

近似链路:

change shared buffer
  -> generate WAL record
      -> insert into WAL buffer
          -> write/flush WAL according to commit policy
              -> acknowledge commit
                  -> data page later written by backend/bgwriter/checkpoint

WAL 让 data page 可以延后写,却把 commit latency、checkpoint、archive、replication 和 crash recovery 连接成一个系统。调其中一项,必须看整条链。

27.3.1 WAL 生成、刷盘与提交延迟

write、flush 与 acknowledge

需要区分:

WAL generated
WAL copied/written to kernel
WAL flushed to durable storage
WAL sent to standby
WAL written/flushed/replayed on standby
client receives success

不同 synchronous_commit level 改变 acknowledge 等待的阶段。它不是“开/关性能”:

  • on:本地 durable,并遵守 synchronous standby 配置;
  • remote_apply:还等待 synchronous standby replay;
  • remote_write:等待 standby 写入 OS;
  • local:等待本地 flush,不等待 synchronous standby;
  • off:允许本地 WAL 尚未 flush 就向 client 确认。

具体 durability 还受 synchronous_standby_namessynchronous_standby_slots、 standby availability 和 application route 影响。不要看到 on 就自动推导 multi-node zero-loss,也不要看到 off 就说“数据会损坏”;它改变的是 crash 时最近 成功确认 transaction 的 loss window。

fsyncfull_page_writes 不是普通 candidate

fsync=off
full_page_writes=off

可能显著提高 benchmark TPS,但会改变 crash safety。PostgreSQL 官方 WAL settings 明确说明,在可能发生 OS/硬件 crash 的系统中关闭相关保护可能导致不可恢复或静默 损坏。

教学 benchmark 若关闭它们,测的是另一个产品合同。

group commit

多个 backend 可以共享一次 flush:

backend A commits
backend B commits nearby
  -> one WAL flush may make both durable

因此:

  • 单 client commit latency 不代表多 client;
  • TPS 与 fsync count 不一定 1:1;
  • storage fsync tail 比平均 throughput 更重要;
  • batch/transaction boundary 改变 group opportunity。

commit_delay/commit_siblings 可以人为等待以扩大 group,但等待本身增加 latency, 只有在高并发、flush 昂贵且实验支持时才考虑。默认不是越大越好。

测 WAL composition

累计层:

SELECT
    wal_records,
    wal_fpi,
    wal_bytes,
    wal_buffers_full,
    stats_reset
FROM pg_stat_wal;

PostgreSQL 18 把 WAL I/O bytes/timing 纳入 pg_stat_io;不要照抄旧版本 pg_stat_wal.wal_write_time/wal_sync_time 查询:

SELECT
    backend_type,
    object,
    context,
    reads,
    read_bytes,
    writes,
    write_bytes,
    fsyncs,
    write_time,
    fsync_time
FROM pg_stat_io
WHERE object = 'wal'
ORDER BY backend_type, context;

列与可用 timing 取决于 PostgreSQL major version 与 track_wal_io_timing。升级时先查 system view schema。

statement 层:

SELECT
    queryid,
    calls,
    wal_records,
    wal_fpi,
    wal_bytes
FROM pg_stat_statements
ORDER BY wal_bytes DESC
LIMIT 20;

不要公开 query text;用 queryid 对回内部 catalog。

分母要有业务语义

WAL bytes / mixed transaction
WAL bytes / write transaction
WAL bytes / order
WAL bytes / changed row

不是同一个指标。

第 26 章 mixed workload 只有 20% place-order,约 158–196 WAL bytes/mixed tx。 不能把它写成“每个订单 196 bytes”。需要按成功 write operation 归一,并把 background activity 与 full-page image phase 作为误差。

WAL buffer full

wal_buffers_full 增加表示 WAL buffer 空间不足时 backend 被迫写 WAL,但不能只看到 counter 就把 wal_buffers 手工调大:

  • 默认 -1 会基于 shared buffers 自动选择;
  • upper bound 通常一个 WAL segment;
  • 写入/flush device 可能才是限制;
  • checkpoint/FPI 或大 transaction 可能改变 burst;
  • counter 是累计 delta。

先对齐发生时间与 WAL generation/write/fsync。

commit latency 的证据链

client transaction latency
  -> server wait event
      -> WAL I/O timing/count
          -> device fsync latency/queue
              -> synchronous standby wait
                  -> network/replay

如果 backend 等 SyncRep,加快本地 WAL device 未必改善;如果 server CPU 已饱和, 启用更重 WAL compression 可能反而恶化。

27.3.2 检查点频率、写突发与恢复时间

checkpoint 做什么

checkpoint 建立 recovery 起点,并把需要的 dirty buffer 写出,使 crash recovery 不必从无限久之前 replay WAL。它不是“定期 fsync 一次”这么简单。

触发:

time: checkpoint_timeout
WAL volume: max_wal_size soft threshold
manual/requested: CHECKPOINT or operational action
shutdown/recovery transitions

PostgreSQL 18 的证据:

SELECT *
FROM pg_stat_checkpointer;

SELECT *
FROM pg_stat_bgwriter;

checkpointer 与 bgwriter 已分开统计。关注:

num_timed / num_requested / num_done
write_time / sync_time
buffers_written
backend writes/fsync
checkpoint warnings/log duration

不要用旧版本列名硬编码跨版本 dashboard。

max_wal_size 是 soft limit

它不是:

pg_wal hard cap
archive backlog cap
slot retention cap
disk safety guarantee

在 archive failure、replication slot、wal_keep_size、重负载等条件下,WAL 可超过 max_wal_size。把 disk provision 写成 max_wal_size + 10% 是错误模型。

太小的症状

requested/WAL-driven checkpoints frequent
checkpoint_warning
WAL FPI rate high
write/sync burst
backend writes increase
throughput/latency periodic sawtooth

因为每次 checkpoint 后,某页第一次修改需要 full-page image(在保护开启时),过于 频繁会增加 WAL。

太大的代价

增加 max_wal_size/checkpoint_timeout 可能:

  • 减少 checkpoint 频率;
  • 降低 checkpoint-induced FPI;
  • 让 dirty work 有更长时间平滑;
  • 增加 crash recovery 要 replay 的 WAL;
  • 增加 pg_wal working space;
  • 延后 dirty page write,扩大 failure/burst 状态;
  • 改变 archive/replica catch-up。

优化点不是“checkpoint 越少越好”,而是满足:

foreground SLO
write smoothness
crash RTO
WAL disk headroom
archive/replica behavior

checkpoint_completion_target

它控制 checkpoint 在 checkpoint interval 中用于完成的目标比例。默认较高是为了把 I/O 分散到大部分 interval。降低会让 checkpoint 更快完成:

higher instantaneous write rate
  -> then longer quiet period

通常不是想要的平滑。参考 Pigsty 沙箱为 0.95;改变它前要看 checkpoint progress、 device latency 与 SLO,而不是复制旧时代 0.7。

写入平滑不是平均值

画 time series:

WAL bytes/s
checkpoint begin/end
data write bytes/s
fsync latency
dirty buffers
backend writes
archive rate/gap
replica receive/replay gap
OLTP p95/p99

平均 20MB/s 可能是:

持续 20MB/s

或:

每 10 秒 200MB/s,其余为 0

设备和 tail latency 面对的是后者。

恢复时间要实测

粗略:

$$ T_{\text{recovery}} \approx \frac{\text{WAL to replay}} {\text{effective replay throughput}} + \text{startup/finalization} $$

replay throughput 受:

WAL record mix
CPU
data/WAL storage
full-page images
compression/decompression
extension
prefetch
recovery conflicts

影响。不能用 WAL device sequential bandwidth 代替。

checkpoint 调整 acceptance 应含 crash/restart 或 replica rebuild drill,而不是只跑 steady primary。

不要为 benchmark 强制 checkpoint

每次 run 前:

CHECKPOINT;

会人为同步 phase:

  • 一开始 full-page image 更多;
  • dirty state 被清空;
  • I/O burst 与 production natural phase 不同。

如果研究 checkpoint phase,应把 “immediately after checkpoint / mid-cycle” 作为显式 factor;否则让背景系统自然运行并记录 checkpoint。

27.3.3 压缩、全页写与归档代价

full-page image 的正确性作用

full_page_writes=on,checkpoint 后某页第一次被修改时,WAL 通常记录整页 image, 以防 torn page 让 recovery 无法还原。于是:

frequent checkpoint
  -> more first modifications
      -> more FPI
          -> more WAL

但关系受 working set 与 write locality 影响。修改同一小批 hot page 与随机修改巨大 dataset 的 FPI 比例不同。

wal_compression

PostgreSQL 18 支持的 method 取决于 build:

off
pglz
lz4
zstd

它主要压缩 full-page image,不是压缩所有 WAL record。tradeoff:

less WAL bytes
  vs
more primary compression CPU
more replay decompression CPU
different archive/network/storage load

参考沙箱:

wal_compression = lz4
source          = configuration file
context         = superuser

第 26 章 c8 已有 CPU pressure,却没有证明 WAL I/O 是 throughput limit。因此“开启更强 compression 减少 WAL”不符合当前 service center evidence。

compression 实验矩阵

至少比较:

维度指标
primaryTPS、p95、CPU、wal_bytes、wal_fpi
WAL devicewrite/fsync bytes/latency
network/archivebytes/s、backlog、catch-up
replicareplay CPU、lag、recovery
backup/PITRarchive compatibility、restore time
failurecrash replay、promotion

只看 wal_bytes 降低不能决定接受。

archive 与 slot 可能让 WAL 无界

WAL generated
  -> pg_wal recycle/remove eligibility
      depends on checkpoint
      + archive completion
      + replication consumers/slots
      + wal_keep_size
      + backup/recovery needs

若 archive throughput $A$ 小于 WAL generation $W$:

$$ \text{backlog growth}

W-A $$

增加 max_wal_size 只推迟 disk full,不修复 consumer。

若 slot inactive:

SELECT
    slot_name,
    slot_type,
    active,
    restart_lsn,
    confirmed_flush_lsn,
    wal_status,
    safe_wal_size
FROM pg_replication_slots;

列随版本变化,先查当前 view。还要配置/验证 max_slot_wal_keep_size 等 guard,但 guard 触发可能使 consumer 无法继续,属于 recovery/runbook 决策。

min_wal_size 与 recycle

min_wal_size 让一定量旧 WAL segment 被 recycle 供未来使用,减少 burst 时反复创建/ 删除。它不是最小 WAL generation,也不是 retention policy。

filesystem 类型可能影响:

wal_init_zero
wal_recycle

尤其 CoW filesystem。但这些是 storage-specific candidate,应以文件系统、allocation 行为和 crash test 证明,不能把云盘经验搬到本地 SSD。

归档压缩与 WAL compression 是两层

wal_compression
  compresses full-page images inside WAL records

archive/backup compression
  compresses WAL files/backup objects during transport/storage

二者可叠加,CPU 消耗发生在不同进程/节点/阶段。容量模型要测最终 archive object bytes 和两侧 CPU,不要把 primary wal_bytes ratio 当作备份压缩率。

参数 ADR

WAL/checkpoint 变更应写:

symptom:
  requested_checkpoints_per_hour: ...
  wal_fpi_ratio: ...
hypothesis:
  parameter: max_wal_size
  mechanism: reduce WAL-driven checkpoint frequency
baseline:
  checkpoint_interval: ...
  wal_bytes_s: ...
  crash_recovery_p95: ...
candidate:
  value: ...
benefit_gate:
  foreground_p95: ...
non_regression:
  pg_wal_peak: ...
  archive_gap: ...
  replica_replay: ...
  crash_recovery: ...
rollback:
  value/source: ...
  restart_or_reload: ...

没有 recovery/retention 证据的 checkpoint 优化,只完成了一半。


上一节:内存预算 · 返回本章目录 · 下一节:规划器、并行与连接参数 · 查看全书目录 · 查看索引中心

最后更新于