OpenGauss线程管理-检查点线程-Checkpointer
检查点线程处理所有检查点。 检查点会在自上一个检查点后经过一定时间后自动分派,并且它也可以发出信号以执行请求的检查点。 (要求每隔这么多 WAL 段设置一个检查点的 GUC 参数是通过在填充 WAL 段时让后端发出信号来实现的;检查点本身并不注意条件。)
术语解释
- 检查点:检查点是一个数据库事件,用来减少崩溃恢复(Crash Recovery)时间。检查点种类包含四种,分别为MMDB的检查点、非失真检查点、失真检查点和日志驱动检查点。
- smgr(PG存储介质管理器):负责统管各种介质管理器,会根据上层的请求选择具体的截止管理器进行操作。
Checkpointer线程
路径:openGauss-server/src/gausskernel/process/postmaster/checkpointer.cpp
在启动子进程完成后,检查点由 postmaster 启动,或者如果我们正在执行归档恢复,则在恢复开始时立即启动检查点。 它一直保持活动状态,直到postmaster命令它终止。 正常终止是由 SIGUSR2,它指示检查点执行关闭检查点,然后退出(0)。 (所有后端必须在发出 SIGUSR2 之前停止!)紧急终止是由 SIGQUIT; 像任何后端一样,检查点将简单地中止并在 SIGQUIT 上退出。
如果检查点意外退出,postmaster 会将其视为后端崩溃:共享内存可能已损坏,因此应通过 SIGQUIT 杀死剩余的后端,然后开始恢复循环。 (即使共享内存没有损坏,我们也丢失了关于哪些文件需要为下一个检查点进行 fsync 的信息,因此需要强制重新启动系统。)
在主线程中的使用
在GaussDbAuxiliaryThreadMain中通过thread_role进入checkpointer线程
代码理解
用于检查点和后端之间通信的共享内存区域 ckpt 计数器允许后端监视它们发送的检查点请求的完成情况。 以下是它的工作原理:
在检查点开始时,检查点读取(并清除)请求标志并增加 ckpt_started,同时保持 ckpt_lck。 检查点完成后,检查点将 ckpt_done 设置为等于 ckpt_started。 在检查点失败时,检查点增加 ckpt_failed 并将 ckpt_done 设置为等于 ckpt_started。
CheckpointerMain
检查点进程的主入口点。这是从 AuxiliaryProcessMain 调用的,它已经创建了基本的执行环境,但尚未启用信号。
void CheckpointerMain(void)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext checkpointer_context;
bool bgwriter_first_startup = true;
t_thrd.checkpoint_cxt.CheckpointerShmem->checkpointer_pid = t_thrd.proc_cxt.MyProcPid;
u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT
? u_sess->attr.attr_storage.incrCheckPointTimeout
: u_sess->attr.attr_storage.fullCheckPointTimeout;
ereport(
LOG, (errmsg("checkpointer started, CheckPointTimeout is %d", u_sess->attr.attr_storage.CheckPointTimeout)));
/*
* 正确接受或忽略postmaster可能发送给我们的信号
*
* 注意:我们故意忽略 SIGTERM,因为在标准的 Unix 系统关闭周期中,
* init 将立即对所有进程进行 SIGTERM。 我们想等待后端退出,
* 然后 postmaster 会告诉我们可以关闭(通过 SIGUSR2)。
*/
(void)gspqsignal(SIGHUP, ChkptSigHupHandler); /* 设置标志以读取配置文件 */
(void)gspqsignal(SIGINT, ReqCheckpointHandler); /* 请求检查点 */
(void)gspqsignal(SIGTERM, SIG_IGN); /* 忽略 SIGTERM */
(void)gspqsignal(SIGQUIT, chkpt_quickdie); /* 硬崩溃时间 */
(void)gspqsignal(SIGALRM, SIG_IGN);
(void)gspqsignal(SIGPIPE, SIG_IGN);
(void)gspqsignal(SIGUSR1, chkpt_sigusr1_handler);
(void)gspqsignal(SIGUSR2, ReqShutdownHandler); /* 请求关闭 */
/*
* 重置一些postmaster接受但不在这里的信号
*/
(void)gspqsignal(SIGCHLD, SIG_DFL);
(void)gspqsignal(SIGTTIN, SIG_DFL);
(void)gspqsignal(SIGTTOU, SIG_DFL);
(void)gspqsignal(SIGCONT, SIG_DFL);
(void)gspqsignal(SIGWINCH, SIG_DFL);
/* 我们始终允许 SIGQUIT (quickdie) */
sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);
/*
* 初始化,以便第一个时间驱动的事件在正确的时间发生。
*/
t_thrd.checkpoint_cxt.last_checkpoint_time = t_thrd.checkpoint_cxt.last_truncate_log_time =
t_thrd.checkpoint_cxt.last_xlog_switch_time = (pg_time_t)time(NULL);
/*
* 创建一个资源所有者来跟踪我们的资源(目前只有缓冲引脚)。
*/
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Checkpointer",
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE));
/*
* 创建一个我们将在其中完成所有工作的内存上下文。我们这样做是为了在错误恢复期间重置上下文,
* 从而避免可能的内存泄漏。 以前这段代码只在 t_thrd.top_mem_cxt 中运行,
* 但重置它是一个非常糟糕的主意。
*/
checkpointer_context = AllocSetContextCreate(t_thrd.top_mem_cxt,
"Checkpointer",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(checkpointer_context);
/*
* 如果遇到异常,则在此处继续处理。
*
* 请参阅 postgres.c 中有关此编码设计的注释。
*/
int curTryCounter;
int* oldTryCounter = NULL;
if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
gstrace_tryblock_exit(true, oldTryCounter);
/*
* 出现任何错误后关闭所有打开的文件。 这在 Windows 上很有帮助,
* 在 Windows 中保持已删除的文件打开会导致各种奇怪的错误。
* 目前尚不清楚我们在其他地方是否需要它,但不应该受到伤害。
*/
/* 由于不使用 PG_TRY,必须手动重置错误堆栈 */
t_thrd.log_cxt.error_context_stack = NULL;
t_thrd.log_cxt.call_stack = NULL;
/* 清理时防止中断 */
HOLD_INTERRUPTS();
/* 向服务器日志报告错误 */
EmitErrorReport();
/* 中止异步 io,必须在 LWlock 释放之前 */
AbortAsyncListIO();
#ifdef ENABLE_MOT
/* 清理其他存储引擎中的任何剩余物(如果采取了 MOT 快照锁,则释放) */
CallCheckpointCallback(EVENT_CHECKPOINT_ABORT, 0);
#endif
/* 释放 lsc 持有的资源 */
AtEOXact_SysDBCache(false);
/*
* 这些操作实际上只是AbortTransaction()的最小子集。
* 我们在checkpointer中不需要担心太多资源,但我们有LWLocks、缓冲区和临时文件。
*/
LWLockReleaseAll();
pgstat_report_waitevent(WAIT_EVENT_END);
AbortBufferIO();
UnlockBuffers();
/* 缓冲引脚在此处释放: */
ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true);
/* 我们不必为其他ResourceOwnerRelease阶段操心 */
AtEOXact_Buffers(false);
AtEOXact_SMgr();
AtEOXact_Files();
AtEOXact_HashTables(false);
/* 警告所有等待的后端检查点失败。 */
if (t_thrd.checkpoint_cxt.ckpt_active) {
/* 使用volatile指针防止代码重排 */
volatile CheckpointerShmemStruct* cps = t_thrd.checkpoint_cxt.CheckpointerShmem;
SpinLockAcquire(&cps->ckpt_lck);
cps->ckpt_failed++;
cps->ckpt_done = cps->ckpt_started;
SpinLockRelease(&cps->ckpt_lck);
t_thrd.checkpoint_cxt.ckpt_active = false;
}
/*
* 现在返回正常的顶级上下文,并清除ErrorContext以备下次使用。
*/
MemoryContextSwitchTo(checkpointer_context);
FlushErrorState();
/* 刷新顶级上下文中的所有泄漏数据 */
MemoryContextResetAndDeleteChildren(checkpointer_context);
/* 现在我们可以再次允许中断 */
RESUME_INTERRUPTS();
/*
* 出现任何错误后至少休眠1秒。写入错误可能会重复出现,我们不希望尽快填写错误日志。
*/
pg_usleep(1000000L);
}
oldTryCounter = gstrace_tryblock_entry(&curTryCounter);
/* 我们现在可以处理安装(错误) */
t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf;
/*
* 解除封锁信号(当postmaster fork我们时,信号被封锁)
*/
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
/*
* 在恢复期间使用恢复目标时间线ID */
if (RecoveryInProgress())
t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI();
/*
* 确保为配置正确设置了所有共享内存值。
* 在这里这样做可以确保没有来自其他并发更新程序的竞争条件。
*/
UpdateSharedMemoryConfig();
/*
* 宣传我们的门闩,后端可以在我们睡觉时唤醒我们。
*/
g_instance.proc_base->checkpointerLatch = &t_thrd.proc->procLatch;
pgstat_report_appname("CheckPointer");
pgstat_report_activity(STATE_IDLE, NULL);
/*
* 永远循环
*/
for (;;) {
bool do_checkpoint = false;
bool do_dirty_flush = false;
int flags = 0;
pg_time_t now;
int elapsed_secs;
int cur_timeout;
int rc;
/* 清除所有已挂起的唤醒 */
ResetLatch(&t_thrd.proc->procLatch);
pgstat_report_activity(STATE_RUNNING, NULL);
/*
* 处理最近收到的任何请求或信号。
*/
CkptAbsorbFsyncRequests();
if (t_thrd.checkpoint_cxt.got_SIGHUP) {
t_thrd.checkpoint_cxt.got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT
? u_sess->attr.attr_storage.incrCheckPointTimeout
: u_sess->attr.attr_storage.fullCheckPointTimeout;
/*
* Checkpointer是最后一个关闭的进程,因此我们要求它保存一系列其他任务所需的键,
* 其中大多数与检查点无关。
*
* 由于各种原因,一些配置值可以动态更改,因此它们的主副本保存在共享内存中,
* 以确保所有后端都看到相同的值。如果参数设置因SIGHUP而更改,
* 我们让Checkpointer负责更新共享内存副本。
*/
UpdateSharedMemoryConfig();
}
if (bgwriter_first_startup && !RecoveryInProgress()) {
t_thrd.checkpoint_cxt.checkpoint_requested = true;
flags = CHECKPOINT_IMMEDIATE;
bgwriter_first_startup = false;
ereport(LOG, (errmsg("database first startup and recovery finish,so do checkpointer")));
}
if (t_thrd.checkpoint_cxt.checkpoint_requested) {
t_thrd.checkpoint_cxt.checkpoint_requested = false;
do_checkpoint = true;
u_sess->stat_cxt.BgWriterStats->m_requested_checkpoints++;
}
if (t_thrd.checkpoint_cxt.shutdown_requested) {
/*
* 从此,elog(ERROR)应该以exit(1)结尾,而不是将控制发送回上面的sigsetjmp块
*/
u_sess->attr.attr_common.ExitOnAnyError = true;
/* 关闭数据库 */
ShutdownXLOG(0, 0);
/* 这里是检查指针的正常退出 */
proc_exit(0); /* done */
}
/*
* 如果自上次检查以来经过的时间过长,则强制执行检查点。
* 注意,只有在没有外部请求的情况下才会在stats中计算定时检查点,
* 但即使有外部请求,我们也会设置CAUSE_TIME标志位。
*/
now = (pg_time_t)time(NULL);
elapsed_secs = now - t_thrd.checkpoint_cxt.last_checkpoint_time;
if (elapsed_secs >= u_sess->attr.attr_storage.CheckPointTimeout) {
if (!do_checkpoint)
u_sess->stat_cxt.BgWriterStats->m_timed_checkpoints++;
do_checkpoint = true;
flags |= CHECKPOINT_CAUSE_TIME;
}
/*
* 如果需要,请执行检查点。
*/
if (do_checkpoint) {
bool ckpt_performed = false;
bool do_restartpoint = false;
/* 使用volatile指针防止代码重排 */
volatile CheckpointerShmemStruct* cps = t_thrd.checkpoint_cxt.CheckpointerShmem;
/*
* 检查我们是否应该执行检查点或重新启动点。
* 作为副作用,如果尚未设置TimeLineID,RecoveryInProgress()将初始化它。
*/
do_restartpoint = RecoveryInProgress();
/*
* 以原子方式获取请求标志,以确定我们应该执行哪种检查点,
* 并增加启动计数器以确认我们已经启动了一个新的检查点。
*/
SpinLockAcquire(&cps->ckpt_lck);
flags |= cps->ckpt_flags;
cps->ckpt_flags = 0;
cps->ckpt_started++;
SpinLockRelease(&cps->ckpt_lck);
/*
* 恢复结束检查点是一个真正的检查点,它在我们仍在恢复过程中执行。
*/
if (flags & CHECKPOINT_END_OF_RECOVERY) {
do_restartpoint = false;
}
/*
* 如果(a)自上一个检查点以来过快(无论是什么原因造成的),
* 以及(b)有人自上次检查点启动以来设置了checkpoint_CAUSE_XLOG标志,
* 我们将发出警告。特别注意,此实现不会生成由CheckPointTimeout<CheckPointWarning引起的警告。
*/
if (!do_restartpoint && (flags & CHECKPOINT_CAUSE_XLOG) &&
elapsed_secs < u_sess->attr.attr_storage.CheckPointWarning)
ereport(LOG,
(errmsg_plural("checkpoints are occurring too frequently (%d second apart)",
"checkpoints are occurring too frequently (%d seconds apart)",
elapsed_secs,
elapsed_secs),
errhint("Consider increasing the configuration parameter \"checkpoint_segments\".")));
/*
* 初始化检查点期间使用的检查指针私有变量
*/
t_thrd.checkpoint_cxt.ckpt_active = true;
if (!do_restartpoint)
t_thrd.checkpoint_cxt.ckpt_start_recptr = GetInsertRecPtr();
t_thrd.checkpoint_cxt.ckpt_start_time = now;
t_thrd.checkpoint_cxt.ckpt_cached_elapsed = 0;
if (flags & CHECKPOINT_FLUSH_DIRTY) {
do_dirty_flush = true;
}
/*
* 执行正常的检查点/重启点。
*/
if (do_dirty_flush) {
ereport(LOG, (errmsg("[file repair] request checkpoint, flush all dirty page.")));
Assert(RecoveryInProgress());
if (ENABLE_INCRE_CKPT) {
g_instance.ckpt_cxt_ctl->full_ckpt_expected_flush_loc = get_dirty_page_queue_tail();
pg_memory_barrier();
if (get_dirty_page_num() > 0) {
g_instance.ckpt_cxt_ctl->flush_all_dirty_page = true;
ereport(LOG, (errmsg("[file repair] need flush %ld pages.", get_dirty_page_num())));
CheckPointBuffers(flags, true);
}
} else {
CheckPointBuffers(flags, true);
}
} else if (!do_restartpoint) {
CreateCheckPoint(flags);
ckpt_performed = true;
if (!bgwriter_first_startup && CheckFpwBeforeFirstCkpt()) {
DisableFpwBeforeFirstCkpt();
}
} else {
ckpt_performed = CreateRestartPoint(flags);
}
/*
* 在任何检查点之后,关闭所有smgr文件。
* 这是因为我们不会无限期地挂起smgr引用来删除文件。
*/
smgrcloseall();
/*
* 向任何等待的后端指示检查点完成。
*/
SpinLockAcquire(&cps->ckpt_lck);
cps->ckpt_done = cps->ckpt_started;
SpinLockRelease(&cps->ckpt_lck);
if (ckpt_performed) {
/*
* 注意,我们将检查点开始时间而不是结束时间记录为
* t_thrd.checkpoint_cxt.last_checkpoint_time。
* 这使得时间驱动的检查点以可预测的间隔发生。
*/
t_thrd.checkpoint_cxt.last_checkpoint_time = now;
} else if (!do_dirty_flush) {
/*
* 我们无法执行重新启动点(如果出现错误,检查点将抛出ERROR)。
* 很可能是因为自上次重新启动点以来,我们没有收到任何新的检查点WAL记录。
* 15秒后再试一次。
*/
t_thrd.checkpoint_cxt.last_checkpoint_time = now - u_sess->attr.attr_storage.CheckPointTimeout + 15;
}
t_thrd.checkpoint_cxt.ckpt_active = false;
}
/* 检查archive_timeout,必要时切换xlog文件。 */
CheckArchiveTimeout();
/*
* 检查archive_timeout,必要时切换xlog文件。将活动统计信息发送到统计信息收集器。
* (我们为此重复使用bgwriter相关代码的原因是,
* bgwrite和checkpointer过去只是一个进程。
* 将stats支持拆分为两种独立的stats消息类型可能不值得费力。)
*/
pgstat_send_bgwriter();
/*
* 休眠,直到收到信号,或者到了另一个检查点或xlog文件切换的时间。
*/
now = (pg_time_t)time(NULL);
elapsed_secs = now - t_thrd.checkpoint_cxt.last_checkpoint_time;
elapsed_secs = (elapsed_secs < 0) ? 0 : elapsed_secs;
if (elapsed_secs >= u_sess->attr.attr_storage.CheckPointTimeout)
continue; /* no sleep for us ... */
cur_timeout = u_sess->attr.attr_storage.CheckPointTimeout - elapsed_secs;
if (u_sess->attr.attr_common.XLogArchiveTimeout > 0 && !RecoveryInProgress()) {
elapsed_secs = now - t_thrd.checkpoint_cxt.last_xlog_switch_time;
if (elapsed_secs >= u_sess->attr.attr_common.XLogArchiveTimeout)
continue; /* no sleep for us ... */
cur_timeout = Min(cur_timeout, u_sess->attr.attr_common.XLogArchiveTimeout - elapsed_secs);
}
pgstat_report_activity(STATE_IDLE, NULL);
rc = WaitLatch(&t_thrd.proc->procLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
cur_timeout * 1000L /* 转换为毫秒 */);
/*
* 如果postmaster去世,紧急救援。这是为了避免手动清理所有邮政局长的孩子。
*/
if (rc & WL_POSTMASTER_DEATH)
gs_thread_exit(1);
}
}
其他函数
函数 | 功能 |
---|---|
static void CheckArchiveTimeout(void) | 检查archive_timeout和切换xlog文件 |
static bool ImmediateCheckpointRequested(void) | 如果immediate检查点请求挂起,则返回true |
void CheckpointWriteDelay(int flags, double progress) | 检查点控制率,负责调节BufferSync()的写入速率以达到checkpoint_completion_target |
static bool IsCheckpointOnSchedule(double progress) | 判断我们是否能按时完成这个检查点 |
static void chkpt_quickdie(SIGNAL_ARGS) | 当postmaster发出SIGQUIT信号时发生 |
Size CheckpointerShmemSize(void) | 计算与检查指针相关的共享内存所需的空间 |
void CheckpointerShmemInit(void) | 分配和初始化与检查指针相关的共享内存 |
static void CheckPointProcRunning(void) | 在等待请求检查点完成时,检查检查点进程是否正在运行 |
void RequestCheckpoint(int flags) | 在后端进程中调用以请求检查点 |
ForwardSyncRequest | 将文件fsync请求从后端转发到checkpointer |
static bool CompactCheckpointerRequestQueue(void) | 从请求队列中删除重复项以避免后端fsync。如果删除了任何条目,则返回“true” |
AbsorbFsyncRequests | 检索排队的fsync请求并将其传递给本地smgr |
static void UpdateSharedMemoryConfig(void) | 根据配置参数更新任何共享内存配置 |
bool FirstCallSinceLastCheckpoint(void) | 允许进程通过异步检查检查点完成情况,在每个检查点周期执行一次操作 |