找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2214

积分

0

好友

345

主题
发表于 9 小时前 | 查看: 1| 回复: 0

什么是 Checkpoint

Checkpoint 是 PostgreSQL 用于确保存储层数据一致性的关键机制。简单来说,它是一个时间点,在这个点上,数据库可以保证所有在此之前的修改都已经持久化到了磁盘。这对于数据恢复至关重要。

PostgreSQL 官方文档是这么定义的:

“Checkpoints are points in the sequence of transactions at which it is guaranteed that the heap and index data files have been updated with all information written before that checkpoint. Any changes made to data files before that point are guaranteed to be already on disk.”

检查点是数据库从备份安全恢复或在崩溃后执行恢复操作的起点,恢复过程将从检查点位置开始重放 WAL(Write-Ahead Logging,预写日志)。

其实现流程主要包括以下几个步骤:

  1. 识别“脏”缓冲区:事务会在共享缓冲区中留下脏页。
  2. 分散写入磁盘:为了避免磁盘I/O出现剧烈峰值,PostgreSQL会通过 checkpoint_completion_target 参数控制,将脏页的写入分散在一段时间内完成。
  3. 执行 fsync():对每个写入的文件执行 fsync(),确保数据已完整到达物理磁盘。
  4. 更新控制文件:使用新的重做位置更新 global/pg_control 这个特殊文件。
  5. 回收旧WAL段:删除或重命名那些在崩溃恢复中不再需要的旧WAL文件。

为什么会导致性能不均衡?

如果你仔细观察一个稳定负载下PostgreSQL的响应时间,很可能会发现一种锯齿状的模式。

不同WAL大小对数据库吞吐量(TPS)的影响折线图

除了 fsync() 的系统调用开销,检查点后出现的性能下降,主要归因于 全页镜像写入。正如我在之前的博客中解释过的,FPI 对于防止因页面部分写入(页面撕裂)而导致的数据损坏是必要的。

文档中明确指出:

“… the PostgreSQL server writes the entire content of each disk page to WAL during the first modification of that page after a checkpoint. This is needed because a page write that is in process during an operating system crash might be only partially completed, leading to an on-disk page that contains a mix of old and new data …”

因此,在一个检查点完成后,会有大量页面首次被修改,从而产生大量的FPI候选。这会导致I/O峰值,进而拖累整体性能。对于追求稳定性的生产系统而言,这种周期性的性能波动是需要被优化的对象。

调优后,能节省多少I/O?

真实的答案是:这取决于具体的工作负载和数据模式

不过,我们完全可以使用像 pgbench 这样的合成工作负载来研究其潜在的优化空间。在这项测试中,我创建了一个测试数据库,并运行固定事务数量的pgbench负载,以此来比较不同配置下的WAL生成量。

pgbench -c 2 -t 1110000

这里,总计111万个事务会通过两个并发连接执行。

为了对比,我设置了四个不同的检查点间隔:5分钟(300秒)、15分钟(900秒)、30分钟(1800秒)和1小时(3600秒)。测试结果如下:

不同检查点间隔对应的WAL数据量与FPI数量表格

通过拉长检查点间隔,WAL的生成量从近12GB下降到了约2GB,节省了6倍!这个效果非常显著。

WAL生成量随检查点间隔变化的折线图

全页镜像(FPI)的写入数量更是从147万级别骤降到16万级别,节省了9倍之多。

FPI写入数量随检查点间隔变化的折线图

另一个值得关注的指标是WAL中全页镜像所占的百分比。在当前版本的PostgreSQL中,除非使用 pg_waldumppg_walinspect 工具分析,否则很难直接获取。pg_stat_wal 视图只能提供FPI的数量 (wal_fpi),但这些镜像在写入WAL文件前会被压缩。

好消息是,这个功能将在PostgreSQL 19中实现。一个新字段 wal_fpi_bytes 将被添加到 pg_stat_wal 视图中。就本文而言,我考虑未压缩的FPI大小(即 wal_fpi * BLCKSZ)。其比例从48.5%下降到了37.8%,这意味着WAL文件中事务数据所占的比例高于全页镜像。

