Postgresql - 源码 - autovacuum deamon

什么是vacuum。在这一文章中就不多阐述了。我们来看一下autovacuum的代码。

代码中的注释信息 ( 代码位置 src/backend/postmaster/autovacuum.c ):

******************************************************************

autovacuum系统是由两种不同的过程构成的:autovacuum launcher 和 autovacuum worker。launcher是一个总是运行的过程,当autovacuum GUC参数被设置时,由 postmaster 启动。发射装置安排 autovacuum workers 在适当的时候开始工作。workers 是执行实际 vacuuming 的过程;他们连接到启动器中确定的数据库,一旦连接,他们检查目录以选择要 vacuum 的表。

autovacuum launcher 不能自己启动辅助进程,因为这样做会导致健壮性问题(即,在特殊情况下无法关闭它们,而且,由于launcher 连接到共享内存,因此受到损坏,它并不像健壮的 postmaster)。所以它把这个任务留给 postmaster。

有一个 autovacuum 共享内存区域,其中 launcher 存储关于它想要清空的数据库的信息。当它想要一个新的 worker 启动时,它在共享内存中设置一个标志并向 postmaster 发送一个信号。然后 postmaster 只知道它必须开始一个 worker ,所以它 fork 一个新的子进程,变成一个 worker 。这个新的进程连接到共享内存,在那里它可以检查 launcher 已经建立的信息。

如果 fork() 在 postmaster 中失败,则在共享内存区域中设置一个标志,并向launcher发送一个信号。 launcher 在注意到标志后,可以通过重新发送信号来尝试再次启动 worker 。请注意,故障只能是暂时的(由于高负载、内存压力、太多的进程而导致的 fork 故障,等等);更持久的问题,如连接到数据库的故障,将在 worker 稍后检测到,并且仅通过让 worker 正常退出来处理。launcher 将按时间表重新启动新的 worker 。

当 worker 完成 vacuum 时,它将SIGUSR2发送到launcher。然后,如果 schedule 安排太紧,以至于立即需要新的 worker ,launcher 就会醒来,并且能够启动另一个 worker 。此时,launcher 还可以平衡各种剩余 worker 基于成本的 vacuum 延迟特性的设置。

注意,数据库中可以同时存在多个 worker 。它们将把当前正在 vacuuming 的表存储在共享内存中,以便其他 worker 避免被阻塞,等待该表的真空锁。他们还会在 vacuuming 每个表之前重新加载pgstats数据,以避免 vacuuming 刚刚结束的表,该表被另一个 worker 清空,因此不再在共享内存中被记录。但是,有一个窗口(由pgstat延迟引起),worker 可以在该窗口上选择已经清空的表;这是当前设计中的一个bug。

******************************************************************

autovacuum launcher的主函数入口 StartAutoVacLauncher(void) ,在postmaster.c的ServerLoop(void)函数调用,或reaper(SIGNAL_ARGS)函数调用。

主函数很精简,主要做了三件事情, InitPostmasterChild(),ClosePostmasterPorts(false), AutoVacLauncherMain(0, NULL)

第一个函数是为postmaster的子进程初始化基本环境,要在子进程启动前被调用。

第二个函数是关闭所有打开的套接字的postmaster,这在子进程启动过程中被调用,以释放该子进程不需要的文件描述符。

第三个函数是真正的autovacuum launcher 进程的主函数了

int StartAutoVacLauncher(void)

{

    pid_t       AutoVacPID;

#ifdef EXEC_BACKEND

    switch ((AutoVacPID = avlauncher_forkexec()))

#else

    switch ((AutoVacPID = fork_process()))

#endif

    {

        case -1:

            ereport(LOG,

                    (errmsg("could not fork autovacuum launcher process: %m")));

            return 0;

#ifndef EXEC_BACKEND

        case 0:

            /* in postmaster child ... */

            InitPostmasterChild();

            /* Close the postmaster's sockets */

            ClosePostmasterPorts(false);

            AutoVacLauncherMain(0, NULL); # 主循环 autovacuum launcher 进程函数

            break;

#endif

        default:

            return (int) AutoVacPID;

    }

    /* shouldn't get here */

    return 0;

}

/* autovacuum launcher 进程函数 */

NON_EXEC_STATIC void

