PostgreSQL 的后台写进程(background writer)是缓冲池与磁盘之间的"节奏控制器"。它不替你做 checkpoint,也不替你做 fsync——它的职责是在 checkpoint 间隙,以可控的节奏把脏页推到操作系统写回队列,避免突发 I/O 把存储压垮。控制这个节奏的核心参数有四个 GUC,本文先拆前两个:bgwriter_delay 定义了进程的"心跳间隔",bgwriter_flush_after 定义了每次心跳"攒多少再刷"。
bgwriter_delay:心跳频率,不是越快越好
bgwriter_delay 的默认值是 200ms。后台写进程每 200ms 醒一次,扫描缓冲池,把一定数量的脏页标记为可写回,然后继续睡眠。
关键认知:这个进程不是"多干快干"的工人。它醒得太频繁,反而浪费 CPU 周期——每次醒来都要遍历缓冲池元数据、争抢 lwlock。醒得太慢,脏页堆积,checkpoint 来临时会触发大量同步写,造成延迟尖峰。
实际调优时,200ms 对大多数 OLTP 场景已经够用。如果你观察到:
- checkpoint 期间 IOWait 骤升、查询延迟抖动明显;
pg_stat_bgwriter中buffers_checkpoint占比远高于buffers_clean;
这说明后台写进程干活太少,脏页都堆到 checkpoint 一次性冲刷了。这时可以考虑缩短 bgwriter_delay,比如降到 100ms 或 50ms,让脏页更均匀地流出。
反过来,如果系统 I/O 本身就很轻、缓冲池命中率极高,把 bgwriter_delay 拉到 500ms 甚至 1s 可以减少无意义的唤醒。
bgwriter_flush_after:攒够再交,减少碎片写
bgwriter_flush_after 的默认值是 512KB(即 64 个 8KB 页)。后台写进程把脏页标记给操作系统后,操作系统并不立刻写到磁盘——它把页挂到自己的写回队列里,攒够一批再发下去。bgwriter_flush_after 就是告诉内核:"我攒到这个量了,你可以开始写回了。"
这个参数和 backend_flush_after 是同一思路:后端进程在检查缓冲池时,如果自己产生的脏页累积超过 backend_flush_after,也会触发一次写回提示。区别在于触发主体——一个是后台写进程,一个是执行查询的后端进程。
把 bgwriter_flush_after 设得太小(比如 128KB),意味着每次只攒 16 页就催内核写回,写请求碎片化,存储设备无法合并顺序写,吞吐下降。设得太大(比如 2MB 以上),脏页在内核写回队列里停留时间长,checkpoint 或紧急回收时可能被迫同步等待。
一个合理的起点是保持默认 512KB,然后根据存储特性微调:
- SSD / NVMe:可以适当放大到 1MB–2MB,因为随机写代价低,攒大批次反而能利用内部并行。
- HDD / 机械盘阵列:保持 512KB 或略缩到 256KB,避免大块随机写打乱顺序流。
实践:查看当前配置与运行状态
下面是一组可以直接在 psql 中运行的查询,帮你判断后台写进程是否在合理区间工作。
-- 1. 查看当前 bgwriter 相关参数
SELECT name, setting, unit, source
FROM pg_settings
WHERE name IN ('bgwriter_delay',
'bgwriter_flush_after',
'bgwriter_lru_maxpages',
'bgwriter_lru_multiplier');
-- 2. 查看 bgwriter 统计:buffers_clean vs buffers_checkpoint
SELECT buffers_clean,
buffers_checkpoint,
buffers_backend,
bgwriter_delay_ms -- 注意:此列在部分版本不存在,可从 pg_settings 取
FROM pg_stat_bgwriter;
-- 3. 计算脏页流出比例:后台写 vs checkpoint
SELECT
round(buffers_clean::numeric /
nullif(buffers_clean + buffers_checkpoint + buffers_backend, 0) * 100, 1)
AS clean_pct,
round(buffers_checkpoint::numeric /
nullif(buffers_clean + buffers_checkpoint + buffers_backend, 0) * 100, 1)
AS checkpoint_pct,
round(buffers_backend::numeric /
nullif(buffers_clean + buffers_checkpoint + buffers_backend, 0) * 100, 1)
AS backend_pct
FROM pg_stat_bgwriter;
解读参考:
clean_pct在 10%–30% 说明后台写进程分担了合理比例;接近 0% 则几乎没干活。checkpoint_pct过高(>70%)是脏页堆积的信号,需要缩短bgwriter_delay或增大bgwriter_lru_maxpages。backend_pct过高说明后端进程自己在刷脏页,查询延迟会受 I/O 影响。
动态调整与验证
这两个参数都是 SIGHUP 级别,可以在不重启数据库的情况下生效:
# 方式一:ALTER SYSTEM(推荐,9.4+)
psql -c "ALTER SYSTEM SET bgwriter_delay = '100ms';"
psql -c "ALTER SYSTEM SET bgwriter_flush_after = '1MB';"
psql -c "SELECT pg_reload_conf();"
# 方式二:直接改 postgresql.conf 后 reload
# 编辑 postgresql.conf
# bgwriter_delay = 100ms
# bgwriter_flush_after = 1MB
pg_ctl -D /var/lib/postgresql/data reload
# 验证生效
psql -c "SELECT name, setting FROM pg_settings
WHERE name IN ('bgwriter_delay','bgwriter_flush_after');"
调整后,观察 5–10 分钟的 pg_stat_bgwriter 变化趋势,确认 buffers_clean 占比上升、checkpoint 期间 IOWait 下降。
调优清单与边界提醒
| 场景 | bgwriter_delay | bgwriter_flush_after | 理由 |
|---|---|---|---|
| OLTP + SSD | 100ms | 1MB | 加频流出、攒批次利用 SSD 并行 |
| OLTP + HDD | 200ms | 512KB | 默认值已较合理,避免碎片写 |
| 大缓冲池(>64GB)+ 低写入 | 500ms | 2MB | 减少唤醒、攒大块减少写回次数 |
| 高写入压力 + 延迟抖动 | 50ms | 512KB | 尽快疏散脏页、checkpoint 来时少突击 |
边界提醒:
bgwriter_delay最小值是 10ms,再短意义不大,CPU 开销反而上升。bgwriter_flush_after设为 0 表示"不主动催内核写回",完全交给内核自身节奏——在内存充裕、I/O 压力低的场景可以尝试,但脏页回收时机变得不可控。- 调优不是孤立动作:
bgwriter_lru_maxpages和bgwriter_lru_multiplier(另外两个 GUC)控制每次心跳最多扫多少页,和bgwriter_delay联动。下一篇会拆解它们。
后台写进程的四个参数是一套联动系统,先理解 bgwriter_delay 的心跳机制和 bgwriter_flush_after 的攒批逻辑,后续调 lru_maxpages 和 lru_multiplier 时才不会盲目。