Postgresql - 源码 - checkpointer process

代码位置:

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);

    }

}

猜你喜欢

转载自blog.csdn.net/chuckchen1222/article/details/83012836