OpenGauss线程管理-数据页写线程(2)-bgwriter

bgwriter线程(BgWriter)主要负责对共享缓冲区的脏页数据进行下盘操作(即写入磁盘),目的是让数据库线程在进行用户查询时可以很少或者几乎不等待写动作的发生(写动作由后端写线程完成)。这样的机制同样也减少了检查点造成的性能下降。后端写线程将持续的把脏页面刷新到磁盘上,所以在检查点到来的时候,只有少量页面需要刷新到磁盘上。但是这样还是增加了I/O的总净负荷,因为以前的检查点间隔里,一个重复弄脏的页面可能只会冲刷一次,而现在一个检查点间隔内,后端写进程可能会写好几次。但在大多数情况下,连续的低负荷要比周期性的尖峰负荷好一些,毕竟数据库稳定十分重要。

术语解释

  • 刷盘:接收到数据后先写入缓冲区,将缓冲区的数据写入到磁盘的过程,称为刷盘。

bgwriter线程

路径:openGauss-server/src/gausskernel/process/postmaster/bgwriter.cpp
  从 Postgres 8.0 开始,后台编写器 (bgwriter) 是新的。 它试图使常规后端不必写出脏共享缓冲区(只有在需要释放共享缓冲区以在另一个页面中读取时才会这样做)。 在最佳情况下,来自共享缓冲区的所有写入都将由后台写入进程发出。 但是,如果 bgwriter 无法维护足够干净的共享缓冲区,则常规后端仍然有权发出写入。
  从 Postgres 9.2 开始,bgwriter 不再处理检查点。
  bgwriter 由 postmaster 在启动子进程完成后立即启动,或者如果我们正在执行归档恢复,则在恢复开始时立即启动。 它一直保持活动状态,直到postmaster命令它终止。 正常终止是由 SIGTERM 完成的,它指示 bgwriter 退出(0)。 通过 SIGQUIT 紧急终止; 像任何后端一样,bgwriter 将简单地中止并在 SIGQUIT 上退出。
   如果 bgwriter 意外退出,postmaster 会将其视为后端崩溃:共享内存可能已损坏,因此剩余的后端应由 SIGQUIT 杀死,然后开始恢复循环。

在主线程中的使用

在GaussDbThreadMain中通过thread_role进入bgwriter线程
在这里插入图片描述

代码理解

BackgroundWriterMain

bgwriter 进程的主入口点 这是从 AuxiliaryProcessMain 调用的,它已经创建了基本的执行环境,但尚未启用信号。