AutoVacLauncherMain(int argc, char *argv[]){

......

    /* Identify myself via ps */ /* 初始化 */

    init_ps_display(pgstat_get_backend_desc(B_AUTOVAC_LAUNCHER), "", "", "");

......

    SetProcessingMode(InitProcessing); /* 进程检查 */

    /*

     * Set up signal handlers. We operate on databases much like a regular

     * backend, so we use the same signal handling. See equivalent code in

     * tcop/postgres.c.

     */

/* 设置 signal handlers */

    pqsignal(SIGHUP, av_sighup_handler);

......

    pqsignal(SIGCHLD, SIG_DFL);

/* 初始化,InitCommunitcation(), DebugFileOpen(), InitFileAccess(), smgrinit(), InitBufferPoolAccess() */

    BaseInit();

/* 在内存中创建了PGPROC,但是除了EXEC_BACKEND,所以在这里必须要初始化进程。 */

#ifndef EXEC_BACKEND

    InitProcess();

#endif

    InitPostgres(NULL, InvalidOid, NULL, InvalidOid, NULL, false);

    SetProcessingMode(NormalProcessing);

/* 创建一个内存上下文,避免在发生错误或出现问题的时候有内存泄漏。 */

    AutovacMemCxt = AllocSetContextCreate(TopMemoryContext,

                                         "Autovacuum Launcher",

                                         ALLOCSET_DEFAULT_SIZES);

    MemoryContextSwitchTo(AutovacMemCxt);

/* 如果遇到异常,则在此做恢复处理。此代码是PostgresMain 错误恢复的精简版本。*/

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

    {

        ......

    }

    /* We can now handle ereport(ERROR) */

    PG_exception_stack = &local_sigjmp_buf;

    /* must unblock signals before calling rebuild_database_list */

    PG_SETMASK(&UnBlockSig);

    /* 设置是中安全的搜索路径。但是Launcher没有连接到数据库,所以没有影响。 */

    SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);

    /* 在autovac过程中强制关闭zero_damaged_pages,即使它被设置在postgresql.conf中。我们真的不希望非交互地应用这种危险的选项。*/

    SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);

    /* 强制设置超时关闭,以避免让这些设置阻止定期维护被执行。 */

    SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);

    SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);

    SetConfigOption("idle_in_transaction_session_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);

    /* 强制 default_transaction_isolation 以读取已提交的文件。我们不想增加可序列化模式的开销,也不增加任何导致死锁或延迟其他事务的风险。 */

    SetConfigOption("default_transaction_isolation", "read committed",

                    PGC_SUSET, PGC_S_OVERRIDE);

    /* 在紧急模式下,只需启动一个worker */

    if (!AutoVacuumingActive())

    {

....

    }

    AutoVacuumShmem->av_launcherpid = MyProcPid;

    /* 创建初始数据库列表。我们希望这个列表保持不变的是,它是通过减少 next_time 来排序的。一旦一个条目被更新到更高的时间,它将被移动到前面。 */

    rebuild_database_list(InvalidOid);

    /* 循环,直到数据库shutdown 。*/

    while (!got_SIGTERM)

    {

        ......

        /* 这个循环与 WaitLatch 的正常使用稍有不同,因为我们希望在第一次启动子进程之前睡眠。所以是WaitLatch ,然后是ResetLatch ,然后检查唤醒状态。 */

        launcher_determine_sleep(!dlist_is_empty(&AutoVacuumShmem->av_freeWorkers), false, &nap);

        /*等待直到 naptime 到期或者我们得到某种类型的信号(所有的信号处理程序将通过调用SetLatch唤醒)。 */

        rc = WaitLatch(MyLatch,

                     WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,

                     (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),

                     WAIT_EVENT_AUTOVACUUM_MAIN);

        ResetLatch(MyLatch);

        /* 当休眠的时候,进程 sinval catchup 中断 */

        ProcessCatchupInterrupt();

        /* 紧急补救,当postmaster 进程死掉了。这是为了避免对所有子进程进行手工清理。 */

        if (rc & WL_POSTMASTER_DEATH)

            proc_exit(1);

        /* the normal shutdown case */

        if (got_SIGTERM)

            break;

        if (got_SIGHUP)

        {

            got_SIGHUP = false;

            ProcessConfigFile(PGC_SIGHUP);

            /* shutdown requested in config file? */

            if (!AutoVacuumingActive())

                break;

            /* rebalance in case the default cost parameters changed */

            LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);

            autovac_balance_cost();

            LWLockRelease(AutovacuumLock);

            /* rebuild the list in case the naptime changed */

            rebuild_database_list(InvalidOid);

        }

        /* 一个worker完成,或postmaster未能启动worker */

        if (got_SIGUSR2)

        {

            got_SIGUSR2 = false;

            /* rebalance cost limits, if needed */

            if (AutoVacuumShmem->av_signal[AutoVacRebalance])

            {

                LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);

                AutoVacuumShmem->av_signal[AutoVacRebalance] = false;

                autovac_balance_cost();

                LWLockRelease(AutovacuumLock);

            }

            if (AutoVacuumShmem->av_signal[AutoVacForkFailed])

            {

                /* 如果主进程启动新的worker失败,sleep一下并重新发送。新的worker状态仍在内存中。之后,我们重新启动主循环。我们应该限制我们重试的次数吗?这是没有意义的,因为worker 之后的启动将继续以同样的方式失败。 */

                AutoVacuumShmem->av_signal[AutoVacForkFailed] = false;

                pg_usleep(1000000L);    /* 1s */

                SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER);

                continue;

            }

        }

        /* 在试图启动一个worker之前,需要做一些检查。首先,我们需要确保有一个worker插槽可用。第二,我们需要确保没有其他worker在启动时失败。*/

        current_time = GetCurrentTimestamp();

        LWLockAcquire(AutovacuumLock, LW_SHARED);

        can_launch = !dlist_is_empty(&AutoVacuumShmem->av_freeWorkers);

        if (AutoVacuumShmem->av_startingWorker != NULL)

        {

            int         waittime;

            WorkerInfo  worker = AutoVacuumShmem->av_startingWorker;

            /* 当一个worker 还在启动时(或者在启动时失败),我们不能启动另一个worker,所以只需要多sleep一会儿;一旦准备好,这个worker就会再次唤醒。然而,我们只会等待 autovacuum_naptime 秒(最多60秒)。注意,无法连接到特定数据库在这里不是问题,因为 worker 在尝试连接之前将自己从 startingWorker 指针中移除。postmaster发现的问题也有不同的报告和处理方式。可能导致此代码启动的唯一问题是,在worker从startingWorker 指针中移除 WorkerInfo 之前,AutoVacWorkerMain 早期部分中的错误。*/

            waittime = Min(autovacuum_naptime, 60) * 1000;

            if (TimestampDifferenceExceeds(worker->wi_launchtime, current_time,

                                         waittime))

            {

                LWLockRelease(AutovacuumLock);

                LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);

                /* 没有其他进程可以让worker处于启动模式,所以如果 startingWorker 在交换锁之后仍然为INVALID,那么我们假设它和上面看到的一样(所以我们不重新检查启动时间) */

                if (AutoVacuumShmem->av_startingWorker != NULL)

                {

                    worker = AutoVacuumShmem->av_startingWorker;

                    worker->wi_dboid = InvalidOid;

                    worker->wi_tableoid = InvalidOid;

                    worker->wi_sharedrel = false;

                    worker->wi_proc = NULL;

                    worker->wi_launchtime = 0;

                    dlist_push_head(&AutoVacuumShmem->av_freeWorkers,

                                    &worker->wi_links);

                    AutoVacuumShmem->av_startingWorker = NULL;

                    elog(WARNING, "worker took too long to start; canceled");

                }

            }

            else

                can_launch = false;

        }

        LWLockRelease(AutovacuumLock);  /* either shared or exclusive */

        /*什么都做不了的时候,sleep */

        if (!can_launch)

            continue;

        /* We're OK to start a new worker */

        if (dlist_is_empty(&DatabaseList))

        {

            /* 当列表为空的特殊情况下:立即启动一个worker 。这覆盖了初始情况,当没有数据库在 pgstats S中(因此列表是空的)。注意,launcher_determine_sleep 中的约束使我们不能太快地开始工作(最多一次是在列表为空时autovacuum_naptime)。 */

            launch_worker(current_time);

        }

        else

        {

            /* 因为rebuild_database_list首先用最远的adl_next_worker构造一个列表,所以我们从列表的尾部获得数据库。*/

            avl_dbase *avdb;

            avdb = dlist_tail_element(avl_dbase, adl_node, &DatabaseList);

            /* 启动一个worker 无论 next_worker是现在还是以前 */

            if (TimestampDifferenceExceeds(avdb->adl_next_worker,

                                         current_time, 0))

                launch_worker(current_time);

        }

    }

    /* 正常退出 */

shutdown:

    ereport(DEBUG1,

            (errmsg("autovacuum launcher shutting down")));

    AutoVacuumShmem->av_launcherpid = 0;

    proc_exit(0);               /* done */

}

猜你喜欢

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