Postgresql - 源码 - background writer process

代码位置

src/backend/postmaster/bgwriter.c

background writer (bgwriter)是PG 8.0的新特性。视图避免常规后端必须写出脏共享缓冲区(只有在需要释放共享缓冲区以在另一页中读取时才会这样做。)最佳方案中,共享缓冲区的所有写入将由后台写入器进程发出。但是,如果 bgwriter 无法保持足够干净的缓冲区,则定期后端仍然有权发出写入。

到PG 9.2中,bgwriter 不再处理 checkpoints。

一旦启动子进程结束,postmaster 就启动 bgwriter ,或者一旦恢复开始,如果我们正在进行存档恢复。它仍然活着,直到 postmaster 命令它终止。正常终止是由SIGTERM ,会发出 exit(0)指示bgwriter 。SIGQUIT 是紧急终止;与任何后端一样,bgwriter 将简单地中止并退出SIGQUIT 。

如果bgwriter出乎意料地退出,postmaster 将处理该后端崩溃:共享内存可能损坏,因此剩余的后端数据将被 SIGQUIT 杀死,然后开始恢复。

/* bgwriter process 主入口函数,这是从 AuxiliaryProcessMain 调用的,它已经创建了基本的执行环境,但还没有启用信号。 */

void

BackgroundWriterMain(void)

