阶段一:锚定绝对时空坐标(获取 REDO LSN)

检查点启动的第一步,是在不阻塞业务读写的前提下,在 WAL 日志流中打下一个“物理地基”。

  1. 抢占轻量级锁:Checkpointer 进程短暂获取 WAL 插入锁(WALInsertLock)。
  2. 打下 REDO 指针:它读取当前 WAL 缓冲区中最新的字节偏移量(LSN)。这个 LSN 被标记为 REDO LSN
  3. 物理意义:这个 REDO LSN 极其重要。它告诉未来的 Startup 进程:“如果数据库在这之后崩溃了,请你重启时,绝对从这个坐标开始向后重放日志,这个坐标之前的数据,我已经保证全部落盘了。”

阶段二:内存快照的物理隔离(锁定“那一刻”的脏页)

这是你逻辑中“启动那一时刻”的底层实现。业务还在疯狂并发修改内存,Checkpointer 是如何做到“只刷启动那一瞬间的脏页,不刷后来产生的新脏页”的?

  1. 扫描 BufferDescriptors(缓冲描述符):Checkpointer 开始以极快的速度遍历 Shared Buffers 的元数据数组。
  2. 状态机分离
    • 如果发现某个 8KB 物理页带有 BM_DIRTY(脏页)标记,它会将其记录到一个名为 SyncRequests(同步请求队列)的私有内存数组中。
    • 同时,它会在这个页的头部打上一个特殊的标记:BM_CHECKPOINT_NEEDED
  3. 物理意义:扫描结束的这一瞬间,就是“内存快照”生成的瞬间。接下来,Checkpointer 只负责处理带有 BM_CHECKPOINT_NEEDED 标记的页。在此之后业务进程新修改的脏页(只有 BM_DIRTY 标记),Checkpointer 连看都不会看一眼,直接留给下一次 Checkpoint。

阶段三:致命纠错——I/O 平滑与限流(绝非“短暂的时间”)

这是你逻辑中最危险的误区。如果在 5TB 的数据库、100GB 的 Shared Buffers 中,产生了 20GB 的脏页,如果 Checkpointer 花“短暂的时间”(全速 I/O)去刷盘,会发生什么?

操作系统的 Page Cache 会被瞬间击穿,磁盘 IOPS 被瞬间耗尽,所有前端业务的 SELECTUPDATE 会被当场卡死(I/O Starvation)。

因此,PostgreSQL 内核设计了极其严格的I/O 平滑机制(Checkpoint Throttling)

  1. 引入 checkpoint_completion_target:假设你的 checkpoint_timeout 设置为 5 分钟,completion_target 设置为 0.9。
  2. 微积分计算:Checkpointer 计算出:“我必须在 $5 \times 0.9 = 4.5$ 分钟内,把这 20GB 慢慢写完。”
  3. 走走停停(Sleep 循环):在 C 语言的 BufferSync() 函数中,每调用操作系统的 write() 刷出几个 8KB 数据块,Checkpointer 就会计算当前进度是否超前。如果超前,它会强制调用 sleep() 让自己休眠,把磁盘 I/O 资源让给业务进程。
  4. 物理意义:检查点刷盘是一个极其漫长、润物细无声的后台渗透过程,而不是一次瞬间的物理爆发。

阶段四:物理闭环(更新控制文件)

当那 20GB 的脏页经过漫长的平滑写入,并且通过操作系统的 fsync() 强制要求磁盘控制器确认落盘后,因果链走向终局:

  1. 写入 Checkpoint Record:Checkpointer 向 WAL 日志中真正写入一条 Checkpoint 记录,里面包含了阶段一获取的那个 REDO LSN。
  2. 篡改系统级指针(pg_control:这是最后、也是最核心的一击。Checkpointer 打开底层的 $PGDATA/global/pg_control 文件,将刚才的 Checkpoint LSN 和 REDO LSN 覆盖写进去,并 fsync 落盘。
  3. 物理意义:只要 pg_control 文件被成功更新,即使下一秒主板烧毁,PostgreSQL 再次启动时,也能精准地从这个新的 REDO LSN 开始恢复。

逻辑因果收束:

检查点并不是“短暂的暴击”,而是一场**“瞬间锁定物理快照(REDO LSN + SyncQueue),随后利用极长的时间(Target 限流)进行平滑 I/O,最终以更新 pg_control 文件作为绝对闭环”**的系统级精密工程。


了解 www.876873.xyz 的更多信息

订阅后即可通过电子邮件收到最新文章。

检查点进程运行机制:等您坐沙发呢!

发表评论