当前位置: 首页 > news >正文

PostgreSQL 中的“脏页(Dirty Pages)”是什么?

PostgreSQL 以固定大小的数据块(Page)存储数据,默认大小为 8 KB。当客户端执行更新或插入操作时,PostgreSQL 并不会立即将变更写入磁盘,而是先将相关数据页加载到共享内存(Shared Buffers)中,在内存中完成修改,并将该页面标记为“脏页”。所谓“脏页”,是指内存中的页面版本已经新于磁盘上的对应版本。

在向客户端返回操作结果之前,PostgreSQL 会先将变更记录写入预写日志(Write-Ahead Log,WAL),以保证即使数据库发生崩溃也能恢复数据一致性。但实际的数据文件并不会立刻更新,只有在检查点(Checkpoint)触发或后台写进程执行刷新时,脏页才会被写回磁盘。

在此之前,脏页会持续累积在内存中,直到通过以下三种机制之一被刷新:

  • 后台写进程(Background WriterBGWriter):一个常驻后台进程,在可用的干净缓冲区数量下降时,持续将脏页写入磁盘。
  • 检查点进程(Checkpointer):在触发检查点时(如达到checkpoint_timeout或 WAL 超过max_wal_size),将所有脏页刷新到磁盘。
  • 后端进程(Backend):在紧急情况下(如共享缓冲区几乎全部为脏页),普通后端进程会自行写脏页,可能导致用户查询阻塞。

理解并合理控制脏页的刷新时机与方式,是优化 PostgreSQL 性能的关键。

脏页为何影响性能

脏页会从多个方面影响数据库性能:

1. 检查点期间的 I/O 峰值

检查点发生时,所有脏页都必须被刷新到磁盘。如果脏页数量较多,会在短时间内产生大量磁盘 I/O,影响其他查询性能。checkpoint_timeoutcheckpoint_completion_targetmax_wal_size等参数决定了检查点触发的频率以及刷新脏页的节奏。

2. 后端写入带来的查询阻塞

当共享缓冲区被大量脏页占满,而 BGWriter 无法及时清理时,后端进程将被迫自行写盘,直接阻塞正在执行的用户查询。为避免此类情况,应通过合理的内存与刷新参数配置,让 BGWriter 承担绝大多数写入工作。实践中通常包括:

  • shared_buffers分配足够内存;
  • 调整bgwriter_delaybgwriter_lru_maxpagesbgwriter_lru_multiplierbgwriter_flush_after等参数,使脏页持续、平稳地写入磁盘;
  • 通过增大checkpoint_timeout、提高checkpoint_completion_target以及增大max_wal_size,减少检查点引发的突发写入。

3. 吞吐量与崩溃恢复时间的权衡

较少的刷新频率(如较大的checkpoint_timeout)可以降低 I/O 开销,但会增加数据库崩溃后需要回放的 WAL 数量;更频繁的刷新可以加快恢复速度,但可能降低运行时性能。合理的参数配置需要根据实际业务负载在两者之间取得平衡。

PostgreSQL 管理脏页的核心机制

后台写进程(Background Writer,BGWriter)

BGWriter 是一个独立进程,其目标是在后台持续写出脏页,保持一定数量的干净缓冲区可用。根据官方文档描述:

  • 当共享缓冲区中可用的干净页面数量低于阈值时,BGWriter 会写出部分脏页并将其标记为干净。
  • 若同一页面在一个检查点周期内被多次修改,可能会被多次写盘,从而增加总体 I/O。

BGWriter 的主要配置参数(位于postgresql.conf)包括:

调优建议:

如果在pg_stat_bgwriter中观察到后端进程写盘较多,应适当提高bgwriter_lru_maxpagesbgwriter_lru_multiplier;若 BGWriter 本身导致 I/O 过高,则可适当降低相关参数。通过调整bgwriter_delay,在写入频率与 CPU 开销之间取得平衡。

检查点(Checkpointer)

检查点触发时,PostgreSQL 会将所有脏页写入磁盘,并在 WAL 中记录检查点位置。合理调整检查点参数有助于平滑 I/O 压力:

通过增大checkpoint_timeoutcheckpoint_completion_target,可以将写入分散到更长时间窗口内;max_wal_size决定了自动检查点的触发时机,从而间接影响脏页刷新频率。

共享缓冲区(Shared Buffers)

shared_buffers决定了 PostgreSQL 可用于缓存数据页和存放脏页的内存大小。该参数直接影响页面在内存中的停留时间以及写盘频率。

在专用数据库服务器上,通常建议将其设置为物理内存的 25%~ 40%。当shared_buffers较大时,往往需要相应提高max_wal_size,以避免检查点过于频繁。

shared_buffers 过小会导致脏页频繁被淘汰,引发后端写盘;设置过大则可能在检查点时一次性刷新大量页面,造成 I/O 峰值。合理的 shared_buffers 配合 BGWriter 调优,可以显著减少后端写入。

自动清理(Autovacuum) 与 冻结清理(Vacuum Freeze)

自动清理(Autovacuum)在更新可见性信息或冻结元组(Freeze)时,也会产生脏页。应确保 Autovacuum 运行频率足以防止表膨胀,但又不过于激进,以免产生不必要的写入。可根据业务负载调整autovacuum_vacuum_cost_limitautovacuum_vacuum_scale_factor。在 SSD 环境下,适当提高 Autovacuum 强度通常是有益的。

面向性能的调优实践

1. 先监控,再调优

通过pg_stat_bgwriter重点关注以下指标:

  • buffers_checkpoint:检查点写出的脏页数量。
  • buffers_clean:BGWriter 写出的页面数量。
  • buffers_backend:后端进程写出的页面数量。