优化的好处不仅来自FPI的减少。如果检查点过于频繁,相同的缓冲页可能会被反复刷新到磁盘。如果检查点间隔足够大,那么除非遇到内存压力,否则脏页可以更长时间地驻留在 shared_buffers 中。

除了这些直接的资源节省,许多用户反馈,仅仅通过调整检查点设置,就能获得大约10%的性能提升。

注意:上述测试基于PostgreSQL 18,数据来源于 pg_stat_wal 视图。

常见的疑虑与反驳

关于拉长检查点间隔,最常见的顾虑是它可能增加崩溃恢复所需的时间。道理很简单:PostgreSQL需要重放自上一个成功检查点以来生成的所有WAL文件,间隔越长,需要处理的WAL自然就越多。

但现实情况是,大多数关键生产系统都会配备基于流复制的高可用方案,例如 Patroni。当主节点崩溃时,系统会立即故障转移到备节点,根本无需等待主节点的崩溃恢复过程。因此,只要拥有可用的备用节点,恢复时间对数据库的可用性几乎没有影响。所以,在有高可用架构保障的前提下,崩溃恢复时间变得无关紧要

另一个因素是,即使PostgreSQL实例是独立的(无备节点),在优化检查点后,WAL的生成量本身已大幅减少,因此应用这些少量的WAL文件会非常快。由分散检查点带来的部分“问题”,被其自身的积极影响抵消了。

我还听过一个误解:如果检查点间隔设为1小时,那么恢复就需要1小时。这绝对是错误的。恢复速度与检查点间隔本身没有直接关系,它取决于需要应用的WAL文件数量以及系统应用WAL的速度。通常,恢复1小时产生的WAL可能只需要几秒或几分钟。让我们用实际的PostgreSQL日志来验证:

以下是两个基础设施水平一般的数据库的崩溃恢复日志示例。

LOG:  database system was not properly shut down; automatic recovery in progress
LOG:  redo starts at 14/EB49CB90
LOG:  redo done at 15/6BEECAD8 system usage: CPU: user: 18.60 s, system: 6.98 s, elapsed: 25.59 s

另一个例子:

LOG:  redo starts at 15/6BEECB78
LOG:  redo done at 16/83686B48 system usage: CPU: user: 47.79 s, system: 14.05 s, elapsed: 69.08 s

以第一个情况为例,恢复操作在25.19秒内应用了从LSN 14/EB49CB9015/6BEECAD8 的WAL。

postgres=# SELECT pg_wal_lsn_diff( '15/6BEECAD8', '14/EB49CB90' ) / 25.19;
       ?column?
-----------------------
 85680702.818578801112
(1 row)

计算结果是约85.68 MB/秒。即使在性能一般的系统上,我们也很容易看到恢复速度达到64 MB/秒或更高。这意味着,即使检查点间隔为一小时,在大多数情况下恢复也能在几分钟内完成。当然,这最终取决于WAL的生成速率。

如何调整检查点设置

PostgreSQL 提供了三个核心参数来控制检查点行为:

  1. checkpoint_timeout:此参数用于设置计划检查点之间的最大时间间隔。每隔 checkpoint_timeout 秒,就会触发一个检查点。如果你想将自动检查点间隔设置为1小时,这是首要调整的参数。对于带有物理备库的生产系统,我通常建议将此值设置为至少30分钟(1800秒)。
  2. max_wal_size:这个参数设定了PostgreSQL允许WAL在两次自动检查点之间增长的最大目标大小。如果这个值设置得太小,可能会过早触发检查点。如前所述,频繁的检查点会因产生更多FPI而增加WAL总量,形成恶性循环。因此,此值应设置得足够大,确保PostgreSQL在两个计划检查点之间有充足的WAL空间。
  3. checkpoint_completion_target:这个参数以 checkpoint_timeout 的分数形式表示。PostgreSQL会调节I/O速率,使得检查点进程在给定比例的时间内完成(或者在达到 max_wal_size 之前完成,以先发生者为准)。默认且通常推荐的值是 0.9,这意味着检查点可以利用两个检查点之间90%的时间来平滑I/O。但我见过的一个最佳实践是,如果 checkpoint_timeout 足够大(例如半小时或一小时),可以这样设置:
    checkpoint_completion_target = (checkpoint_timeout - 2min) / checkpoint_timeout

    这为检查点完成预留了约2分钟的安全余量。

