探索 PostgreSql 中的 checkpoint 机制

checkpoint 是 PostgreSql 中一个非常重要的概念,本文将详细的介绍什么是 checkpoint,为什么要有 checkpoint,以及 checkpoint 的运作原理。

什么是 checkpoint

大家在玩游戏的时候经常会需要存档,而这个存档点就是由 checkpoint 单词翻译而来的。

Postgresql 官方文档中,checkpoint 的定义如下:

A checkpoint is a point in the write-ahead log sequence at which all data files have been updated to reflect the information in the log. All data files will be flushed to disk.

翻译成中文就是:checkpoint 是 WAL(write-ahead log) 日志中的一个位点,在这个点位之前数据库中的所有数据都和 WAL 日志中反映的信息相同。

说起来有些绕口,并且有些困惑:WAL 日志中反应的信息是什么?数据库中的所有数据为什么会和 WAL 日志中反应的不同?

带着这个问题,我们来看接下来的部分。

为什么要有 checkpoint

在解释这个问题之前,我们先来补充一点背景。

PostgreSQL 如何写数据

以一条 SQL 语句为例:INSERT INTO tbl VALUES(1);
其执行流程如下图所示

image.png

  1. 将 INSERT 1 这个操作写入 WAL 日志中
  2. 修改 shared buffer 中该页的信息(如果该页不在buffer中,则从磁盘去取)
  3. background 进程会在某个时刻将 shared buffer 中的数据刷到磁盘(图中红色标出)。但是这并不是立刻发生的,而是一个异步操作。

这也就回答了上述中的第一个问题,WAL 日志反应的信息:WAL 日志可以看成是 redo log,将所有的操作原样记载在 WAL 日志中。但实际上 WAL 日志是物理日志,记录的是对某个文件某个块的修改。

但是这时候又衍生出了几个问题:为什么要有 WAL 日志,它能干啥?

PostgreSQL 故障恢复

这一章我们就来讲讲 WAL 日志的作用。为了方便描述,这里将 WAL 中的信息简化,增加可读性。
还是刚才那张图,假如 background 进程正在将 shared buffer 中的数据刷到磁盘,还没刷完的时候你电脑坏了。这时候你上一条 INSERT INTO tbl VALUES(1)的 SQL 仿佛失效了。

image.png

过了一会,你的电脑重启了,为了让你上一条 SQL 不白写,PG 会进入恢复模式,惊喜的发现 WAL 日志里面记录了上一条 SQL 的 redo 信息,遂直接重放,数据又重新回到了数据库中~

这时候我们就明白了 WAL 日志作为 redo log 的作用。但是又出现了两个问题:

  1. 我怎么知道数据库什么时候崩的,WAL 日志应该从哪开始重放呢?
  2. WAL 日志就这么一直写下去,子子孙孙无穷匮也,磁盘不炸了吗?

checkpoint 出场

终于,我们引入了主角:checkpoint 。之前我们说过,checkpoint 是 WAL 日志中的位点,那么这个位点的意义是什么呢:

该位点之前所有 shared buffer 中的脏页均被刷入到存储

那么这个位点又是怎么产生的呢?

没错,是由 checkpoint 这个操作产生的。尽管说起来有些绕口,checkpoint 一会是名词一会是动词,不过我们可以这么理解:checkpoint 操作会往 WAL 日志里写 checkpoint 位点

底下我们来看 checkpoint 操作的过程

image.png

  1. checkpoint 操作首先记录下 checkpoint 的开始位置,记录为 redo point(重做位点)
  2. checkpoint 将 shared buffer 中的数据刷到磁盘里面去
  3. 这时候数据库又来了一条 SQL insert 3
  4. checkpoint 刷脏结束,redo point 之前的数据均已被刷到磁盘存储(数据1和2)
  5. 这时候在 WAL 日志里面记录 checkpoint 位点(红色),表明 checkpoint 操作结束。checkpoint 位点会记录相关信息,比如 redo point 的值(从哪开始重做)
  6. 将最新的 checkpoint 位点记录在 pg_control 文件中

image.png
这时候假如开始数据库恢复,那么数据库会从 pg_control 文件中找到最新的 checkpoint 位置,再从 checkpoint 找到 redo point 的位置,开始重放日志。

