Background Writer(后台写入进程)
Background Writer(后台写入进程,简称 bgwriter)是 PostgreSQL 中的常驻辅助进程,它的唯一职责是提前把共享缓冲区中的脏页刷到磁盘,从而减轻后端进程在申请缓冲区时被迫同步刷脏的压力。本节将讲解 bgwriter 的定位、与检查点和后端刷脏的分工、关键参数,以及如何通过统计视图判断它是否工作良好。
前置知识
阅读本节前,建议先了解:Backend 后端进程(Backend Process)
基础概念
| 术语 | 英文 | 说明 |
|---|---|---|
| 共享缓冲区 | Shared Buffers | shared_buffers 指定大小的共享内存池,所有进程读写数据页的入口 |
| 脏页 | Dirty Page / Dirty Buffer | 内容已被修改但尚未写回磁盘的数据页(对应缓冲区头 BM_DIRTY 标记) |
| 后台写入进程 | Background Writer(bgwriter) | 常驻辅助进程,周期性扫描缓冲区并提前刷脏 |
| 后端刷脏 | Backend Flush | 后端进程找不到干净缓冲区时,自己把某个脏页同步写盘 |
| 检查点 | Checkpoint | 由 checkpointer 进程触发的全量刷脏 + 记录检查点,保证崩溃恢复起点 |
| 时钟扫描 | Clock Sweep | shared_buffers 的缓冲区替换算法,模拟 LRU 但不做全排序 |
| 使用计数 | Usage Count | 缓冲区头中的访问计数(0~5),时钟扫描据此判断「是否常被访问」 |
| 缓冲映射 | Buffer Mapping | 把 (relfilenode, fork, block) 映射到缓冲区槽位的哈希表 |
| 刷盘节流 | fsync Throttling / *_flush_after | 一次写多少数据后主动停顿,避免内核脏页堆积形成 I/O 尖峰 |
常见误区:bgwriter 不是为了「让数据落盘」
即使 bgwriter 完全不工作,数据也不会丢——真正的持久性由 WAL(预写日志) 保证,而不是由数据页刷盘保证。bgwriter 存在的唯一目的是性能:让脏页在后台被平滑地洗掉,避免后端进程在某个瞬间被迫做大量同步写,导致查询延迟尖刺。
换句话说:
- 数据页刷盘 是「为了腾出可复用缓冲区 / 缩短恢复时间」,可以延迟。
- WAL 刷盘 是「为了事务持久性」,提交时必须完成。
两者是完全独立的路径。
脏页的三种归宿
一个脏页最终被写到磁盘,可能经由三条完全不同的路径:
┌──────────────────────────────────────────────┐
│ Shared Buffers(共享缓冲区) │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 干净页 │ │ 脏页 │ │ 脏页 │ │ 干净页 │ │
│ └────────┘ └───┬────┘ └───┬────┘ └────────┘ │
└───────────────────┼──────────┼────────────────┘
│ │
① bgwriter 主动刷脏 │ │ ③ checkpointer 全量刷脏
(平滑、后台、无关请求) │ │ (检查点触发、批量、有节流)
▼ ▼
┌──────────────────────────┐
│ 磁盘数据文件 │
└──────────────────────────┘
▲
│
② backend 被迫刷脏:后端需要缓冲区但找不到干净页,
于是自己选一个脏页写盘,然后占用该槽位(同步阻塞该查询)- 路径 ①:bgwriter 周期唤醒,主动挑一批脏页写盘。它是纯收益——几乎不影响任何前台查询。
- 路径 ②:后端进程被迫自己刷脏。这是有代价的——该查询会被 I/O 阻塞。bgwriter 的全部价值就是把这条路径的占比压到最低。
- 路径 ③:checkpointer 在检查点时全量刷脏。它由
checkpoint_timeout/max_wal_size触发,属于周期性「大扫除」。
性能优化的目标可以一句话概括:让 ① 尽可能多、② 尽可能少、③ 尽可能平滑。
核心机制
缓冲区替换与「为什么需要提前刷脏」
PostgreSQL 的 shared_buffers 是一块固定大小的内存池,用时钟扫描(Clock Sweep) 算法复用缓冲区。当后端进程需要读入一个新页面时:
1. 在缓冲映射表中查找该页 → 命中则直接使用(buffer hit),结束
2. 未命中 → 需要从空闲链表或替换链表中取一个缓冲区
3. 取出候选缓冲区时检查:
- 引用计数 refcount > 0 → 被人正在用,跳过
- 标记为脏(BM_DIRTY) → 必须先写盘(路径 ②,后端被迫刷脏)
- 使用计数 usage_count > 0 → 说明最近被访问过,减 1 并跳过
4. 找到干净且 usage_count = 0 的缓冲区 → 直接复用(无 I/O 成本)关键点在第 3 步:如果候选页是脏的,当前这个后端就必须等它写完。这个等待直接叠加到用户查询的延迟上。
bgwriter 的作用就是在后端来找缓冲区之前,按 LRU 思路提前把这些脏页洗掉,让第 3 步能走第 4 步的快路径。
bgwriter 的工作循环
bgwriter 是一个无限循环的进程,核心逻辑可以简化成下面这段伪代码:
/* 概念性伪代码,非 PostgreSQL 源码原文 */
for (;;)
{
/* 1. 按 bgwriter_delay 休眠(默认 200ms) */
pg_usleep(bgwriter_delay);
/* 2. 从上次停下的位置继续扫描缓冲区,最多洗 bgwriter_lru_maxpages 页 */
for (i = 0; i < bgwriter_lru_maxpages; i++)
{
/* 3. 估算「下一次唤醒前,后端大约需要多少个缓冲区」 */
recent_alloc = 距上次唤醒期间被后端占用的缓冲区数;
next_expected = recent_alloc * bgwriter_lru_multiplier;
/* 4. 该页在时钟扫描中的「未来使用时间」可被安全推迟 → 无需刷 */
if (next_expected < 距该页被复用的天数)
break;
/* 5. 刷新一个脏页到磁盘 */
if (buffer_is_dirty(buf))
flush_buffer(buf);
}
/* 6. 更新统计:buffers_clean++ */
}上面的第 3、4 步是 bgwriter 的「智能」所在——它不是无脑地把所有脏页刷完,而是根据最近后端申请缓冲区的速度(recent_alloc)动态推算未来需求,只刷「反正马上就要被复用」的那部分脏页。这样既避免了后端被迫刷脏,又不会做无效 I/O(刷了马上又要改)。
关键参数
| 参数 | 默认值 | 作用 |
|---|---|---|
bgwriter_delay | 200ms | bgwriter 每轮之间的休眠时间。越短越平滑,但会占用更多 CPU 去扫描缓冲区 |
bgwriter_lru_maxpages | 100 | 每轮最多刷多少个脏页。设为 0 等于关闭 bgwriter |
bgwriter_lru_multiplier | 2.0 | 预测倍率:未来需求 = 最近分配量 × 该值。越大越激进(提前多刷),越小越保守 |
bgwriter_flush_after | 512kB(默认按 shared_buffers 推算,上限 2MB) | 累计写满该字节数后强制停顿,让内核把脏页写出去,避免 I/O 尖峰 |
# postgresql.conf
# 常规 OLTP 场景的温和加强版:更短的间隔 + 更多的每轮刷页数
bgwriter_delay = 200ms # 保持默认,200ms 已足够平滑
bgwriter_lru_maxpages = 200 # 从 100 提到 200,让每轮能洗更多脏页
bgwriter_lru_multiplier = 4.0 # 从 2.0 提到 4.0,更激进地提前刷脏
bgwriter_flush_after = 512kB # 保持默认;机械盘可考虑调小到 256kBbgwriter_lru_maxpages 是一个「安全阀」
设成很大的值(比如 1000)并不会让 bgwriter 疯狂刷盘——它仍然受 bgwriter_delay 与内部估算逻辑约束,只是「这一轮最多洗这么多」。真正决定刷不刷的是「是否马上就要被复用」。而设成 0 则是明确关闭,此时所有脏页都只能走后端被迫刷脏或检查点两条路径。
bgwriter_flush_after 与 SSD
该参数的设计目标是限制内核脏页队列长度。在带有电池保护写缓存(BBU)的 RAID 卡或企业级 SSD 上,内存中的写合并本身很快,此时过小的 *_flush_after 反而会降低吞吐。若你的存储设备能在断电时保住缓存数据,可以把它设为 0(禁用强制停顿),让内核自己调度。
与检查点(Checkpointer)的分工
初学者最容易把 bgwriter 和 checkpointer 混淆,两者都「刷脏」,但目标与节奏完全不同:
| 维度 | Background Writer(bgwriter) | Checkpointer(checkpointer 进程) |
|---|---|---|
| 触发条件 | 周期性(bgwriter_delay) | 达到 checkpoint_timeout 或 max_wal_size |
| 刷页范围 | 只刷「马上会被复用」的少数脏页 | 刷全部脏页(遍历整个缓冲区) |
| 节奏控制 | 天然分散、极细粒度 | 检查点期间集中,由 checkpoint_completion_target 摊平 |
| 是否记录检查点 | 否 | 是,写入 pg_control 与 WAL 中的 checkpoint 记录 |
| 是否影响恢复点 | 否 | 是,检查点决定了崩溃恢复的起点 |
| 统计视图 | pg_stat_io(PG16+)、旧 pg_stat_bgwriter | pg_stat_checkpointer(PG17+)、旧 pg_stat_bgwriter |
一句话记忆:bgwriter 只关心「缓冲区够不够用」,checkpointer 才关心「崩溃后从哪里恢复」。
统计视图的版本变迁(重要)
统计 bgwriter 行为的视图在近几个大版本中变化剧烈,这是排查时最容易踩坑的地方:
PG 15 及以前:
pg_stat_bgwriter 承载了「bgwriter + checkpointer + backend 刷脏」的全部计数
PG 16 起:
引入 pg_stat_io 视图,把各后台进程的 I/O 行为按 backend_type 拆分统计
(bgwriter、checkpointer 的读写次数/字节/耗时都能在 pg_stat_io 按类型查)
PG 17 起:
pg_stat_checkpointer 独立成视图(checkpoints_timed / checkpoints_req / ...)
pg_stat_bgwriter 中与检查点相关的列被移除,只保留 bgwriter 自己的计数
受影响的列:checkpoints_timed、checkpoints_req、checkpoint_write_time、
checkpoint_sync_time、buffers_checkpoint、buffers_clean 等-- PG 17+ 推荐:从专用视图分别查看
SELECT
buffers_clean, -- bgwriter 刷的缓冲区数
maxwritten_clean, -- 因超过 bgwriter_lru_maxpages 而提前结束的轮次数
buffers_alloc, -- 后端分配的缓冲区总数
stats_reset
FROM pg_stat_bgwriter;-- PG 16 及以前:所有计数都在 pg_stat_bgwriter 里
SELECT
checkpoints_timed, -- 按 checkpoint_timeout 触发的检查点数
checkpoints_req, -- 按 max_wal_size 等原因强制触发的检查点数
buffers_checkpoint, -- 检查点刷的缓冲区数
buffers_clean, -- bgwriter 刷的缓冲区数
buffers_backend, -- 后端被迫刷的缓冲区数
buffers_backend_fsync, -- 后端自己执行 fsync 的次数
buffers_alloc -- 后端分配的缓冲区总数
FROM pg_stat_bgwriter;判断 bgwriter 是否称职的三个比值
拿到这些计数后,重点看三个比值(buffers_alloc 为分母):
buffers_clean / buffers_alloc—— bgwriter 的贡献率,健康时应「明显大于 0」,但没有绝对值标准。buffers_backend / buffers_alloc—— 后端被迫刷脏的代价率,应尽量低。持续偏高说明 bgwriter 跟不上脏页产生速度。maxwritten_clean—— 应「基本不增长」。如果它每轮都在涨,说明bgwriter_lru_maxpages太小,bgwriter 每轮都没洗完就被截断了。
需要注意的是:buffers_backend 不是越低越好,因为后端自己刷的那一页是「它本来就必须等待的」,有些 I/O 无论如何都躲不掉。优化目标是压低「被迫额外等待」的部分。
详细说明
脏页产生速率与 bgwriter 的追赶能力
脏页的产生速率由写入型负载决定:UPDATE、INSERT、DELETE、大批量 COPY、索引维护、以及 VACUUM 都会弄脏数据页。bgwriter 的清洗能力则由三个参数共同决定:
理论清洗上限 ≈ bgwriter_lru_maxpages × (1000 / bgwriter_delay_ms) 页/秒
= 100 × (1000 / 200) = 500 页/秒
按 8KB/页换算:
500 × 8KB ≈ 4 MB/s这个数字在机械盘上勉强够用,在高写入负载下则明显偏低。当脏页产生速率长期高于 bgwriter 的清洗上限时,后果不是「bgwriter 追不上」这么简单,而是会形成恶性循环:
脏页产生太快
↓
bgwriter 每轮都被 bgwriter_lru_maxpages 截断(maxwritten_clean 持续增长)
↓
缓冲区里干净页减少,后端找不到可复用槽位
↓
后端被迫走「路径 ②」自己刷脏并同步 fsync(buffers_backend / backend_fsync 增长)
↓
用户查询延迟出现尖刺,QPS 下降,可用的 CPU 被浪费在等待 I/O 上此时正确的处理顺序是:
- 先看是不是有超大批量写入(如一次
UPDATE上百万行、无节制的COPY)——这是最常见的根因,应拆批。 - 再看
bgwriter_lru_maxpages/bgwriter_lru_multiplier是否过小。 - 最后才考虑调大
shared_buffers——缓冲区越大,单次检查点需要刷的页越多,恢复时间也越长,是有代价的。
后端刷脏(path ②)与 backend_fsync
后端被迫刷脏时有两种可能:
- 普通刷脏:把脏页写入操作系统页缓存即返回(
buffers_backend计数,但没有对应的 fsync)。此时若内核随后成功落盘,数据不丢。 - 带 fsync 的刷脏:当该缓冲区是「必须保证落盘」的场景(如正在做崩溃安全的检查点收尾、或
wal_sync_method相关约束)时,后端需要自己调用fsync(buffers_backend_fsync计数)。
buffers_backend_fsync > 0 是非常值得警惕的信号,它意味着用户请求被卡在了真实磁盘 I/O 上。不过要注意:
buffers_backend_fsync 不总是问题
在检查点期间,后端协助刷脏并 fsync 一部分页面是有意为之的设计——它能让检查点更快完成、把工作分摊出去。所以该值偶尔为正是正常的。真正需要担心的是它在非检查点期间的持续增长。
-- PG 16 及以前:一次性算出三条刷脏路径的占比
SELECT
buffers_clean,
buffers_checkpoint,
buffers_backend,
buffers_backend_fsync,
round(100.0 * buffers_clean / NULLIF(buffers_alloc, 0), 2) AS pct_clean,
round(100.0 * buffers_checkpoint / NULLIF(buffers_alloc, 0), 2) AS pct_checkpoint,
round(100.0 * buffers_backend / NULLIF(buffers_alloc, 0), 2) AS pct_backend
FROM pg_stat_bgwriter; buffers_clean | buffers_checkpoint | buffers_backend | buffers_backend_fsync | pct_clean | pct_checkpoint | pct_backend
---------------+--------------------+-----------------+-----------------------+-----------+----------------+-------------
1842331 | 923044 | 41208 | 1156 | 65.31 | 32.72 | 1.46上例中 pct_backend = 1.46% 属于健康区间(一般经验值:< 5% 良好,> 10% 需要介入),pct_clean 占了约三分之二,说明 bgwriter 承担了主要清洗工作。
用 pg_stat_io 精确观测(PG 16+)
PG 16 引入的 pg_stat_io 视图按照 backend_type(进程类型)与 object(对象类型)拆分 I/O 统计,可以精确回答「谁在刷盘、刷了多少字节、花了多少时间」:
-- PG 16+:按进程类型查看数据文件的写入情况
SELECT
backend_type,
object,
context,
writes,
write_bytes,
write_time,
fsyncs,
fsync_time
FROM pg_stat_io
WHERE object = 'relation' -- 只看数据文件
AND writes > 0
ORDER BY writes DESC; backend_type | object | context | writes | write_bytes | write_time | fsyncs | fsync_time
-----------------+----------+---------+----------+-------------+------------+--------+------------
checkpointer | relation | normal | 1239871 | 8123942192 | 912341 | 8821 | 120983
bgwriter | relation | normal | 1842100 | 12079398821 | 1120334 | 0 | 0
client backend | relation | normal | 41201 | 337706496 | 121003 | 1156 | 21908
autovacuum | relation | vacuum | 8123 | 66552220 | 18220 | 0 | 0关键解读:
bgwriter的fsyncs恒为 0:bgwriter 只调用write()把页交给内核,它不需要保证落盘(持久性由 WAL 负责),因此不做 fsync。这也解释了为什么它比后端刷脏「便宜」得多。context区分normal/vacuum/bulkwrite:可以看出 VACUUM 与批量写入各自的 I/O 贡献。write_time需要track_io_timing = on才有意义,否则为 0。
track_io_timing 有开销
在每次 I/O 前后调用计时函数会带来可测量的性能损耗(在高 IOPS 的 SSD 上尤其明显,可能达到 5%~10%)。除非正在做 I/O 性能诊断,否则不要长期打开。
实战示例
示例 1:读取 bgwriter 统计并判断健康度
-- PG 17+:bgwriter 自身计数(检查点相关列已移到 pg_stat_checkpointer)
SELECT
buffers_clean,
maxwritten_clean,
buffers_alloc,
round(100.0 * buffers_clean / NULLIF(buffers_alloc, 0), 2) AS pct_clean
FROM pg_stat_bgwriter; buffers_clean | maxwritten_clean | buffers_alloc | pct_clean
---------------+------------------+---------------+-----------
1842331 | 127 | 2830482 | 65.09-- 结合 pg_stat_checkpointer(PG 17+)看三条路径的全貌
SELECT
c.num_timed, -- 超时触发的检查点
c.num_requested, -- 请求(如 max_wal_size)触发的检查点
b.buffers_clean, -- bgwriter 刷脏
b.buffers_alloc -- 后端分配总数
FROM pg_stat_checkpointer c, pg_stat_bgwriter b; num_timed | num_requested | buffers_clean | buffers_alloc
-----------+---------------+---------------+---------------
31 | 4 | 1842331 | 2830482判读要点:
num_requested远小于num_timed是好的(说明max_wal_size够大,没被 WAL 量逼着频繁检查点)。maxwritten_clean = 127且增长缓慢可接受;若每次采样都涨几百,则应上调bgwriter_lru_maxpages。
示例 2:构造高写入负载并观察 bgwriter 是否跟得上
-- 建一张写入压力测试表
CREATE TABLE t_bgwriter_demo (
id bigserial PRIMARY KEY,
payload text NOT NULL,
flag integer NOT NULL DEFAULT 0
);
INSERT INTO t_bgwriter_demo (payload)
SELECT repeat('x', 200) FROM generate_series(1, 200000);-- 记录基线
SELECT buffers_clean, maxwritten_clean, buffers_alloc, stats_reset
FROM pg_stat_bgwriter;-- 制造持续脏页:反复全表更新(会同时弄脏表页与索引页)
DO $$
BEGIN
FOR i IN 1..20 LOOP
UPDATE t_bgwriter_demo SET flag = flag + 1;
END LOOP;
END $$;-- 观察增量:buffers_clean 应显著增长;若 maxwritten_clean 也大幅增长则说明每轮被截断
SELECT
buffers_clean,
maxwritten_clean,
buffers_alloc,
round(100.0 * buffers_clean / NULLIF(buffers_alloc, 0), 2) AS pct_clean
FROM pg_stat_bgwriter; buffers_clean | maxwritten_clean | buffers_alloc | pct_clean
---------------+------------------+---------------+-----------
2071244 | 618 | 3120884 | 66.37上例中 maxwritten_clean 从 127 涨到 618,说明 bgwriter 多轮都在刷满 bgwriter_lru_maxpages 后被截断——这正是「脏页产生速率超过清洗上限」的信号。
# postgresql.conf —— 针对上述信号调参
bgwriter_lru_maxpages = 400 # 每轮上限从 100 提高(示例值,可按需调整)
bgwriter_lru_multiplier = 4.0 # 提高预测倍率,更早开始刷示例 3:用 pg_stat_io 定位到底是谁在刷盘(PG 16+)
-- 清零统计(需超级用户,且会影响所有统计视图)
SELECT pg_stat_reset_shared('bgwriter');
SELECT pg_stat_reset_shared('io');
-- 等待一段时间后,查看关系文件的写入排行
SELECT
backend_type,
context,
writes,
write_bytes,
fsyncs
FROM pg_stat_io
WHERE object = 'relation'
ORDER BY writes DESC
LIMIT 5; backend_type | context | writes | write_bytes | fsyncs
-----------------+---------+---------+-------------+--------
bgwriter | normal | 1122230 | 8911333210 | 0
checkpointer | normal | 884120 | 6233882190 | 6621
client backend | normal | 31201 | 255703808 | 812
autovacuum | vacuum | 9021 | 73904128 | 0结论:bgwriter 承担了六成以上的写入且 fsyncs = 0(纯 write 调用,成本低),client backend 的写入与 fsyncs 都很低,说明后端被迫刷脏的情况不严重。这是健康状态。
示例 4:临时关闭 bgwriter 观察后端刷脏的变化
-- 先记录当前值
SHOW bgwriter_lru_maxpages; bgwriter_lru_maxpages
-----------------------
100-- 关闭 bgwriter(会话级修改,仅影响当前会话发起 SET 的进程,用于实验观察)
-- 注意:要全局验证效果应使用 ALTER SYSTEM + reload
SET bgwriter_lru_maxpages = 0;
-- 注意:该参数标记为 PGC_SIGHUP 级别,SET 只能改当前会话,bgwriter 进程本身不受影响。
-- 真正关闭需在 postgresql.conf 中设置为 0 并 reload。# postgresql.conf
bgwriter_lru_maxpages = 0 # 关闭 bgwriter,仅用于故障复现或极端特殊场景-- reload 后观察:buffers_backend 会逐渐增长(PG 16 及以前可直接查 pg_stat_bgwriter)
SELECT buffers_backend, buffers_backend_fsync, buffers_alloc
FROM pg_stat_bgwriter; buffers_backend | buffers_backend_fsync | buffers_alloc
-----------------+-----------------------+---------------
184023 | 8812 | 3021884恢复 bgwriter_lru_maxpages = 100 并 reload 后,buffers_backend 的增长速率会明显放缓。这个对比实验能直观说明 bgwriter 的价值。
注意事项
- bgwriter 关闭不会丢数据:持久性由 WAL 保证。关闭 bgwriter 只是让性能变差(后端被迫刷脏),不会影响正确性。
bgwriter_lru_maxpages是 SIGHUP 级参数:SET命令无法改变运行中的 bgwriter 进程行为,必须写入postgresql.conf后reload或ALTER SYSTEM SET+SELECT pg_reload_conf()。- 不要同时把 bgwriter 和检查点都调得很激进:两者的 I/O 会叠加,反而更容易打满磁盘。
pg_stat_bgwriter的字段随版本变动:写监控脚本时必须按版本分支,否则升级到 PG 17 会因列不存在而报错。buffers_backend_fsync在检查点期间为正是正常的:检查点会让后端协助刷脏并 fsync,不要误判为故障。buffers_alloc统计的是分配次数,不是「读入页数」:命中与未命中都会分配缓冲区(未命中时填充,命中时仅增加引用)。bgwriter_flush_after不是越大越好:它约束的是单次连续写入的批量,设得过大就失去了「避免内核脏页堆积」的初衷。- 统计视图不会自动清零:
stats_reset可能已是很久以前的值,做增量分析前务必先取基线。 pg_stat_io需要 PG 16+:在更早版本只能靠pg_stat_bgwriter的聚合数字推断。- 内核页缓存仍是双重缓存(Double Buffering)的一部分:bgwriter 用
write()而非O_DIRECT,数据先进入内核页缓存;真正落盘时机由内核决定,这也是*_flush_after要存在的原因。
# postgresql.conf —— 不推荐:把 bgwriter 当成检查点调节器
# bgwriter_lru_maxpages = 1000 # 不推荐:误以为调大就能替代检查点调优
# bgwriter_delay = 10ms # 不推荐:过短会让 bgwriter 空转占用 CPU-- 不推荐:在看统计前不做基线采样
-- SELECT buffers_clean FROM pg_stat_bgwriter; -- 不推荐:绝对值无意义,必须看增量与比值最佳实践
- 保持 bgwriter 开启:
bgwriter_lru_maxpages = 0只应在故障复现或极端排查中使用。 - 优先用比值而非绝对值判断:关注
pct_clean、pct_backend与maxwritten_clean的增长趋势。 maxwritten_clean持续增长就加bgwriter_lru_maxpages:这是最直接的调参信号。- 写入型负载上调
bgwriter_lru_multiplier:从2.0调到4.0能让 bgwriter 更早出手,代价是可能多刷一些马上又被修改的页。 - 机械盘保留
bgwriter_flush_after默认值,SSD/NVMe 可考虑设为0以提升吞吐。 - 按版本选择正确的统计视图:PG 17+ 用
pg_stat_checkpointer+pg_stat_bgwriter+pg_stat_io,PG 16 用pg_stat_bgwriter+pg_stat_io。 - 把 bgwriter 指标纳入常规监控:至少采集
buffers_clean、buffers_backend、maxwritten_clean三个量的增长率。 - 先用
pg_stat_io定位 I/O 来源,再决定调参:盲目调参可能让 checkpointer 与 bgwriter 互相争抢 I/O。 - 不要指望 bgwriter 解决「I/O 总量过大」的问题:如果写入量本身就是瓶颈,应优化 SQL、拆批提交或升级存储,而不是调 bgwriter。
- 调参后必须做对照验证:改动前后各取一段等长区间的增量数据,用比值对比,而不是凭感觉。
下一节
继续学习:WAL Writer(预写日志写入进程)