目标是将buffers_backend控制在接近 0 的水平。

2. 合理配置共享缓冲区大小

以物理内存的 25% 作为起点,根据负载特征调整:

  • 若业务场景以读操作为主,可适当增大共享缓冲区。
  • 若写操作导致检查点产生大幅性能波动,则需调小共享缓冲区。

3. 调整 BGWriter 参数

  • 降低bgwriter_delay参数值(如设置为 100 毫秒),提高后台写入器的唤醒频率。
  • 针对高写入负载场景,增大bgwriter_lru_maxpages(建议取值范围 200–1000)与bgwriter_lru_multiplier(建议取值范围 3–4)参数,提升单次周期内的脏页处理能力。
  • bgwriter_flush_after参数设为与存储系统最佳写入粒度匹配的值;对于固态硬盘,512KB–1MB 为推荐取值区间。

4. 优化检查点行为

  • 增大checkpoint_timeout参数,降低检查点触发频率(建议取值范围 15–60 分钟)。
  • checkpoint_completion_target参数提升至 0.7–0.9,使检查点的写入操作均匀分布。
  • 增大 max_wal_size 参数,避免检查点被过度频繁触发。

5. 避免后端写入

buffers_backend持续增长时,应优先增加 shared_buffers 或增强 BGWriter 的写盘能力。后端写盘往往是查询延迟的主要来源。

6. 操作系统层面优化

  • Linux 系统中,应避免vm.dirty_background_ratiovm.dirty_ratio设置过高,以免内核长时间累积脏页,造成突发写回。
  • 关闭透明大页(THP),并在内存充足的服务器上启用静态大页(Huge Pages),以提升整体性能。

7. 持续评估与迭代

不同业务负载差异较大,应结合pg_stat_activitypg_stat_bgwriter以及 PostgreSQL 17 引入的pg_stat_io等视图,持续评估调优效果,并逐步调整参数。

总结

“脏页”本质上是 PostgreSQL 内存中等待写回磁盘的已修改数据页。通过脏页机制,PostgreSQL 能够合并写入操作,并借助 WAL 保证崩溃安全性。但如果相关参数配置不当,脏页处理可能引发 I/O 峰值和查询延迟。

深入理解共享缓冲区、后台写进程、检查点与 WAL 的协同机制,并合理调优bgwriter_delaybgwriter_lru_maxpagesbgwriter_lru_multipliercheckpoint_timeoutshared_buffers等关键参数,有助于在保障数据可靠性的同时,实现平稳、可预测的数据库性能。

原文链接:

https://stormatics.tech/blogs/what-are-dirty-pages-in-postgresql

作者:Umair Shahid

http://www.cnnetsun.cn/news/69079.html

相关文章:

  • Temu 分销重塑跨境生态:轻资产时代的新增长法则
  • Hello World的深度演进:一个Ascend C标量算子的性能剖析之旅
  • [Python桌面开发] 本地多服务启动神器:Python + Tkinter 构建“进程批量启动与监控工具”(跨平台 GUI + 源码开放)
  • 量子算法的实现路径解析(工业级应用稀缺技术曝光)
  • 揭秘Python最被低估的8个标准库,第6个能省下你一半代码量
  • GraphQL + PHP错误处理全解析,构建高可用API的必备技能
  • 当AI接管代码:哈佛调查显示53%年轻开发者每天用AI,却59%担心被取代,这届程序员太难了!
  • 16、编程中的颜色与图形绘制及HTML基础入门
  • PHP 8.6的JIT缓存机制揭秘:5大策略提升应用执行效率300%
  • 基于微信小程序的动漫社区交流小程序的设计与实现(源码+lw+部署文档+讲解等)
  • 响应格式化踩坑实录:Symfony 8开发者必须避开的5个陷阱
  • PHP 8.6性能监控面板实战(专家级配置全公开)
  • 性能监控在DevOps中的角色
  • RN Hooks 设计规范与反模式清单
  • 《Advanced Science》最新研究:多自由度折纸模块构建可编程机械超材料网络
  • 用梯形图+SCL玩转FactoryIO码垛控制
  • 7、Nagios 安装与功能拓展全解析
  • 读懂 NVIDIA Jetson OP-TEE 官方源码:从目录结构到 JetPack / Yocto 构建与运行的完整指南
  • LobeChat能否实现邀请码注册机制?控制用户增长节奏
  • Angular AOT编译失败?这份官方文档解读帮你10分钟定位问题
  • PHP环境下医疗数据备份怎么做?5种高可用方案对比分析
  • 【Python库选型避坑手册】:5年踩坑经验总结出的7条黄金法则
  • PHP 8.6 JIT编译器重大升级(指令优化黑科技曝光)
  • Keil串口通信全教程:UART初始化、数据收发(中断/查询模式)+串口调试助手验证
  • 揭秘WebSocket频繁断线之谜:3种常见错误码分析与修复方案
  • LPDDR6 JEDEC 原文解读学习—2.4 Data Packet Format(3)(~持续更新)
  • 【PHP性能优化关键一步】:深入PHP 8.6内存泄漏监控与自动预警方案
  • PHP 8.6兼容性测试实战(资深架构师亲授迁移经验)
  • 从入门到精通:用R Shiny打造可交互的多维度数据仪表盘
  • 光伏阵列遇到局部阴影就像吃火锅被隔壁桌抢肉——憋屈得很。今天咱们用Python整活,搞个3×3电池板的阴影仿真模型,重点观察串联结构在阴影下的输出曲线怎么抽风