不难看出,1 和 2 这两个数据在 checkpoint 中已经持久化到磁盘存储,WAL 日志中也只有 INSERT 3 操作需要重放。

至此,我们就回答了上一小节的最后一个问题:WAL 日志应该从哪开始重放呢。

还剩下一个问题,WAL 日志一直写下去吗,不清理吗?

我们注意到,checkpoint 的时候已经将 redo point 之前的所有数据都落盘了,那 redo point 之前的所有 WAL 日志都已经没有用了(下次宕机的时候这部分数据已经被持久化了,不属于要恢复的数据),可以请理了。

因此,checkpoint 的第二个作用就是用于标记这个位点之前的 WAL 日志都可以被回收了。

最后,我们来总结一下 checkpoint 的两大作用(重要的事情说三遍):

  1. checkpoint中记录了 redo point,标记 redo point 之前的数据均已刷脏,完成持久化存储
  2. 标记 redo point 之前的 WAL 日志可以被清理回收

checkpoint 在代码中的实现

我们以 PostgreSQL 最新的版本 13.2 为例,简要的讲述一下代码里 checkpoint 的实现方式。为了降低理解的难度,下面的代码会做一些简化,去掉加锁等内容。

首先,我们定位到 xlog.c 文件的 CreateCheckPoint 函数,顾名思义,这个函数就是用来完成一次 checkpoint 操作的。

按照,我们的逻辑,checkpoint 应该首先创造一个 redo point。

curInsert = XLogBytePosToRecPtr(Insert->CurrBytePos);
.......
.......
freespace = INSERT_FREESPACE(curInsert);
if (freespace == 0)
{
        if (XLogSegmentOffset(curInsert, wal_segment_size) == 0)
                curInsert += SizeOfXLogLongPHD;
        else
                curInsert += SizeOfXLogShortPHD;
}
checkPoint.redo = curInsert;

上述代码的作用就是找出当前的最后一个 XLOG record 位置,并计算出下一个合法的 XLOG record 位置。

接着在 CheckPointGuts 函数中完成刷脏操作。

/*
 * Flush all data in shared memory to disk, and fsync
 *
 * This is the common code shared between regular checkpoints and
 * recovery restartpoints.
 */
static void
CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
{
	CheckPointCLOG();
	CheckPointCommitTs();
	CheckPointSUBTRANS();
	CheckPointMultiXact();
	CheckPointPredicate();
	CheckPointRelationMap();
	CheckPointReplicationSlots();
	CheckPointSnapBuild();
	CheckPointLogicalRewriteHeap();
	CheckPointBuffers(flags);	/* performs all required fsyncs */
	CheckPointReplicationOrigin();
	/* We deliberately delay 2PC checkpointing as long as possible */
	CheckPointTwoPhase(checkPointRedo);
}

刷脏操作结束之后,就应该把 checkpoint 作为一条日志写入 WAL 了。

/*
 * Now insert the checkpoint record into XLOG.
 */
XLogBeginInsert();
XLogRegisterData((char *) (&checkPoint),sizeof(checkPoint));
recptr = XLogInsert(RM_XLOG_ID, shutdown ? XLOG_CHECKPOINT_SHUTDOWN : XLOG_CHECKPOINT_ONLINE);
XLogFlush(recptr);

到这里已经在 WAL 日志中写下了 checkpoint 这条记录,最后要做的就是在 pg_control 文件中更新 checkpoint 位置的相关信息。

/*
 * Update the control file.
 */
if (shutdown)
        ControlFile->state = DB_SHUTDOWNED;
ControlFile->checkPoint = ProcLastRecPtr;
ControlFile->checkPointCopy = checkPoint;
ControlFile->time = (pg_time_t) time(NULL);

/* crash recovery should always recover to the end of WAL */
ControlFile->minRecoveryPoint = InvalidXLogRecPtr;
ControlFile->minRecoveryPointTLI = 0;

ControlFile->unloggedLSN = XLogCtl->unloggedLSN;

UpdateControlFile(); // 更新 control file

checkpoint 什么时候运行

这部分不属于本文的重点内容,因此就大概提一下。

  1. 系统会定期运行 checkpoint,这个时间间隔可以通过参数设置;
  2. 手动运行 checkpoint,直接进行一次 checkpoint 操作。
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总想玩世不恭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值