checkpoint又名检查点,一般checkpoint会将某个时间点之前的脏数据全部刷新到磁盘,以实现数据的一致性与完整性。目前各个流行的关系型数据库都具备checkpoint功能,其主要目的是为了缩短崩溃恢复时间,以oracle为例,在进行数据恢复时,会以最近的checkpoint为参考点执行事务前滚。而在wal机制的浅析中,也提过postgresql在崩溃恢复时会以最近的checkpoint为基础,不断应用这之后的wal日志。
在xlog.h文件中,有如下代码对checkpoint进行了相应的分类:
也就是说,以下几种情况会触发数据库操作系统做检查点操作:
超级用户(其他用户不可)执行checkpoint命令
数据库shutdown
数据库recovery完成
xlog日志量达到了触发checkpoint阈值
周期性地进行checkpoint
需要刷新所有脏页
为了能够周期性的创建检查点,减少崩溃恢复时间,同时合并i/o,postgresql提供了辅助进程checkpointer。它会对不断检测周期时间以及上面的xlog日志量阈值是否达到,而周期时间以及xlog日志量阈值可以通过参数来设置大小,接下来介绍下与checkpoints相关的参数。
checkpoint_segments
wal log的最大数量,系统默认值是3。超过该数量的wal日志,会自动触发checkpoint。
checkpoint_timeout
系统自动执行checkpoint之间的最大时间间隔。系统默认值是5分钟。
checkpoint_completion_target
该参数表示checkpoint的完成时间占两次checkpoint时间间隔的比例,系统默认值是0.5,也就是说每个checkpoint需要在checkpoints间隔时间的50%内完成。
checkpoint_warning
系统默认值是30秒,如果checkpoints的实际发生间隔小于该参数,将会在server log中写入写入一条相关信息。可以通过设置为0禁用。
当postgresql触发checkpoint发生的条件后,会调用createcheckpoint函数创建具体的检查点,具体过程如下:
遍历所有的数据buffer,将脏页块状态从bm_dirty改为bm_checkpoint_needed,表示这些脏页将要被checkpoint刷新到磁盘
调用checkpointguts函数将共享内存中的脏页刷出到磁盘
生成新的checkpoint 记录写入到xlog中
更新控制文件、共享内存里xlogctl的检查点相关成员、检查点的统计信息结构
postgresql 控制文件pg_control里存储的数据是一个controlfiledata结构,具体如下:
其中,minrecoverypoint和minrecoverypointtli确定数据库启动前,如果做归档恢复,我们必须恢复到的最小检查点。其中minrecoverypoint指向该检查点对应的lsn位置,minrecoverypointtli指向该检查点对应的时间线。其具体的用法,我们将在之后的postgresql崩溃恢复中分析,这里我们主要分析下postgresql中的时间线概念。
postgresql中wal日志段名称,由时间线id、日志id、段id的八位16进制数依次构成。例如:
00000001
0000008f
时间线timelineid
逻辑日志id
段id
其中时间线是作为日志段名称的一部分,用来标识数据库归档恢复后产生的一系列新的wal记录。在每次归档恢复完成后,都会产生一个新的时间线和新的wal日志段。时间线可以理解为平行时空中的各个平行宇宙,我们完全可以恢复到某个时间点,重开一条时间线,继续进行数据操作,这样就可以实现完全的ptir。
在postgresql中,一个新的时间线产生,系统伴随它会建立一个以“新timelineid+.history”命名的“时间线历史”文件(timeline history),它是一个类似于txt的文件,其中包含所有在当前时间线以前的时间线,同时记录了每个时间线开始时的第一个wal段,这样数据库恢复时,通过读取时间线历史文件文件,根据目标时间点可以快速找到正确的日志段文件。如果上一次恢复是恢复到具体某时刻,在时间线历史文件中还会记录该时间线对应的具体时刻。
在pitr恢复时,无需扫描所有wal日志文件,而是通过时间线直接定位某个wal段,再从该wal段中找到符合该时间点的日志记录,这样就大大提高了效率。同时数据库恢复时,默认是沿着基备份开始时的时间点进行,即利用从基备份完成后产生的第一个日志段文件做恢复,如果想恢复到指定时间点(时间线),需要在recovery.conf配置文件中设置目标时间线(target timeline id),但是target timeline id不能指定为基备份以前的时间线。
checkpointguts函数将共享内存里的数据刷出并文件同步到磁盘,具体定义如下:
可以看出,checkpointguts根据不同的缓存类型,把clog、subtrans、multixact、predicate、relationmap、buffer(数据文件)和twophase相应缓存分别调用不同的方法,将缓存刷到磁盘中:
提交事务日志管理器的方法checkpointclog
子事务日志管理器的方法checkpointsubtrans
多事务日志管理器的方法checkpointmultixact
支持序列化事务隔离级别的谓词锁模块的方法checkpointpredicate
目录/系统表到文件节点映射模块的方法checkpointrelationmap
缓存管理器的方法checkpointbuffers
两阶段提交模块的方法checkpointtwophase
其中,前四个函数最后都调用了slru模块的simplelruflush(简单最近最少使用)方法,把相应的共享内存数据写到磁盘,并通过调用pg_fsync方法把相应文件刷到磁盘上对应文件。
后二个函数没有使用slru算法,直接调用pg_fsync方法把相应文件刷到磁盘上对应文件。
而目录/系统表到文件节点映射模块的方法checkpointrelationmap,会将共享内存里系统表和对应物理文件映射的map文件刷到磁盘。
至此,我们大体了解了checkpoint的用法和整个实现过程,但是还需要对一些特别的地方做出说明。
每个检查点后,第一次数据页的变化会导致整个页面会被记录在xlog日志中
检查点的开销比较高,可以用checkpoint_warning自检,相应调大checkpoint_segments
检查点的位置保存在文件 pg_control,pg_control文件被损坏可能会导致数据库不可用
其中,如果pg_control文件损坏,在数据库崩溃恢复时可能出现一些问题,这些问题我们将在分析postgresql数据库崩溃恢复时具体分析。