代码位置:
src/backend/postmaster/checkpointer.c
代码中的注释:
检查指针是Postgres 9.2 的新功能。它处理所有checkpoint。checkpoint在从上一个checkpoint经过一定时间之后自动调度,并且还可以用信号通知它执行所请求的checkpoint。(每隔这么多WAL段指定一个checkpoint的GUC参数通过在它们填充WAL段时具有后端信号来实现;checkpoint本身不监视环境。)
一旦启动子进程结束,postmaster 就启动检查指针,或者如果我们正在进行归档恢复,或是只要恢复开始,检查指针就启动。它仍然活着,postmaster 命令它终止。正常终止是 SIGUSR2,它指示检查指针执行关机checkpoint,然后exit(0) 。(所有的后端必须在 SIGUSR2 发布之前停止!)SIGQUIT 是紧急终止;与任何后端一样,检查指针将简单地中止并退出SIGQUIT。
如果检查指针意外地退出,则 postmaster 将此视为后端崩溃:共享内存可能损坏,因此剩余的后端应该被SIGQUIT杀死,然后开始恢复循环。(即使共享内存没有损坏,我们也丢失了关于在下一个checkpoint 需要对哪些文件进行fsync的信息,因此需要强制重新启动系统。)
用于检查指针和后端之间通信的共享内存区域
CKPT计数器允许后端监视他们发送的checkpoint请求的完成。这就是它的运作方式:
- 在checkpoint开始时,检查指针读取(清除)请求标志并增加 ckpt_started,同时保持 ckpt_lck。
- 在checkpoint完成后,检查指针将 ckpt_done 设置为等于 ckpt_started。
- 在checkpoint失败时,检查指针递增ckpt_failed ,并将 ckpt_done 设置为等于 ckpt_started。
后端的算法是:
1. 在保持ckpt_lck的时候,记录 ckpt_failed 和ckpt_started 的值,并设置请求标志。
2. 发送信号请求checkpoint。
3. sleep直到 ckpt_started 变化。现在,知道自启动此算法以来checkpoint已经开始(尽管*不*它是由您的信号专门发起的),并且它正在使用标志。
4. 记录ckpt_started 的新值。
5. sleep 直到ckpt_done >= 被保存的 ckpt_started 。(这里使用模算法,以防出现计数器。)现在知道checkpoint已经启动和完成,但不知道它是否成功。
6. 如果 ckpt_failed 与最初保存的值不同,则假定请求失败,否则它肯定是成功的。
ckpt_flags 保存自上一个checkpoint开始以来所有请求后端发送的checkpoint请求标志的OR。选择了标志,以便 OR 是组合多个请求的正确方式。
num_backend_writes 用于计算由用户后端进程执行的缓冲区写入数。这个计数器应该足够宽,在一个处理周期内不能溢出。 num_backend_fsync 那些也必须执行它们自己的fsync的写入的子集进行计数,因为检查指针无法吸收它们的请求。
请求数组保存后端发送的 fsync 请求,而未被检查指针吸收。
与checkpoint字段不同, num_backend_writes,num_backend_fsync 和请求字段由 CheckpointerCommLock 保护。
下面看一下checkpointer进程的主入口
/* checkpointer进程的主入口,这是从 AuxiliaryProcessMain 调用的,它已经创建了基本的执行环境,但还没有启用信号。 */
void
CheckpointerMain(void)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext checkpointer_context;
CheckpointerShmem->checkpointer_pid = MyProcPid;
/* 正确地接受或忽略 postmaster 可能发送给我们的信号。注意:我们故意忽略SIGTERM, 因为在标准的Unix系统关闭周期中,init将同时对所有进程进行SIGTERM 。我们希望等待后端退出,因此postmaster 会告诉我们关闭(通过 SIGUSR2 )是可以的。 */
pqsignal(SIGHUP, ChkptSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */
pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
pqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, chkpt_sigusr1_handler);
pqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */
/* 重置一些由 postmaster 接受但不在这里接受的信号 */
pqsignal(SIGCHLD, SIG_DFL);
pqsignal(SIGTTIN, SIG_DFL);
pqsignal(SIGTTOU, SIG_DFL);
pqsignal(SIGCONT, SIG_DFL);
pqsignal(SIGWINCH, SIG_DFL);
/* 我们允许在任何时候 SIGQUIT (quickdie) */
sigdelset(&BlockSig, SIGQUIT);
/* 初始化以使第一次驱动事件发生在正确的时间 */
last_checkpoint_time = last_xlog_switch_time = (pg_time_t) time(NULL);
/* 创建一个resource owner 来跟踪我们的资源(目前仅是buffer pins) */
CurrentResourceOwner = ResourceOwnerCreate(NULL, "Checkpointer");
/* 创建一个内存上下文,我们将完成所有的工作。我们这样做,以便在错误恢复过程中可以重置上下文,从而避免可能的内存泄漏。以前,这段代码只是在TopMemoryContext 运行,但是重新设置这将是一个非常糟糕的想法。 */
checkpointer_context = AllocSetContextCreate(TopMemoryContext,
"Checkpointer",
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
/* 如果遇到异常,则在此恢复处理。 */
if (sigsetjmp(local_sigjmp_buf, 1) != 0)
{
/* 由于不使用 PG_TRY,必须手动重置错误堆栈。 */
error_context_stack = NULL;
/* 清理时防止中断 */
HOLD_INTERRUPTS();
/* 向服务器日志报告错误 */
EmitErrorReport();
/* 这些操作实际上只是 AbortTransaction() 的最小子集。我们在检查指针中没有太多的资源需要考虑,但是我们确实有LWLocks、buffers 和临时文件。 */
LWLockReleaseAll();
ConditionVariableCancelSleep();
pgstat_report_wait_end();
AbortBufferIO();
UnlockBuffers();
/* buffer pins 在这里被释放 */
ResourceOwnerRelease(CurrentResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, true);
/* 我们不必担心其他资源所有者的发布阶段 */
AtEOXact_Buffers(false);
AtEOXact_SMgr();
AtEOXact_Files(false);
AtEOXact_HashTables(false);
/* 警告任何等待checkpoint失败的后端。 */
if (ckpt_active)
{
SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
CheckpointerShmem->ckpt_failed++;
CheckpointerShmem->ckpt_done = CheckpointerShmem->ckpt_started;
SpinLockRelease(&CheckpointerShmem->ckpt_lck);
ckpt_active = false;
}
/* 现在返回正常的顶级上下文,下次清除错误上下文。 */
MemoryContextSwitchTo(checkpointer_context);
FlushErrorState();
/* 在顶层上下文中清除任何泄漏的数据 */
MemoryContextResetAndDeleteChildren(checkpointer_context);
/* 现在我们可以再次中断 */
RESUME_INTERRUPTS();
/* 在发生任何错误之后,sleep至少1秒。一个写错误很可能会被重复,并且我们不想以尽可能快的速度填充错误日志。 */
pg_usleep(1000000L);
/* 在发生任何错误之后关闭所有打开的文件。这在Windows上是有用的,保存删除的文件会导致各种奇怪的错误。目前还不清楚我们在别处是否需要,但不应该伤害。 */
smgrcloseall();
}
/* We can now handle ereport(ERROR) */
PG_exception_stack = &local_sigjmp_buf;
/* 解除封锁信号(当 postmaster forked 时,他们被封锁) */
PG_SETMASK(&UnBlockSig);
/* 确保配置的所有共享内存值都正确设置。这样做确保了其他并发更新程序中没有竞争条件。 */
UpdateSharedMemoryConfig();
/* 通知我们, 锁住唤醒正在休眠的后端。 */
ProcGlobal->checkpointerLatch = &MyProc->procLatch;
/*
* Loop forever
*/
for (;;)
{
bool do_checkpoint = false;
int flags = 0;
pg_time_t now;
int elapsed_secs;
int cur_timeout;
int rc;
/* 清除任何已挂起的唤醒 */
ResetLatch(MyLatch);
/* 处理最近收到的任何请求或信号。 */
AbsorbFsyncRequests();
if (got_SIGHUP)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
/* 检查指针是最后一个要关闭的进程,因此我们要求它保存一系列其他任务所需的密钥,这些任务中的大多数与checkpoint没有任何关系。由于各种原因,一些配置值可以动态更改,因此它们的主要副本保存在共享内存中,以确保所有后端看到相同的值。我们让检查指针负责更新共享内存拷贝,如果参数设置因叹息而改变。 */
UpdateSharedMemoryConfig();
}
if (checkpoint_requested)
{
checkpoint_requested = false;
do_checkpoint = true;
BgWriterStats.m_requested_checkpoints++;
}
if (shutdown_requested)
{
/* 从这里开始,elog(ERROR) 应该以exit(1) 结束,而不是将控制返回到上面的 sigsetjmp 块。 */
ExitOnAnyError = true;
/* 关闭数据库 */
ShutdownXLOG(0, 0);
/* 从检查指针中正常退出 */
proc_exit(0); /* done */
}
/* 如果最后一个时间过多,则强制checkpoint 。注意,只有在没有外部请求的情况下才对计时checkpoint进行统计,但是即使存在外部请求,我们也设置 CAUSE_TIME 标志位。 */
now = (pg_time_t) time(NULL);
elapsed_secs = now - last_checkpoint_time;
if (elapsed_secs >= CheckPointTimeout)
{
if (!do_checkpoint)
BgWriterStats.m_timed_checkpoints++;
do_checkpoint = true;
flags |= CHECKPOINT_CAUSE_TIME;
}
/* 如果需要,做一次checkpoint */
if (do_checkpoint)
{
bool ckpt_performed = false;
bool do_restartpoint;
/*
* Check if we should perform a checkpoint or a restartpoint. As a
* side-effect, RecoveryInProgress() initializes TimeLineID if
* it's not set yet.
*/
do_restartpoint = RecoveryInProgress();
/*
* Atomically fetch the request flags to figure out what kind of a
* checkpoint we should perform, and increase the started-counter
* to acknowledge that we've started a new checkpoint.
*/
SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
flags |= CheckpointerShmem->ckpt_flags;
CheckpointerShmem->ckpt_flags = 0;
CheckpointerShmem->ckpt_started++;
SpinLockRelease(&CheckpointerShmem->ckpt_lck);
/* 恢复checkpoint的结束是一个真正的checkpoint,我们还在恢复中执行。 */
if (flags & CHECKPOINT_END_OF_RECOVERY)
do_restartpoint = false;
/* 如果(a)自上次checkpoint以来过早(无论什么原因),我们将警告(b)有人自上次checkpoint 开始以来设置了CHECKPOINT_CAUSE_XLOG 标志。特别注意,此实现不会生成警告是因为CheckPointTimeout < CheckPointWarning。 */
if (!do_restartpoint &&
(flags & CHECKPOINT_CAUSE_XLOG) &&
elapsed_secs < 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 \"max_wal_size\".")));
/* 初始化checkpoint 中使用的检查指针私有变量。 */
ckpt_active = true;
if (do_restartpoint)
ckpt_start_recptr = GetXLogReplayRecPtr(NULL);
else
ckpt_start_recptr = GetInsertRecPtr();
ckpt_start_time = now;
ckpt_cached_elapsed = 0;
/* 做一次 checkpoint */
if (!do_restartpoint)
{
CreateCheckPoint(flags);
ckpt_performed = true;
}
else
ckpt_performed = CreateRestartPoint(flags);
/* 在任何checkpoint之后,关闭所有 smgr 文件。因此,我们不会无限期地挂起对删除文件的引用。 */
smgrcloseall();
/* 任何等待的后端指示checkpoint完成。 */
SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
CheckpointerShmem->ckpt_done = CheckpointerShmem->ckpt_started;
SpinLockRelease(&CheckpointerShmem->ckpt_lck);
if (ckpt_performed)
{
/* 注意,我们将checkpoint开始时间不结束的时间记录为last_checkpoint_time . 这使得time-driven 的checkpoint以可预测的间隔发生。 */
last_checkpoint_time = now;
}
else
{
/* 我们无法重新执行启动点(checkpoint 在错误的情况下抛出错误)。最可能的是,自从上次重新启动点以来,我们还没有收到任何新的checkpoint WAL记录。15秒再试一次。 */
last_checkpoint_time = now - CheckPointTimeout + 15;
}
ckpt_active = false;
}
/* 如果需要,检查archive_timeout 并切换 xlog 文件。Check for archive_timeout and switch xlog files if necessary. */
CheckArchiveTimeout();
/* 向统计收集器发送活动统计信息。(我们重新使用bgwriter-related 代码的原因是bgwriter 和checkpointer 只不过是一个进程。将统计支持分为两个独立的统计信息类型可能是不值得的。 */
pgstat_send_bgwriter();
/* sleep 直到我们发出信号,或者是另一个checkpoint 或xlog 文件切换。 */
now = (pg_time_t) time(NULL);
elapsed_secs = now - last_checkpoint_time;
if (elapsed_secs >= CheckPointTimeout)
continue; /* no sleep for us ... */
cur_timeout = CheckPointTimeout - elapsed_secs;
if (XLogArchiveTimeout > 0 && !RecoveryInProgress())
{
elapsed_secs = now - last_xlog_switch_time;
if (elapsed_secs >= XLogArchiveTimeout)
continue; /* no sleep for us ... */
cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);
}
rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
cur_timeout * 1000L /* convert to ms */ ,
WAIT_EVENT_CHECKPOINTER_MAIN);
/* 如果postmaster 进程死掉,将紧急救助。这是为了避免对所有postmaster 的子进程 进行手工清理。 */
if (rc & WL_POSTMASTER_DEATH)
exit(1);
}
}