void BackgroundWriterMain(void)
{
    sigjmp_buf local_sigjmp_buf;
    MemoryContext bgwriter_context;
    bool prev_hibernate = false;
    WritebackContext wb_context;

    t_thrd.role = BGWRITER;

    ereport(LOG, (errmsg("bgwriter started")));

    setup_bgwriter_signalhook();

    /*
     * 我们刚刚开始,假设已经关闭或恢复结束快照。
     */
    last_snapshot_ts = GetCurrentTimestamp();

    /*
     * 创建一个资源所有者来跟踪我们的资源(目前只有缓冲引脚)。
     */
    t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer",
        THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE));
    /*
     * 创建一个我们将在其中完成所有工作的内存上下文。
     * 我们这样做是为了在错误恢复期间重置上下文,从而避免可能的内存泄漏。 
     * 以前这段代码只在 t_thrd.top_mem_cxt 中运行,但重置它是一个非常糟糕的主意。
     */
    bgwriter_context = AllocSetContextCreate(t_thrd.top_mem_cxt,
        "Background Writer",
        ALLOCSET_DEFAULT_MINSIZE,
        ALLOCSET_DEFAULT_INITSIZE,
        ALLOCSET_DEFAULT_MAXSIZE);
    MemoryContextSwitchTo(bgwriter_context);

    WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after);

    /*
     * 如果遇到异常,则在此处继续处理。
     *
     * 请参阅 postgres.c 中有关此编码设计的注释。
     */
    int curTryCounter;
    int* oldTryCounter = NULL;
    if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
        gstrace_tryblock_exit(true, oldTryCounter);
        bgwriter_handle_exceptions(wb_context, bgwriter_context);

        /* 在这里报告等待结束,当没有进一步等待的可能性时 */
        pgstat_report_waitevent(WAIT_EVENT_END);
    }
    oldTryCounter = gstrace_tryblock_entry(&curTryCounter);

    /* 我们现在可以处理 ereport(ERROR) */
    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();

    /*
     * 出现任何错误后重置休眠状态。
     */
    prev_hibernate = false;

    pgstat_report_appname("Background writer");
    pgstat_report_activity(STATE_IDLE, NULL);

    /*
     * 永远循环
     */
    for (;;) {
        bool can_hibernate = false;
        int rc;

        /*
         * 当双写被禁用时,pg_dw_meta 将在 dw_file_num = 0 的情况下创建,
         * 所以这里用于升级过程。 
         * bgwriter 将在 enable_incremetal_checkpoint = off 时运行。
         */
        if (pg_atomic_read_u32(&g_instance.dw_batch_cxt.dw_version) < DW_SUPPORT_REABLE_DOUBLE_WRITE
            && t_thrd.proc->workingVersionNum >= DW_SUPPORT_REABLE_DOUBLE_WRITE) {
            dw_upgrade_renable_double_write();
        }

        /* 清除任何已挂起的唤醒 */
        ResetLatch(&t_thrd.proc->procLatch);

        pgstat_report_activity(STATE_RUNNING, NULL);

        if (t_thrd.bgwriter_cxt.got_SIGHUP) {
            t_thrd.bgwriter_cxt.got_SIGHUP = false;
            ProcessConfigFile(PGC_SIGHUP);
        }

        if (t_thrd.bgwriter_cxt.shutdown_requested) {
            /*
             * 从这里开始,elog(ERROR) 应该以 exit(1) 结束,
             * 而不是将控制权发送回上面的 sigsetjmp 块
             */
            u_sess->attr.attr_common.ExitOnAnyError = true;
            /* bgwriter的正常退出就在这里 */
            proc_exit(0); /* done */
        }

        /*
         * 执行一个周期的脏缓冲区写入。
         */
        can_hibernate = BgBufferSync(&wb_context);

        /*
         * 将活动统计信息发送到统计信息收集器
         */
        pgstat_send_bgwriter();

        if (FirstCallSinceLastCheckpoint()) {
            /*
             * 在任何检查点之后,关闭所有 smgr 文件。 
             * 这样我们就不会无限期地挂在对已删除文件的 smgr 引用上。
             */
            smgrcloseall();
        }
        /*
         * 时不时地记录一个新的 xl_running_xacts,
         * 以便复制可以更快地进入一致状态(想想 suboverflowed 快照)
         * 并更频繁地清理资源(锁、KnownXids*)。 
         * 这样做的成本相对较低,
         * 因此每分钟执行 4 次 (LOG_SNAPSHOT_INTERVAL_MS) 似乎没问题。
         *
         *我们假设写入 xl_running_xacts 的间隔明显大于 BgWriterDelay,
         因此我们不会使整体超时处理复杂化,而是假设即使休眠模式处于活动状态,
         我们也会经常被调用。 严格满足 log_snap_interval_ms 并不重要。 
         为了确保我们不会在空闲系统上不必要地唤醒磁盘,
         我们检查自上次记录运行 xacts 以来是否插入了任何 WAL。
         *
         * 我们在 bgwriter 中进行此日志记录,因为它是唯一定期运行并始终返回其主循环的进程。 
         * 例如。 Checkpointer 在激活时几乎不会在其主循环中,因此很难定期记录。
         */
        if (XLogStandbyInfoActive() && !RecoveryInProgress()) {
            TimestampTz timeout = 0;
            TimestampTz now = GetCurrentTimestamp();
            timeout = TimestampTzPlusMilliseconds(last_snapshot_ts, LOG_SNAPSHOT_INTERVAL_MS);

            /*
             * 只有在足够的时间过去并且插入了一些 xlog 记录时才记录。
             */
            if (now >= timeout && !XLByteEQ(last_snapshot_lsn, GetXLogInsertRecPtr())) {
                last_snapshot_lsn = LogStandbySnapshot();
                last_snapshot_ts = now;
            }
            if (now >= timeout) {
                LogCheckSlot();
            }
        }

        /*
         * 睡眠直到我们收到信号或 BgWriterDelay 已经过去。
         *
         * 注意:BgBufferSync() 中的反馈控制循环期望我们每 BgWriterDelay 毫秒调用一次。 
         * 虽然准确无误并不重要,但如果我们偏离得太远,反馈循环可能会出现异常行为。 
         * 因此,请避免使用在正常操作期间可能频繁发生的锁存事件来加载此过程。
         */
        pgstat_report_activity(STATE_IDLE, NULL);
        rc = WaitLatch(&t_thrd.proc->procLatch,
            WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
            u_sess->attr.attr_storage.BgWriterDelay /* ms */);

        /*
         * 如果没有闩锁事件并且 BgBufferSync 表示没有发生任何事情,
         * 请在“休眠”模式下延长睡眠时间,
         * 在这种模式下,我们的睡眠时间比 bgwriter_delay 说的要长得多。 
         * 更少的唤醒可以节省电力。 当后端再次开始使用缓冲区时,
         * 它会通过设置锁存器来唤醒我们。 
         * 因为额外的睡眠只会在没有缓冲区分配发生的情况下持续存在,
         * 这不会严重扭曲 BgBufferSync 的控制循环的行为; 
         * 本质上,它会认为系统范围的空闲间隔不存在。
         *
         * 这里有一个竞争条件,因为后端可能会在 BgBufferSync 看到分配计数为零的
         * 时间和我们调用 StrategyNotifyBgWriter 的时间之间分配一个缓冲区。 
         * 虽然无论如何我们不休眠并不重要,但我们试图通过仅在 BgBufferSync 连续
         * 两个周期没有发生任何事情时休眠来降低这种可能性。 
         * 此外,我们通过不永远休眠来减轻错过唤醒的任何可能后果。
         */
        if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate) {
            /* Ask for notification at next buffer allocation */
            StrategyNotifyBgWriter(t_thrd.proc->pgprocno);
            /* Sleep ... */
            rc = WaitLatch(&t_thrd.proc->procLatch,
                WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
                u_sess->attr.attr_storage.BgWriterDelay * HIBERNATE_FACTOR);
            /* 在我们超时的情况下重置通知请求*/
            StrategyNotifyBgWriter(-1);
        }

        /*
         * 如果postmaster 死亡,紧急救助。 这是为了避免手动清理所有 postmaster 子项的必要性。
         */
        if (rc & WL_POSTMASTER_DEATH)
            gs_thread_exit(1);

        prev_hibernate = can_hibernate;
    }
}
其他函数
函数功能
信号处理程序static void bgwriter_quickdie(SIGNAL_ARGS)在 postmaster 发出 SIGQUIT 信号时发生
static void bgwriter_sighup_handler(SIGNAL_ARGS)SIGHUP:设置标志以在下次方便时重新读取配置文件
static void bgwriter_request_shutdown_handler(SIGNAL_ARGS)SIGTERM:设置标志关闭并退出
static void bgwriter_sigusr1_handler(SIGNAL_ARGS)SIGUSR1:用于锁存器唤醒
bgwriter 视图功能Datum bgwriter_view_get_node_name()获取节点名称
Datum bgwriter_view_get_actual_flush_num()获得实际的冲洗次数
Datum bgwriter_view_get_last_flush_num()获取最后一次刷新次数
Datum bgwriter_view_get_candidate_nums()获取候选人编号
Datum bgwriter_view_get_num_candidate_list()获取 num 候选列表
Datum bgwriter_view_get_num_clock_sweep()获取 num 时钟扫描
增量检查点 bgwriter 线程函数static void drop_rel_all_forks_buffers();删除 rel 所有fork缓冲区
static void drop_rel_one_fork_buffers();删除 rel 的一个 fork 缓冲区
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