检查点,通俗的理解就是数据库处于数据一致性,完整性的点。
通常创建检查点会需要一个漫长的过程,那么怎么保证数据的一致性和完整性呢?
从数据恢复(XLOG)的角度来看,检查点在XLOG文件中分为两个位置,一个是逻辑位置,一个是物理位置。
逻辑位置即开始位置,也是一致性位置,在这个位置之前已提交的事务,确保它们的事务状态和脏数据都已经写入持久化存储。
物理位置即结束位置,因为做检查点时,需要将逻辑位置之前已提交事务的事务状态和脏数据都写入持久化存储,这个需要一个过程,这些刷脏页面和CLOG的动作同样会产生XLOG,所以这一系列动作完成后,就是检查点结束的位置,即物理位置。
从逻辑角度来看,这两个XLOG位置实际是同一个位置,所以在做数据恢复时,先找到检查点的XLOG物理位置,然后根据这里的结束检查点时写入的XLOG信息找到逻辑位置,从逻辑位置开始,读取XLOG并实施xlog replay恢复,至少要恢复到XLOG物理位置才能确保数据库的一致性和完整性。
如图:
创建检查点示意图:
数据恢复示意图:
检查点调度的小结:
如果我们开启了检查点调度,默认是开启的,调度系数设置为0.5。
这个调度值到底是什么用意呢?
检查点的任务之一是刷脏块,例如有1000个脏块需要刷新,那么当刷到100个脏块时,progress=(100/1000)*0.5=0.05
如果这个时候,XLOG经历了10个文件,checkpoint_segments为100,也就是0.1
0.05<0.1,返回false,不休息。什么情况能休息? 当xlog经历个数比值小于等于0.05时才能休息,也就是发生在XLOG 5个或以内时。
如果调大调度系数到1,那么progress=(100/1000)*1=0.1,当xlog经历个数比值小于等于0.1时才能休息,也就是发生在XLOG10个或以内时。
现在可以理解为,调度系数就是休息区间系数,休息区间为checkpoint_segments和checkpoint_timeout。
调度系数越大,checkpointer休息区间越大,checkpointer可以经常休息,慢悠悠的fsync;
调度系数越小,checkpointer休息区间越小,checkpointer只能在最初的小范围内休息,超过后就要快马加鞭了。
checkpointer刷缓存主要分几个步骤,
1. 遍历shared buffer区,将当前SHARED BUFFER中脏块新增FLAG need checkpoint,
2. 遍历shared buffer区,将上一步标记为need checkpoint的块write到磁盘,WRITE前需要确保该buffer lsn前的XLOG已经fsync到磁盘,
3. 将前面的write sync到持久化存储。
具体耗时可以参考期间的探针,或者检查点日志输出。
本文要说的是full page write的影响,这个影响实际上是非常大的。
1. 写xlog的本质是这样的,数据库在刷shared buffer中的脏块前,必须确保脏块相关的REDO操作先写XLOG成功,才能刷脏块。
2. Postgresql的检查点有什么作用呢?
当数据库crash后需要恢复时,或者因为其他原因需要恢复时,从最后一个检查点开始,读取XLOG并实施恢复。
根据以上特征,为了保证数据的一致性,除了保证XLOG写成功,还要保证数据块是一致的(即刷数据块必须是原子操作,磁盘中这个数据块不能有新老数据共同存在)。
full_page_writes是在文件系统不能保证这种原子操作的前提下设计的,做法就是当刷脏数据块前,如果这个数据块是在检查点后第一次变脏的,那么需要在XLOG中记录下整个数据块的内容。那么在数据恢复时,即使刷脏块不是原子操作也没关系,因为WAL中记录了整个数据块的内容,恢复时会使用xlog中记录的full page覆盖数据文件中的块。
正因为这样,数据块越大,写full page带来的影响也愈大,而且检查点越频繁,WAL内容也会越多(因为FULL PAGE WRITE较多)。
那么怎么样才能安全关闭full_page_writes呢?
1. 文件系统能帮我们避免partial write。数据文件所在的文件系统可以确保不出现partial write,例如写一个32K的数据块,那么这个写操作必须是原子性的。例如zfs文件系统提供了这样的参数,允许用户打开full page write,如果文件系统打开了这种写,数据库就可以关闭full_page_writes。
2. 硬件支持full page write接口,例如FusionIO,以及宝存的PCI-E SSD硬盘产品,提供了相应的原子写API,(或者它们的原子操作本身就大于数据库的block size),只要Postgresql 的block_size小于等于硬件能提供的原子写SIZE,使用对应的API后,就可以关闭数据库的full page writes。
如果关闭full_page_writes后,因为硬件问题,或者其他问题导致操作系统Crash,并且在检查点后第一次成为脏块时出现了partial write(data corruption)。(例如32KB的数据块,其中有一些是未改写(old),一些已改写(new)) 那么怎么办?
如果真的这样了的话,查询数据时,遇到这种数据块可能报错,可以设置zero_damaged_pages来跳过这种数据块。另一方面,XLOG必须是顺序写入的,所以有一个锁保护,因此在write wal to wal buffer时,需要加这个锁。
也就是说,写WAL越慢,TPS会越低,即使是异步(因为wal异步虽然不需要等flush wal to disk,但是也要保证写wal buffer完成。)