如何监控检查点

如果开启了 log_checkpoints 参数,每个检查点的详细信息都会被记录到PostgreSQL日志中。例如,以下是一个 checkpoint_timeout 为60分钟,checkpoint_completion_target 为0.9的数据库的日志条目:

2026-01-14 16:16:48.181 UTC [226618] LOG:  checkpoint starting: time
2026-01-14 17:10:51.463 UTC [226618] LOG:  checkpoint complete: wrote 4344 buffers (1.7%), wrote 381 SLRU buffers; 0 WAL file(s) added, 0 removed, 549 recycled; write=3239.823 s, sync=0.566 s, total=3243.283 s; sync files=28, longest=0.156 s, average=0.021 s; distance=8988444 kB, estimate=11568648 kB; lsn=26/2E954A50, redo lsn=24/1514C308

这条日志告诉我们:

  • 定时检查点于 16:16:48.181 开始,于 17:10:51.463 完成,总耗时 3243.283 秒(约54分钟)。
  • 检查点进程写入了 4344 个缓冲区(约34MB),写阶段耗时 3239.823 秒。这54分钟的写入时长正是由 checkpoint_timeout * checkpoint_completion_target = 60 * 0.9 = 54 分钟所规划的,因此I/O影响被极大地平滑了。
  • distance=8988444 kB:表示自上一个检查点以来生成了多少WAL(约8.6GB),反映了这段时间的WAL生成速率。
  • estimate=11568648 kB(≈11.5 GB):是PostgreSQL预测的当前检查点到下一个检查点之间可能产生的WAL量。PostgreSQL会根据此预测来节流I/O,确保检查点在达到 max_wal_size 前完成。

此外,PostgreSQL还提供了统计视图用于监控:

  • pg_stat_wal(PostgreSQL 14+):提供WAL生成的累积统计。
  • pg_stat_bgwriter(至PostgreSQL 16):提供后台写入器(包括检查点)的摘要信息。
  • pg_stat_checkpointer(PostgreSQL 17+):专用于检查点进程的统计视图。

总结

检查点会生成大量WAL,直接影响服务器的整个I/O子系统,进而波及性能、备份和WAL归档。优化检查点设置能带来多方面的显著收益:

  1. 大幅节省昂贵的、同步性质的WAL I/O操作。
  2. 由于I/O操作减少,带来的相关性能提升是显而易见的,尤其在I/O成为瓶颈的系统中效果更为明显。
  3. 减轻了WAL归档的负担,节省了备份基础设施和存储成本。
  4. 生成的WAL越少,系统因WAL写满而耗尽空间的风险就越低。
  5. 备节点因应用大量WAL而出现“卡顿”的可能性更小。
  6. 节省用于流复制和WAL归档传输的网络带宽。

总而言之,优化检查点是每一位负责 PostgreSQL 的DBA在数据库性能调优中应该优先考虑的步骤之一。在具备高可用(如Patroni)框架的生产环境中,将检查点间隔安全地分散到1小时或更长时间,是完全可行且值得推荐的实践。合理的配置与监控是保障数据库稳定高效运行的重要一环,更多关于数据库运维的讨论,欢迎在云栈社区交流。

引用链接

[1] 一篇博客文章: https://www.percona.com/blog/wal-compression-in-postgresql-and-recent-improvements-in-version-15/
[2] 原文连接: https://www.percona.com/blog/importance-of-tuning-checkpoint-in-postgresql/




上一篇:PyAgrum使用指南:在Python中构建贝叶斯网络进行概率推理
下一篇:Node.js配置管理升级:从.env文件到动态配置的三种实现方案
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-7 19:24 , Processed in 0.295805 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表