纲要
Background Writer进程概述- 核心职责:定期将
shared_buffers中的脏页写入Page Cache - 设计目标:分摊
Checkpoint的 I/O 压力,减少后端进程(Backend)主动刷脏
- 核心职责:定期将
- 核心参数
bgwriter_delaybgwriter_lru_maxpagesbgwriter_lru_multiplierbgwriter_flush_after
- 监控视图
pg_stat_bgwriter(PG 16 及更早)buffers_cleanbuffers_backendmaxwritten_clean
pg_stat_io(PG 16+)pg_stat_checkpointer(PG 17+)
- PG 17 的重大变更
buffers_backend与buffers_backend_fsync从pg_stat_bgwriter中移除- 可观测性向
pg_stat_io迁移
- 多语言示例
- Node.js(
pg) - Go(
pgx) - Python(
psycopg2) - Java(JDBC)
- Node.js(
- 调优策略与最佳实践
一、Background Writer 的设计背景与核心职责
PostgreSQL 使用 shared_buffers 作为共享缓冲区缓存,所有数据页的读写都经过这一层。Checkpoint 进程负责将 shared_buffers 中的所有脏页刷入磁盘,但若两次 Checkpoint 间隔较长且写负载较高,累积的大量脏页会在单一 Checkpoint 瞬间触发巨大的 I/O 突发(I/O Spike),直接影响查询响应时间。
为解决这一问题,PostgreSQL 引入了独立的 Background Writer 后台写进程。其核心职责包括:
- 周期性扫描
shared_buffers:以固定间隔唤醒,扫描缓冲区中的脏页。 - 将脏页写入
Page Cache:Background Writer仅将数据写入操作系统Page Cache,实际的fsync落盘由Checkpoint进程负责。 - 为
Checkpoint分摊压力 :提前将部分脏页写出,Checkpoint执行时可跳过已写入的数据块,从而减少 I/O 突发。 - 减少后端进程主动刷脏 :当
shared_buffers空闲缓冲区不足时,后端进程需自行选择脏页淘汰并刷盘(称为buffers_backend)。Background Writer若能及时释放缓冲区,可显著降低后端进程的查询延迟。
#mermaid-svg-K07Rse1N1aEnf9Go{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K07Rse1N1aEnf9Go .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K07Rse1N1aEnf9Go .error-icon{fill:#552222;}#mermaid-svg-K07Rse1N1aEnf9Go .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K07Rse1N1aEnf9Go .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K07Rse1N1aEnf9Go .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K07Rse1N1aEnf9Go .marker.cross{stroke:#333333;}#mermaid-svg-K07Rse1N1aEnf9Go svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K07Rse1N1aEnf9Go p{margin:0;}#mermaid-svg-K07Rse1N1aEnf9Go .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K07Rse1N1aEnf9Go .cluster-label text{fill:#333;}#mermaid-svg-K07Rse1N1aEnf9Go .cluster-label span{color:#333;}#mermaid-svg-K07Rse1N1aEnf9Go .cluster-label span p{background-color:transparent;}#mermaid-svg-K07Rse1N1aEnf9Go .label text,#mermaid-svg-K07Rse1N1aEnf9Go span{fill:#333;color:#333;}#mermaid-svg-K07Rse1N1aEnf9Go .node rect,#mermaid-svg-K07Rse1N1aEnf9Go .node circle,#mermaid-svg-K07Rse1N1aEnf9Go .node ellipse,#mermaid-svg-K07Rse1N1aEnf9Go .node polygon,#mermaid-svg-K07Rse1N1aEnf9Go .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K07Rse1N1aEnf9Go .rough-node .label text,#mermaid-svg-K07Rse1N1aEnf9Go .node .label text,#mermaid-svg-K07Rse1N1aEnf9Go .image-shape .label,#mermaid-svg-K07Rse1N1aEnf9Go .icon-shape .label{text-anchor:middle;}#mermaid-svg-K07Rse1N1aEnf9Go .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K07Rse1N1aEnf9Go .rough-node .label,#mermaid-svg-K07Rse1N1aEnf9Go .node .label,#mermaid-svg-K07Rse1N1aEnf9Go .image-shape .label,#mermaid-svg-K07Rse1N1aEnf9Go .icon-shape .label{text-align:center;}#mermaid-svg-K07Rse1N1aEnf9Go .node.clickable{cursor:pointer;}#mermaid-svg-K07Rse1N1aEnf9Go .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K07Rse1N1aEnf9Go .arrowheadPath{fill:#333333;}#mermaid-svg-K07Rse1N1aEnf9Go .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K07Rse1N1aEnf9Go .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K07Rse1N1aEnf9Go .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K07Rse1N1aEnf9Go .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K07Rse1N1aEnf9Go .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K07Rse1N1aEnf9Go .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K07Rse1N1aEnf9Go .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K07Rse1N1aEnf9Go .cluster text{fill:#333;}#mermaid-svg-K07Rse1N1aEnf9Go .cluster span{color:#333;}#mermaid-svg-K07Rse1N1aEnf9Go div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K07Rse1N1aEnf9Go .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K07Rse1N1aEnf9Go rect.text{fill:none;stroke-width:0;}#mermaid-svg-K07Rse1N1aEnf9Go .icon-shape,#mermaid-svg-K07Rse1N1aEnf9Go .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K07Rse1N1aEnf9Go .icon-shape p,#mermaid-svg-K07Rse1N1aEnf9Go .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K07Rse1N1aEnf9Go .icon-shape .label rect,#mermaid-svg-K07Rse1N1aEnf9Go .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K07Rse1N1aEnf9Go .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K07Rse1N1aEnf9Go .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K07Rse1N1aEnf9Go :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 定期扫描脏页
fsync
Backend 主动刷脏
用户查询
Backend Process
shared_buffers
Background Writer
Page Cache
Checkpoint
磁盘
shared_buffers 不足
二、Background Writer 核心参数详解
以下参数均在 postgresql.conf 中配置,修改后需重新加载(pg_reload_conf() 或 SELECT pg_reload_conf();)或重启生效。
2.1 bgwriter_delay
含义 :Background Writer 两次活动轮次之间的延迟时间(毫秒)。
默认值 :200ms。
说明 :每轮中 Background Writer 会写入若干脏页(由后续参数控制),然后休眠 bgwriter_delay 毫秒后重复。若某次扫描未发现任何脏页,进程将进入更长的休眠。注意,许多系统上睡眠延迟的有效分辨率仅为 10 毫秒,设置为非 10 的倍数将向上取整。
调优建议 :写入密集型工作负载可适当降低该值(如 100ms),使 Background Writer 更频繁地工作,分散 I/O 负载。
2.2 bgwriter_lru_maxpages
含义 :每轮中 Background Writer 最多写入的缓冲区数量。
默认值 :100 个缓冲区。
说明 :设置为 0 将禁用背景写入(Checkpoint 活动不受影响)。该参数与 bgwriter_lru_multiplier 共同决定每轮实际写入量。
调优建议 :I/O 能力充足的系统可适当提高该值(如 200 或更高),让 Background Writer 每轮处理更多脏页。
2.3 bgwriter_lru_multiplier
含义:每轮写入脏页数量的乘数因子。
默认值 :2.0。
说明 :每轮写入量基于"最近几轮中后端进程所需的新缓冲区数量"的平均值乘以该乘数。设置 1.0 代表"准时制"策略------恰好写入预测所需的数量;更大的值提供应对突发需求的缓冲,更小的值则有意将写入工作留给后端进程。
调优建议:
- 若
buffers_backend过高,可适当提高bgwriter_lru_multiplier(如2.5或3.0),使Background Writer更积极地清理脏页。 - 若 I/O 资源紧张,可降低该值以减少背景写带来的额外 I/O 开销。
2.4 bgwriter_flush_after
含义 :Background Writer 写入超过此字节数后,强制操作系统将写入下发到底层存储。
默认值 :512kB(Linux)。
说明 :该参数控制 Background Writer 调用 posix_fadvise 等接口的频率,避免 Page Cache 中积累过多脏页。
三、监控视图与指标解读
3.1 pg_stat_bgwriter(PG 16 及更早)
该视图始终包含单行全局统计数据。核心字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
buffers_checkpoint |
bigint |
Checkpoint 期间写入的缓冲区数 |
buffers_clean |
bigint |
Background Writer 写入的缓冲区数 |
maxwritten_clean |
bigint |
Background Writer 因写入过多而停止清理扫描的次数 |
buffers_backend |
bigint |
后端进程直接写入的缓冲区数 |
buffers_backend_fsync |
bigint |
后端进程执行 fsync 的次数 |
buffers_alloc |
bigint |
分配的缓冲区数 |
sql
-- 查询 Background Writer 相关统计(PG 16 及更早)
SELECT
buffers_clean,
buffers_backend,
buffers_backend_fsync,
maxwritten_clean,
buffers_alloc
FROM pg_stat_bgwriter;
指标解读:
buffers_clean应远大于buffers_backend,表明大多数刷脏工作由Background Writer完成。- 若
buffers_backend过高,说明后端进程频繁主动刷脏,应调优Background Writer参数使其更积极地工作。 maxwritten_clean过高说明Background Writer每轮写入量达到bgwriter_lru_maxpages上限,可考虑提高该值。
3.2 pg_stat_io(PG 16+)
PG 16 引入了 pg_stat_io 视图,按后端类型、对象类型和 I/O 上下文细粒度统计 I/O 操作。这是 PostgreSQL 可观测性的一次重大提升。
sql
-- 查询后端进程直接写入的统计(PG 16+)
SELECT
backend_type,
object,
context,
writes,
write_bytes
FROM pg_stat_io
WHERE backend_type = 'client backend'
OR backend_type = 'background writer';
3.3 pg_stat_checkpointer(PG 17+)
PG 17 将 Checkpoint 相关统计从 pg_stat_bgwriter 中剥离,独立为 pg_stat_checkpointer 视图。
sql
-- PG 17+ 查询 Checkpoint 统计
SELECT
checkpoints_timed,
checkpoints_req,
checkpoint_write_time,
checkpoint_sync_time,
buffers_checkpoint
FROM pg_stat_checkpointer;
四、PG 17 的重大变更:buffers_backend 的移除与可观测性演进
4.1 变更内容
PostgreSQL 17 对 pg_stat_bgwriter 视图进行了重要重构:
- 移除的字段 :
buffers_backend和buffers_backend_fsync。 - 迁移路径 :这些信息现已通过
pg_stat_io视图提供。 - Checkpoint 统计独立 :
checkpoints_timed、checkpoints_req、checkpoint_write_time、checkpoint_sync_time、buffers_checkpoint迁移至pg_stat_checkpointer。
4.2 为什么 buffers_backend 是"误导人的指标"
原素材中提到的"buffers_backend 是一个误导人的指标",其根本原因在于:
buffers_backend 不仅统计普通后端进程因缓冲区不足而主动刷脏的次数,还统计了 autovacuum 等后台进程的写入操作。这意味着该指标混杂了多种来源的写入,无法准确反映"查询后端因 Background Writer 工作不及时而被迫刷脏"的真实情况。
PG 17 通过 pg_stat_io 的 backend_type 字段实现了精确区分,运维人员可按需过滤:
sql
-- PG 17+ 精确查询 client backend 的主动写入
SELECT
writes AS backend_direct_writes,
write_bytes AS backend_direct_write_bytes
FROM pg_stat_io
WHERE backend_type = 'client backend'
AND context = 'normal';
4.3 版本兼容建议
| PostgreSQL 版本 | pg_stat_bgwriter 可用字段 |
后端写入统计来源 |
|---|---|---|
| ≤ 16 | buffers_backend, buffers_backend_fsync |
pg_stat_bgwriter |
| ≥ 17 | 移除 buffers_backend |
pg_stat_io |
若需编写跨版本兼容的监控脚本,可先检查 PostgreSQL 主版本号,再选择对应的查询语句。
五、多语言示例
以下示例演示如何通过不同编程语言连接 PostgreSQL,查询 Background Writer 相关统计信息。所有示例均使用原生驱动,并附有对应的 PostgreSQL 原生 SQL 注释。
5.1 Node.js(pg 库)
ts
import { Client } from 'pg';
async function getBgWriterStats() {
const client = new Client({
host: 'localhost',
port: 5432,
database: 'postgres',
user: 'postgres',
password: 'your_password',
});
await client.connect();
try {
// 查询 pg_stat_bgwriter(PG 16 及更早)
// SELECT buffers_clean, buffers_backend, maxwritten_clean
// FROM pg_stat_bgwriter;
const res = await client.query(`
SELECT
buffers_clean,
buffers_backend,
maxwritten_clean
FROM pg_stat_bgwriter
`);
console.log('Background Writer Stats:');
console.log(` buffers_clean: ${res.rows[0].buffers_clean}`);
console.log(` buffers_backend: ${res.rows[0].buffers_backend}`);
console.log(` maxwritten_clean: ${res.rows[0].maxwritten_clean}`);
} finally {
await client.end();
}
}
getBgWriterStats().catch(console.error);
5.2 Go(pgx 库)
go
package main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v5"
)
func main() {
conn, err := pgx.Connect(context.Background(), "postgres://postgres:your_password@localhost:5432/postgres")
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
// 查询 pg_stat_bgwriter(PG 16 及更早)
// SELECT buffers_clean, buffers_backend, maxwritten_clean
// FROM pg_stat_bgwriter;
var buffersClean, buffersBackend, maxwrittenClean int64
err = conn.QueryRow(context.Background(), `
SELECT
buffers_clean,
buffers_backend,
maxwritten_clean
FROM pg_stat_bgwriter
`).Scan(&buffersClean, &buffersBackend, &maxwrittenClean)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Background Writer Stats:\n")
fmt.Printf(" buffers_clean: %d\n", buffersClean)
fmt.Printf(" buffers_backend: %d\n", buffersBackend)
fmt.Printf(" maxwritten_clean: %d\n", maxwrittenClean)
}
5.3 Python(psycopg2 库)
python
import psycopg2
def get_bgwriter_stats():
conn = psycopg2.connect(
host="localhost",
port=5432,
database="postgres",
user="postgres",
password="your_password"
)
try:
with conn.cursor() as cur:
# 查询 pg_stat_bgwriter(PG 16 及更早)
# SELECT buffers_clean, buffers_backend, maxwritten_clean
# FROM pg_stat_bgwriter;
cur.execute("""
SELECT
buffers_clean,
buffers_backend,
maxwritten_clean
FROM pg_stat_bgwriter
""")
row = cur.fetchone()
print("Background Writer Stats:")
print(f" buffers_clean: {row[0]}")
print(f" buffers_backend: {row[1]}")
print(f" maxwritten_clean: {row[2]}")
finally:
conn.close()
if __name__ == "__main__":
get_bgwriter_stats()
5.4 Java(JDBC)
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class BgWriterStats {
public static void main(String[] args) {
String url = "jdbc:postgresql://localhost:5432/postgres";
String user = "postgres";
String password = "your_password";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement()) {
// 查询 pg_stat_bgwriter(PG 16 及更早)
// SELECT buffers_clean, buffers_backend, maxwritten_clean
// FROM pg_stat_bgwriter;
String sql = """
SELECT
buffers_clean,
buffers_backend,
maxwritten_clean
FROM pg_stat_bgwriter
""";
try (ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
System.out.println("Background Writer Stats:");
System.out.println(" buffers_clean: " + rs.getLong("buffers_clean"));
System.out.println(" buffers_backend: " + rs.getLong("buffers_backend"));
System.out.println(" maxwritten_clean: " + rs.getLong("maxwritten_clean"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.5 多语言对比
| 维度 | Node.js (pg) | Go (pgx) | Python (psycopg2) | Java (JDBC) |
|---|---|---|---|---|
| 驱动 | pg |
pgx/v5 |
psycopg2 |
org.postgresql.Driver |
| 连接方式 | 连接字符串 / 对象 | DSN 连接字符串 | 关键字参数 | JDBC URL |
| 查询接口 | client.query() |
QueryRow() / Query() |
cursor.execute() |
Statement.executeQuery() |
| 结果处理 | rows[0].column |
Scan(&var) |
fetchone()[idx] |
rs.getLong("column") |
| 资源管理 | 手动 end() |
defer Close() |
try-finally |
try-with-resources |
| 类型安全 | 弱类型(JS) | 强类型 | 弱类型(Python) | 强类型 |
| 适用场景 | 快速原型、Web 后端 | 高性能微服务 | 数据科学、自动化运维 | 企业级应用 |
六、调优策略与最佳实践
6.1 判断 Background Writer 是否"懒惰"
通过对比 buffers_clean 与 buffers_backend 可判断 Background Writer 的工作效率:
- 健康状态 :
buffers_clean>>buffers_backend,表明刷脏工作主要由Background Writer完成。 - 需调优状态 :
buffers_backend接近或超过buffers_clean,表明后端进程频繁主动刷脏,应考虑:- 降低
bgwriter_delay(如100ms),提高唤醒频率。 - 提高
bgwriter_lru_maxpages(如200),增加每轮写入量。 - 提高
bgwriter_lru_multiplier(如2.5或3.0),使Background Writer更积极。
- 降低
6.2 注意 PG 17 的监控变更
若使用 PG 17 及以上版本:
- 使用
pg_stat_io替代buffers_backend监控后端写入。 - 使用
pg_stat_checkpointer监控Checkpoint相关指标。 - 编写监控脚本时需做版本适配。
6.3 Checkpoint 与 Background Writer 的协同
Background Writer 的目标是减少 而非消除 Checkpoint 的 I/O 压力。过度激进的 Background Writer 可能增加整体 I/O 负载(因为同一脏页可能被多次写出)。调优时需在"减少 Checkpoint 突发"与"控制背景写额外开销"之间取得平衡。
七、官方文档
- PostgreSQL 官方文档 - Background Writer :https://www.postgresql.org/docs/current/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-BACKGROUND-WRITER
- PostgreSQL 官方文档 - pg_stat_bgwriter :https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-BGWRITER-VIEW
- PostgreSQL 官方文档 - pg_stat_io :https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-IO-VIEW
- PostgreSQL 官方文档 - pg_stat_checkpointer :https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-CHECKPOINTER-VIEW
八、参考链接
- Understanding the PostgreSQL Background Writer - Fastware
- The pg_stat_checkpointer view in Postgres 17 - pganalyze
- PostgreSQL 17: pg_stat_bgwriter column changes - Munin适配方案
- PostgreSQL BGWriter 与 Checkpoint 性能调优 - AustinDatabases
总结
Background Writer 是 PostgreSQL 中负责将 shared_buffers 脏页定期写入 Page Cache 的后台进程,其核心价值在于为 Checkpoint 分摊 I/O 压力并减少后端进程主动刷脏带来的查询延迟。关键调优参数包括 bgwriter_delay、bgwriter_lru_maxpages、bgwriter_lru_multiplier 和 bgwriter_flush_after,需根据 buffers_clean 与 buffers_backend 的比例进行动态调整。
PG 17 将 buffers_backend 从 pg_stat_bgwriter 中移除并迁移至 pg_stat_io,同时将 Checkpoint 统计独立为 pg_stat_checkpointer,显著提升了可观测性的精细度。多语言 Demo 展示了 Node.js、Go、Python、Java 四种主流技术栈访问 pg_stat_bgwriter 的实践方式,便于运维与开发团队快速集成监控能力。