He3DB 恢复过程源码分析系列

85 阅读10分钟

He3DB集群实例会有三个角色:primary、standby、push stanby,所有角色的实例宕机都会基于checkpoint点来恢复,所以首先我们会介绍下pg的checkpoint基本原理以及pg的宕机恢复原理,然后从primary宕机、standby宕机、push standby宕机这几种场景介绍如何恢复。(postgresql14.2)

一、什么是pg的checkpoint

checkpoint简单点说就是一个数据库事件,用来保证数据一致性和完整性,postgres在处理业务过程中,为了避免每次修改数据对应内存数据页强制刷盘产生随机写操作影响性能,数据库内存数据页修改会通过记wal日志方式变成顺序写,而数据页刷盘动作留给辅助进程bgwriter、checkpoint来完成。

正常情况下触发checkpoint有以下几种主要的情况:

  1. 手动执行checkpoint;

  2. 设置checkpoint_timeout时间到达

  3. Wal目录下wal文件大小达到max_wal_size

  4. 数据库正常关机

所有创建checkpoint动作的函数入口CreateCheckPoint,对源码我们暂时不做细致分析,源码简单分析下,checkpoint如何保证数据库一致性和完整性。

记录CreateCheckPoint操作结构为:

具体里面的类型什么作用这次不讲,因为里面很多变量为基于时间线判断是否能正常恢复使用,我们主要看下redo变量,这是记录createpoint事件日志在wal的位置。

CreateCheckPoint函数大致执行内容如下:

1. 记录checkpoint开始时redo日志在wal的具体位置与时间线等信息

Checkpoint发生后,checkPoint变量记录当前时间线,以及当前checkpoint要写wal日志的位置,为了避免多进程操作共享内存xlog写,使用WALInsertLockAcquireExclusive排它锁控制。

Insert->CurrBytePos为逻辑地址,日志最终写到文件中以page管理,会增加一些page头信息,curInsert为转化带page头部信息的物理地址。

checkPoint的redo、ThisTimeLineID会记录本次checkpoint开始的位置与时间线。

2. chekPoint.redo之前的内存dirty page刷盘

因为chekPoint.redo记录一致性点,所以必须将checkpoint之前的所有也进行刷盘操作,保证宕机时,恢复点可以以最近一次checkpoint.redo来向后重做。

3. 数据页脏页刷盘完成将checkpoint写wal日志,但有个问题WALInsertLockRelease();在写日志之前就释放了,会不会有问题。

没问题,checkpoint.redo记录checkpoint开始的点,后续恢复读取pg_control的checkpoint位置后从checkpoint.redo开始点重做日志。

使用pg_waldump查看如果有数据写入,发现wal中checkpoint日志与checkpoint开始有一段距离。

为了后期快速找到checkpoint在日志位置,我们记录在pg_control中checkPoint记录wal的checkpoint的位置,后期直接从这个wal日志位置读取checkPoint

4. 将checkpoint写pg_control文件中,为宕机时快速找到日志恢复点

小结:

整个checkpoint分析就结束了,checkpoint主要在为了在快速恢复与性能之间找个平衡点,当checkpoint超时、max_wal_size以及shared_buffer设置的非常大时,可以避免频繁的checkpoint刷盘操作,数据库性能会有比较大的提升,但是一旦宕机后,恢复速度会比较慢RTO会非常大。

二、pg如何进行宕机恢复的

pg宕机恢复主要基于checkpoint一致性点恢复

pg启动后主要通过 StartupDataBase()调用StartupXLOG()恢复到宕机点,因为恢复过程会有很多时间线考虑,这里我就不聊了,主要聊下普通恢复流程的情况

1. StartupXLOG函数读取checkpoint

record就存放wal日志读出的checkpoint,checkPointLoc为wal日志checkpoint信息,RedoStartLSN为需要从哪里开始重做

2. 正常关机情况下,不需要重做

对于RecPtr < checkPoint.redo是不可能状态,因为先记录checkpoint开始位置,数据刷盘完成后才写checkpoint日志,因此checkPoint.redo<=RecPtr,对于正常关机状态,所有其他进程先停止后,会给checkpoint进程发生shutdown类型checkpoint,因此此时pg_control中的checkPoint.redo==RecPtr,理论上不需要做recover。

