数据库异常关闭时,数据库关闭时来不及或者没机会做checkpoint,则需要从上一个一致性检查的开始恢复。
PostgreSQL备机checkpoint是不能产生checkpoint WAL的,因为如果写这样类型的checkpoint的话,就会将接收的WAL打乱,那么日志将混乱,回放会出问题。
那么问题来了,备机支持checkpoint吗?他的checkpoint怎么做的?
PostgreSQL为了缩短恢复时间,备机上也支持checkpoint,即CreateRestartPoint。但是其pg_control文件的checkpoint记录的位点是从主机传过来WAL里面的checkpoint记录位置。
1、备机回放
StartupXLOG
do{
...
RmgrTable[record->xl_rmid].rm_redo(xlogreader);//回放
...
record = ReadRecord(xlogreader, InvalidXLogRecPtr, LOG, false);//读取一个xlog
} while (record != NULL);
2、回放函数
void
xlog_redo(XLogReaderState *record)
{
...
else if (info == XLOG_CHECKPOINT_SHUTDOWN){
...
memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
...
RecoveryRestartPoint(&checkPoint);
}else if (info == XLOG_CHECKPOINT_ONLINE){
...
memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
...
RecoveryRestartPoint(&checkPoint);
}
...
}
3、RecoveryRestartPoint
static void
RecoveryRestartPoint(const CheckPoint *checkPoint)
{
...
SpinLockAcquire(&XLogCtl->info_lck);
XLogCtl->lastCheckPointRecPtr = ReadRecPtr;//ReadRecPtr为读取checkpoint记录后的位置
XLogCtl->lastCheckPointEndPtr = EndRecPtr;
XLogCtl->lastCheckPoint = *checkPoint;
SpinLockRelease(&XLogCtl->info_lck);
}
4、ReadRecPtr赋值
ReadRecord
for (;;)
{
char *errormsg;
record = XLogReadRecord(xlogreader, RecPtr, &errormsg);
ReadRecPtr = xlogreader->ReadRecPtr;
EndRecPtr = xlogreader->EndRecPtr;
...
}
5、备机createcheckpoint
bool
CreateRestartPoint(int flags)
{
LWLockAcquire(CheckpointLock, LW_EXCLUSIVE);
SpinLockAcquire(&XLogCtl->info_lck);
lastCheckPointRecPtr = XLogCtl->lastCheckPointRecPtr;//checkpoint的位置来自XLogCtl->lastCheckPointRecPtr
lastCheckPointEndPtr = XLogCtl->lastCheckPointEndPtr;
lastCheckPoint = XLogCtl->lastCheckPoint;
SpinLockRelease(&XLogCtl->info_lck);
...
if (XLogRecPtrIsInvalid(lastCheckPointRecPtr) || lastCheckPoint.redo <= ControlFile->checkPointCopy.redo){
//回放了最后一个checkpoint记录后,备机再次手动执行checkpoint命令
UpdateMinRecoveryPoint(InvalidXLogRecPtr, true);
if (flags & CHECKPOINT_IS_SHUTDOWN){
LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
ControlFile->state = DB_SHUTDOWNED_IN_RECOVERY;
ControlFile->time = (pg_time_t) time(NULL);
UpdateControlFile();
LWLockRelease(ControlFileLock);
}
LWLockRelease(CheckpointLock);
return false;
}
...
LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
if (ControlFile->state == DB_IN_ARCHIVE_RECOVERY && ControlFile->checkPointCopy.redo < lastCheckPoint.redo){
ControlFile->prevCheckPoint = ControlFile->checkPoint;
ControlFile->checkPoint = lastCheckPointRecPtr;//checkpoint的位置
ControlFile->checkPointCopy = lastCheckPoint;
ControlFile->time = (pg_time_t) time(NULL);
...
if (flags & CHECKPOINT_IS_SHUTDOWN)
ControlFile->state = DB_SHUTDOWNED_IN_RECOVERY;
UpdateControlFile();
}
...
return true;
}
6、备机shutdown
void
ShutdownXLOG(int code, Datum arg)
{
WalSndInitStopping();
WalSndWaitStopping();
if (RecoveryInProgress())//备机写checkpoint
CreateRestartPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
else
{
if (XLogArchivingActive() && XLogArchiveCommandSet())
RequestXLogSwitch(false);
CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
}
ShutdownCLOG();
ShutdownCommitTs();
ShutdownSUBTRANS();
ShutdownMultiXact();
}
7、总结
PostgreSQL备库也可以写检查点,目的是避免每次重启备库都需要从上一个检查点(由主库产生,在WAL中回放出来的)APPLY后面所有的WAL。但是他记录的checkpoint位点是从主库传过来的。这样的话就有问题了,如果主机很长时间都没有做checkpoint了,备机即使正常关闭,重启时,也会从上一个checkpoint开始恢复,这样也会恢复很长时间;并且多次重启也需要从上一次checkpoint开始重复恢复。