{

    sigjmp_buf  local_sigjmp_buf;

    MemoryContext bgwriter_context;

    bool        prev_hibernate;

    WritebackContext wb_context;

    /* 正确地接受或忽略 postmaster 可能发送给我们的信号。 bgwriter 不参与ProcSignal 发信号,但仍然需要锁存唤醒的 SIGUSR1 处理程序。 */

    pqsignal(SIGHUP, BgSigHupHandler);  /* set flag to read config file */

    pqsignal(SIGINT, SIG_IGN);

    pqsignal(SIGTERM, ReqShutdownHandler);  /* shutdown */

    pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */

    pqsignal(SIGALRM, SIG_IGN);

    pqsignal(SIGPIPE, SIG_IGN);

    pqsignal(SIGUSR1, bgwriter_sigusr1_handler);

    pqsignal(SIGUSR2, SIG_IGN);

    /* 重置一些由 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);

    /* 创建一个 resource owner 来跟踪我们的资源 (目前仅是buffer pins ) */

    CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer");

    /* 我们刚刚开始,假设有一个关闭或 end-of-recovery 快照 */

    last_snapshot_ts = GetCurrentTimestamp();

    /* 创建一个内存上下文,我们将完成所有的工作。我们这样做,以便在错误恢复过程中可以重置上下文,从而避免可能的内存泄漏。以前,这段代码只是在 TopMemoryContext 运行,但是重新设置这将是一个非常糟糕的想法。 */

    bgwriter_context = AllocSetContextCreate(TopMemoryContext,

                                             "Background Writer",

                                             ALLOCSET_DEFAULT_SIZES);

    MemoryContextSwitchTo(bgwriter_context);

    WritebackContextInit(&wb_context, &bgwriter_flush_after);

    /* 如果遇到异常,则在此恢复处理 */

    if (sigsetjmp(local_sigjmp_buf, 1) != 0)

    {

        /* 由于不使用PG_TRY,必须手动重置错误堆栈 */

        error_context_stack = NULL;

        /* 清理时防止中断 */

        HOLD_INTERRUPTS();

        /* 向服务器日志报告错误 */

        EmitErrorReport();

        /* 这些操作实际上只是 AbortTransaction() 的最小子集。在 bgwriter中,我们没有太多的资源需要担心,但我们确实有LWLocks, buffers, 和 temp files。 */

        LWLockReleaseAll();

        ConditionVariableCancelSleep();

        AbortBufferIO();

        UnlockBuffers();

        /* buffer pins 在这里释放: */

        ResourceOwnerRelease(CurrentResourceOwner,

                             RESOURCE_RELEASE_BEFORE_LOCKS,

                             false, true);

        /* 我们不必担心其他ResourceOwnerRelease 阶段 */

        AtEOXact_Buffers(false);

        AtEOXact_SMgr();

        AtEOXact_Files(false);

        AtEOXact_HashTables(false);

        /* 现在返回正常的顶级上下文,下次清除错误上下文。 */

        MemoryContextSwitchTo(bgwriter_context);

        FlushErrorState();

        /* 在顶层上下文中清除任何泄漏的数据 */

        MemoryContextResetAndDeleteChildren(bgwriter_context);

        /* 重新初始化以避免重复错误引起问题 */

        WritebackContextInit(&wb_context, &bgwriter_flush_after);

        /* 现在我们可以再次中断 */

        RESUME_INTERRUPTS();

        /* 在发生错误的之后至少sleep一秒。一个写错误很可能会被重复,并且我们不想以尽可能快的速度填充错误日志。 */

        pg_usleep(1000000L);

        /* 在所有错误之后关闭所有打开的文件。这在Windows上是有用的,在那里保存删除的文件会导致各种奇怪的错误。目前还不清楚我们需要在别处 */

        smgrcloseall();

        /* 当没有进一步等待的可能性时,报告等待结束 */

        pgstat_report_wait_end();

    }

    /* 现在我们可以处理ereport(ERROR) */

    PG_exception_stack = &local_sigjmp_buf;

    /* 解锁信号(当postmaster fork时,被锁住) */

    PG_SETMASK(&UnBlockSig);

    /* 任何错误之后重置休眠状态 */

    prev_hibernate = false;

    /* Loop forever */

    for (;;)

    {

        bool        can_hibernate;

        int         rc;

        /* 清除任何已挂起的唤醒 */

        ResetLatch(MyLatch);

        if (got_SIGHUP)

        {

            got_SIGHUP = false;

            ProcessConfigFile(PGC_SIGHUP);

        }

        if (shutdown_requested)

        {

            /* 从这里开始,elog(ERROR) 应该以 exit(1) 结束,而不是将控制返回到上面的sigsetjmp 块。 */

            ExitOnAnyError = true;

            /* 正常退出 */

            proc_exit(0);       /* done */

        }

        /* 做一个dirty-buffer 写入循环。 */

        can_hibernate = BgBufferSync(&wb_context);

        /* 向stats collector 发送活动统计信息 */

        pgstat_send_bgwriter();

        if (FirstCallSinceLastCheckpoint())

        {

            /* 在任何检查点之后,关闭所有 smgr 文件。因此,我们不会无限期地挂起 smgr 对删除文件的引用。 */

            smgrcloseall();

        }

        /* 记录一个新的 xl_running_xacts 这样复制可以更快地进入一致状态(想想子溢出的快照),并且更频繁地清理资源(锁,KnownXids* )这样做的成本相对较低,所以一分钟做4次(LOG_SNAPSHOT_INTERVAL_MS)似乎很好。我们假设写入 xl_running_xacts 的间隔比BgWriterDelay 大很多,因此我们不会使整个超时处理复杂化,而只是假设即使休眠模式是活动的,我们也会经常被调用。严格地说,log_snap_interval_ms接口是不重要的。为了确保我们在空闲系统上没有不必要地唤醒磁盘,我们检查自上次记录运行的 xacts 以来是否插入了WAL。我们在 bgwriter 中进行日志记录,因为它是唯一有规律运行并始终返回到它的主循环的进程。例如,当激活时,检查指针几乎不在主循环中,因此很难定期记录。 */

        if (XLogStandbyInfoActive() && !RecoveryInProgress())

        {

            TimestampTz timeout = 0;

            TimestampTz now = GetCurrentTimestamp();

            timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,

                                                 LOG_SNAPSHOT_INTERVAL_MS);

            /* 如果已经过了足够的时间,并且自上次快照以来插入了有用的记录,则只进行日志记录。<= 代替 < , 因为GetLastImportantRecPtr()指向记录的开始,而last_snapshot_lsn 指向记录的末尾。 */

            if (now >= timeout &&

                last_snapshot_lsn <= GetLastImportantRecPtr())

            {

                last_snapshot_lsn = LogStandbySnapshot();

                last_snapshot_ts = now;

            }

        }

        /* sleep,直到我们发出信号或BgWriterDelay已经完成。BgBufferSync()中的反馈控制循环期望我们将调用它的每一毫秒BgWriterDelay。虽然这并不是正确的正确性,但如果我们偏离太远,反馈回路可能会出错。因此,避免在正常操作期间可能频繁发生的闩锁事件加载该进程。 */

        rc = WaitLatch(MyLatch,

                     WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,

                     BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN);

        /* 如果没有锁存事件并且 BgBufferSync 表示什么也没有发生,那么在“休眠”模式下扩展 sleep ,其中我们睡眠的时间比bgwriter_delay 更长。当后端再次使用缓冲区时,它会通过设置锁存器来唤醒我们。因为只有当没有缓冲区分配发生时才会持续额外的休眠,所以这不应该严重扭曲 BgBufferSync 的控制循环的行为;实际上,它会认为系统范围的空闲间隔不存在。这里存在一个竞争条件,后端可以在 BgBufferSync 将分配计数为零的时间和我们调用 StrategyNotifyBgWriter 的时间之间分配缓冲区。虽然我们无论如何不休眠并不重要,但我们试图通过只在 BgBufferSync 表示连续两个周期什么都没有发生时休眠来减少这种可能性。此外,我们用永远不休眠减轻了错过唤醒的任何可能的后果。 */

        if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate)

        {

            /* 在分配下一个缓冲区请求通知 */

            StrategyNotifyBgWriter(MyProc->pgprocno);

            /* Sleep ... */

            rc = WaitLatch(MyLatch,

                         WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,

                         BgWriterDelay * HIBERNATE_FACTOR,

                         WAIT_EVENT_BGWRITER_HIBERNATE);

            /* 在我们超时的情况下重置通知请求 */

            StrategyNotifyBgWriter(-1);

        }

        /* 如果 postmaster 进程死了,将紧急救助。这是为了避免对所有 postmaster 的子进程进行手工清理。 */

        if (rc & WL_POSTMASTER_DEATH)

            exit(1);

        prev_hibernate = can_hibernate;

    }

}

猜你喜欢

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