3. 非正常关机情况下,需要一直重做到宕机点。

RedoStartLSN开始回放日志恢复数据库回放,一直循环回放到日志结束

/*
* main redo apply loop
*/
do
{
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->replayEndRecPtr = EndRecPtr;
XLogCtl->replayEndTLI = ThisTimeLineID;
SpinLockRelease(&XLogCtl->info_lck);
/* Now apply the WAL record itself */
RmgrTable[record->xl_rmid].rm_redo(xlogreader);
/*
* Update lastReplayedEndRecPtr after this record has been
* successfully replayed.
*/
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->lastReplayedEndRecPtr = EndRecPtr;
XLogCtl->lastReplayedTLI = ThisTimeLineID;
SpinLockRelease(&XLogCtl->info_lck);
/* Else, try to fetch the next WAL record */
record = ReadRecord(xlogreader, LOG, false);
}while (record != NULL);

ReadRecord-》XLogReadRecord-》DecodeXLogRecord函数中读取wal日志到xlogreader,rm_redo进行回放,rmgrlist.h中xlog_redo、smgr_redo就是不同日志类型的回放函数。

小结:

因为这部分只是简单介绍宕机恢复过程,没有去写pg standby宕机恢复,主要有两种场景,正常关机情况下,pg_control记录的就是结束点checkpoint,所有数据页在关机过程都已经刷盘,非正常关机过程,需要根据最近一次checkpoint记录重做直到日志结束。

三、Primary宕机恢复

未完待续

四、Standby宕机恢复

未完待续

五、Push standby宕机恢复

未完待续

备注:调试过程中的一些坑。

  1. psql客户端执行checkpoint命令,最终是与之对应的postgres连接进程给checkpoint进程发信号SIGINT信号让辅助进程checkpoint处理checkpoint相关动作,但是继续调试checkpoint辅助进程,发现永远走不到ReqCheckpointHandler,gdb时checkpoint进程明明收到SIGINT却不处理,但调用不到CreateCheckPoint,最后看到No Pass发现原来GDB不处理这个信号导致。

后在收到SIGINT,gdb执行handle SIGINT pass,让gdb处理这个信号后,可以正常调用checkpoint

He3DB集群实例会有三个角色:primary、standby、push stanby,所有角色的实例宕机都会基于checkpoint点来恢复,所以首先我们会介绍下pg的checkpoint基本原理以及pg的宕机恢复原理,然后从primary宕机、standby宕机、push standby宕机这几种场景介绍如何恢复。(postgresql14.2)

一、什么是pg的checkpoint

checkpoint简单点说就是一个数据库事件,用来保证数据一致性和完整性,postgres在处理业务过程中,为了避免每次修改数据对应内存数据页强制刷盘产生随机写操作影响性能,数据库内存数据页修改会通过记wal日志方式变成顺序写,而数据页刷盘动作留给辅助进程bgwriter、checkpoint来完成。

正常情况下触发checkpoint有以下几种主要的情况:

  1. 手动执行checkpoint;

  2. 设置checkpoint_timeout时间到达

  3. Wal目录下wal文件大小达到max_wal_size

  4. 数据库正常关机

所有创建checkpoint动作的函数入口CreateCheckPoint,对源码我们暂时不做细致分析,源码简单分析下,checkpoint如何保证数据库一致性和完整性。

记录CreateCheckPoint操作结构为:

具体里面的类型什么作用这次不讲,因为里面很多变量为基于时间线判断是否能正常恢复使用,我们主要看下redo变量,这是记录createpoint事件日志在wal的位置。

CreateCheckPoint函数大致执行内容如下:

1. 记录checkpoint开始时redo日志在wal的具体位置与时间线等信息

Checkpoint发生后,checkPoint变量记录当前时间线,以及当前checkpoint要写wal日志的位置,为了避免多进程操作共享内存xlog写,使用WALInsertLockAcquireExclusive排它锁控制。

Insert->CurrBytePos为逻辑地址,日志最终写到文件中以page管理,会增加一些page头信息,curInsert为转化带page头部信息的物理地址。

checkPoint的redo、ThisTimeLineID会记录本次checkpoint开始的位置与时间线。

2. chekPoint.redo之前的内存dirty page刷盘

