阶段一:锚定绝对时空坐标(获取 REDO LSN)
检查点启动的第一步,是在不阻塞业务读写的前提下,在 WAL 日志流中打下一个“物理地基”。
- 抢占轻量级锁:Checkpointer 进程短暂获取 WAL 插入锁(
WALInsertLock)。 - 打下 REDO 指针:它读取当前 WAL 缓冲区中最新的字节偏移量(LSN)。这个 LSN 被标记为 REDO LSN。
- 物理意义:这个 REDO LSN 极其重要。它告诉未来的
Startup进程:“如果数据库在这之后崩溃了,请你重启时,绝对从这个坐标开始向后重放日志,这个坐标之前的数据,我已经保证全部落盘了。”
阶段二:内存快照的物理隔离(锁定“那一刻”的脏页)
这是你逻辑中“启动那一时刻”的底层实现。业务还在疯狂并发修改内存,Checkpointer 是如何做到“只刷启动那一瞬间的脏页,不刷后来产生的新脏页”的?
- 扫描 BufferDescriptors(缓冲描述符):Checkpointer 开始以极快的速度遍历 Shared Buffers 的元数据数组。
- 状态机分离:
- 如果发现某个 8KB 物理页带有
BM_DIRTY(脏页)标记,它会将其记录到一个名为SyncRequests(同步请求队列)的私有内存数组中。 - 同时,它会在这个页的头部打上一个特殊的标记:
BM_CHECKPOINT_NEEDED。
- 如果发现某个 8KB 物理页带有
- 物理意义:扫描结束的这一瞬间,就是“内存快照”生成的瞬间。接下来,Checkpointer 只负责处理带有
BM_CHECKPOINT_NEEDED标记的页。在此之后业务进程新修改的脏页(只有BM_DIRTY标记),Checkpointer 连看都不会看一眼,直接留给下一次 Checkpoint。
阶段三:致命纠错——I/O 平滑与限流(绝非“短暂的时间”)
这是你逻辑中最危险的误区。如果在 5TB 的数据库、100GB 的 Shared Buffers 中,产生了 20GB 的脏页,如果 Checkpointer 花“短暂的时间”(全速 I/O)去刷盘,会发生什么?
操作系统的 Page Cache 会被瞬间击穿,磁盘 IOPS 被瞬间耗尽,所有前端业务的 SELECT 和 UPDATE 会被当场卡死(I/O Starvation)。
因此,PostgreSQL 内核设计了极其严格的I/O 平滑机制(Checkpoint Throttling):
- 引入
checkpoint_completion_target:假设你的checkpoint_timeout设置为 5 分钟,completion_target设置为 0.9。 - 微积分计算:Checkpointer 计算出:“我必须在 $5 \times 0.9 = 4.5$ 分钟内,把这 20GB 慢慢写完。”
- 走走停停(Sleep 循环):在 C 语言的
BufferSync()函数中,每调用操作系统的write()刷出几个 8KB 数据块,Checkpointer 就会计算当前进度是否超前。如果超前,它会强制调用sleep()让自己休眠,把磁盘 I/O 资源让给业务进程。 - 物理意义:检查点刷盘是一个极其漫长、润物细无声的后台渗透过程,而不是一次瞬间的物理爆发。
阶段四:物理闭环(更新控制文件)
当那 20GB 的脏页经过漫长的平滑写入,并且通过操作系统的 fsync() 强制要求磁盘控制器确认落盘后,因果链走向终局:
- 写入 Checkpoint Record:Checkpointer 向 WAL 日志中真正写入一条 Checkpoint 记录,里面包含了阶段一获取的那个 REDO LSN。
- 篡改系统级指针(
pg_control):这是最后、也是最核心的一击。Checkpointer 打开底层的$PGDATA/global/pg_control文件,将刚才的 Checkpoint LSN 和 REDO LSN 覆盖写进去,并fsync落盘。 - 物理意义:只要
pg_control文件被成功更新,即使下一秒主板烧毁,PostgreSQL 再次启动时,也能精准地从这个新的 REDO LSN 开始恢复。
逻辑因果收束:
检查点并不是“短暂的暴击”,而是一场**“瞬间锁定物理快照(REDO LSN + SyncQueue),随后利用极长的时间(Target 限流)进行平滑 I/O,最终以更新 pg_control 文件作为绝对闭环”**的系统级精密工程。
了解 www.876873.xyz 的更多信息
订阅后即可通过电子邮件收到最新文章。
检查点进程运行机制:等您坐沙发呢!