OpenGauss线程管理-检查点线程-Checkpointer

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)允许进程通过异步检查检查点完成情况,在每个检查点周期执行一次操作
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值