因为chekPoint.redo记录一致性点,所以必须将checkpoint之前的所有也进行刷盘操作,保证宕机时,恢复点可以以最近一次checkpoint.redo来向后重做。

3. 数据页脏页刷盘完成将checkpoint写wal日志,但有个问题WALInsertLockRelease();在写日志之前就释放了,会不会有问题。

没问题,checkpoint.redo记录checkpoint开始的点,后续恢复读取pg_control的checkpoint位置后从checkpoint.redo开始点重做日志。

使用pg_waldump查看如果有数据写入,发现wal中checkpoint日志与checkpoint开始有一段距离。

为了后期快速找到checkpoint在日志位置,我们记录在pg_control中checkPoint记录wal的checkpoint的位置,后期直接从这个wal日志位置读取checkPoint

4. 将checkpoint写pg_control文件中,为宕机时快速找到日志恢复点

小结:

整个checkpoint分析就结束了,checkpoint主要在为了在快速恢复与性能之间找个平衡点,当checkpoint超时、max_wal_size以及shared_buffer设置的非常大时,可以避免频繁的checkpoint刷盘操作,数据库性能会有比较大的提升,但是一旦宕机后,恢复速度会比较慢RTO会非常大。

二、pg如何进行宕机恢复的

pg宕机恢复主要基于checkpoint一致性点恢复

pg启动后主要通过 StartupDataBase()调用StartupXLOG()恢复到宕机点,因为恢复过程会有很多时间线考虑,这里我就不聊了,主要聊下普通恢复流程的情况

1. StartupXLOG函数读取checkpoint

record就存放wal日志读出的checkpoint,checkPointLoc为wal日志checkpoint信息,RedoStartLSN为需要从哪里开始重做

2. 正常关机情况下,不需要重做

对于RecPtr < checkPoint.redo是不可能状态,因为先记录checkpoint开始位置,数据刷盘完成后才写checkpoint日志,因此checkPoint.redo<=RecPtr,对于正常关机状态,所有其他进程先停止后,会给checkpoint进程发生shutdown类型checkpoint,因此此时pg_control中的checkPoint.redo==RecPtr,理论上不需要做recover。

3. 非正常关机情况下,需要一直重做到宕机点。

RedoStartLSN开始回放日志恢复数据库回放,一直循环回放到日志结束

/*
* main redo apply loop
*/
do
{
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->replayEndRecPtr = EndRecPtr;
XLogCtl->replayEndTLI = ThisTimeLineID;
SpinLockRelease(&XLogCtl->info_lck);
/* Now apply the WAL record itself */
RmgrTable[record->xl_rmid].rm_redo(xlogreader);
/*
* Update lastReplayedEndRecPtr after this record has been
* successfully replayed.
*/
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->lastReplayedEndRecPtr = EndRecPtr;
XLogCtl->lastReplayedTLI = ThisTimeLineID;
SpinLockRelease(&XLogCtl->info_lck);
/* Else, try to fetch the next WAL record */
record = ReadRecord(xlogreader, LOG, false);
}while (record != NULL);

ReadRecord-》XLogReadRecord-》DecodeXLogRecord函数中读取wal日志到xlogreader,rm_redo进行回放,rmgrlist.h中xlog_redo、smgr_redo就是不同日志类型的回放函数。

小结:

因为这部分只是简单介绍宕机恢复过程,没有去写pg standby宕机恢复,主要有两种场景,正常关机情况下,pg_control记录的就是结束点checkpoint,所有数据页在关机过程都已经刷盘,非正常关机过程,需要根据最近一次checkpoint记录重做直到日志结束。

三、Primary宕机恢复

未完待续

四、Standby宕机恢复

未完待续

五、Push standby宕机恢复

未完待续

备注:调试过程中的一些坑。

  1. psql客户端执行checkpoint命令,最终是与之对应的postgres连接进程给checkpoint进程发信号SIGINT信号让辅助进程checkpoint处理checkpoint相关动作,但是继续调试checkpoint辅助进程,发现永远走不到ReqCheckpointHandler,gdb时checkpoint进程明明收到SIGINT却不处理,但调用不到CreateCheckPoint,最后看到No Pass发现原来GDB不处理这个信号导致。

image.png

后在收到SIGINT,gdb执行handle SIGINT pass,让gdb处理这个信号后,可以正常调用checkpoint

image.png