在业务持续高并发写入(内存不断变化)的动态环境中,如何精确划定一条“时间分割线”,并确保这条分割线之前的所有数据,100% 写入底层的物理磁盘。
以下是 SELECT pg_start_backup() 在系统底层执行的 5 个绝对因果步骤。逻辑不跳跃,严格按照源码执行顺序推演:
第一步:API 接收与进程间硬中断触发
【前置状态】
PostgreSQL 处于高并发运行中,内存的 shared_buffers 中存在大量未写入磁盘的 BM_DIRTY(脏页)。底层的物理磁盘数据落后于内存数据。
【源码执行】
- 指令接收:
pg_probackup通过 TCP 网络协议(libpq)向 PostgreSQL 发送SELECT pg_start_backup()。 - 函数调用: 负责处理该连接的后台进程(Backend 进程)接收指令,跳转执行源码中的
do_pg_start_backup()函数。 - 发送中断信号: Backend 进程自身不负责写磁盘。它通过操作系统层面的进程间通信(IPC)机制,向专门负责刷盘的 Checkpointer(检查点进程) 发送一个唤醒信号。该信号携带
CHECKPOINT_IMMEDIATE和CHECKPOINT_FORCE两个极其严格的标志位。 - 自身挂起: 发送信号后,Backend 进程调用
WaitLatch(),将自身置于休眠状态,死死等待 Checkpointer 进程的完成回执。
第二步:排他锁抢占与绝对坐标提取(划定分割线)
【前置状态】
Checkpointer 进程被唤醒,它必须立刻确立一个不可争议的数学坐标,作为本次物理备份的起点。
【源码执行】
- 抢占排他锁: Checkpointer 进程向系统申请并获取
WALInsertLock(WAL 插入排他锁)。 - I/O 屏障形成: 在持有该锁的几微秒内,系统内所有其他的业务 Backend 进程,被物理禁止向 WAL 日志缓冲区追加任何新的修改记录。整个数据库的物理时间轴在此刻被强行“截断”。
- 读取字节偏移量: Checkpointer 读取当前 WAL 缓冲区的最末端写入指针。这是一个 64 位的无符号整数(例如
0/A000028),代表自数据库建库以来 WAL 日志不断追加写入的绝对字节总数。 - 释放排他锁: Checkpointer 将这个值记录在自己的内存变量中(记为 REDO LSN,即后续返回给备份工具的 Anchor LSN),随后立刻释放
WALInsertLock,允许业务进程继续写入。
【因果推导】
这一步确立了绝对的因果边界:因为获取 REDO LSN 时业务被短暂禁止写入,所以,所有在 REDO LSN 之前产生的修改,其对应的 8KB 数据页必然已经存在于 shared_buffers 中,且必定带有 BM_DIRTY 标记。
第三步:内存状态机分离(圈定目标数据页)
【前置状态】
时间轴已经划定,但业务进程恢复了写入,shared_buffers 中会不断产生新的、LSN 大于 REDO LSN 的脏页。系统必须把“旧脏页”和“新脏页”在物理上隔离开来。
【源码执行】
- 极速遍历: Checkpointer 进程开始以最高优先级遍历
shared_buffers中的所有页面描述符(Buffer Descriptors)。 - 状态判定与追加标记: 它检查每一个描述符。如果发现某个 8KB 页面当前携带有
BM_DIRTY(脏页)标记,Checkpointer 就会在这个描述符的内存结构中,强行写入一个额外的标志位:BM_CHECKPOINT_NEEDED。 - 跳过干净页: 如果页面没有
BM_DIRTY标记,直接跳过。
【因果推导】
因为第二步和第三步是严格串行执行的。所以在第二步划定 REDO LSN 之前就已经存在的脏页,在第三步遍历时,100% 会被系统打上 BM_CHECKPOINT_NEEDED 标记。至于遍历过程中业务新产生的脏页,即使漏打了标记也没有关系,因为它们的修改发生在 REDO LSN 之后,不属于本次备份的保障范围。
第四步:抹平 I/O 鸿沟(系统调用 write 与 fsync)
【前置状态】
现在,Checkpointer 进程拥有了一份明确的、带有 BM_CHECKPOINT_NEEDED 标记的目标页面清单。
【源码执行】
- 第一段 I/O (
write): Checkpointer 再次遍历缓冲区,针对每一个带有BM_CHECKPOINT_NEEDED标记的 8KB 页面,调用 Linux 内核的write()函数。这一步将 8192 字节从 PostgreSQL 的用户态内存,拷贝进入 Linux 操作系统的内核页缓存(Page Cache)。 - 第二段 I/O (
fsync): 写入系统缓存后,Checkpointer 紧接着对相应的文件描述符调用fsync()函数。这是一个强制阻塞的系统调用,它命令 Linux 内核:立刻指挥底层的磁盘阵列卡或 NVMe 硬盘控制器,将缓存中的电子信号真正刻录到物理磁盘的扇区上。 - 标记清除: 当
fsync()成功返回0,Checkpointer 会清除该页面上的BM_CHECKPOINT_NEEDED和BM_DIRTY标记。
【因果推导】
不管这一步执行耗费了 5 秒还是 50 秒,不管前端业务在这期间又修改了多少次数据,底层物理逻辑都不会错乱。因为执行 fsync() 的对象,是且仅是第三步圈定的那个静态集合。执行完毕后,REDO LSN 之前的所有修改,已经 100% 存在于物理磁盘上。
第五步:控制文件更新与通关凭证签发
【前置状态】
物理磁盘数据已经与 REDO LSN 严格对齐。需要将这个安全状态通知给等待的备份工具。
【源码执行】
- 更新全局控制文件: Checkpointer 进程将第二步获取的 REDO LSN 写入底层文件系统的
$PGDATA/global/pg_control文件中,并执行fsync。这标志着底层检查点动作的彻底闭环。 - 唤醒等待者: Checkpointer 向第一步中处于休眠状态的 Backend 进程发送唤醒信号。
- 强行修改内核参数: Backend 进程醒来后,立刻在内存中将
full_page_writes参数强制置为on。这是为了防止后续备份工具用pread()读取文件时,恰好遇到业务进程修改文件而导致的数据页物理撕裂(Torn Page)。 - 返回数据流: Backend 进程将第二步记录的 REDO LSN(此时称为 Anchor LSN)封装为文本数据流(
backup_label的内容),通过 TCP 网络返回给pg_probackup。
【最终后果】
pg_probackup 接收到这个 LSN,确认数据库底层的物理文件已经满足绝对一致性。随后,它开始利用 fork() 创建工作进程,直接向 Linux 操作系统发起 open() 和 pread() 调用,开始生吃底层的物理文件。
Show me the visualization
这就是从 API 触发,到内存锁定,再到磁盘物理扇区写入的严密源码级因果链。没有任何步骤可以倒置,每一行 C 语言代码都在为下一步的物理状态做绝对的保证。
了解 www.876873.xyz 的更多信息
订阅后即可通过电子邮件收到最新文章。
打下时空锚点::等您坐沙发呢!