OpenGauss线程管理-垃圾清理线程-autovacuum
openGauss默认使用MVCC(Multi-Version Concurrency Control)来保证事务的原子性和隔离性。而MVCC机制使得数据库的更新和删除记录实际不会被立即删除并释放存储空间,而是标记为历史快照版本,openGauss使用MVCC机制和这些历史快照实现数据的读写不冲突。但是这样会使得操作频繁的表积累大量的过期数据,占用磁盘空间,当扫描查询数据时,需要更多的IO消耗,降低查询效率。所以需要一个线程对这些过期数据进行清理,并回收存储空间。
术语解释
- MVCC机制:多版本并发控制 (Multi-Version Concurrency Control),MVCC机制的作用是避免同一个数据在不同事务之间的竞争,提高系统的并发性能。特点:①允许多个版本同时存在,并发执行。②不依赖锁机制,性能高。③只在读已提交和可重复读的事务隔离级别下工作。
- 事务:访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- PGPROC结构(数据结构):作用是为了进程间协同和通讯以及postmaster的跟踪。
autovacuum线程
路径:openGauss-server/src/gausskernel/process/postmaster/autovacuum.cpp
autovacuum系统由两种不同的过程组成:垃圾清理启动线程(autovacuum launcher)和垃圾清理执行线程(autovacuum worker),启动线程是始终运行的,在autovacuum GUC参数设置好后由postmaster启动,启动线程会在适当的时候启动执行线程,执行线程是在执行实际的垃圾清理,它们连接到启动程序中确定的数据库,一旦连接,它们就检查目录以选择要清理的表。
请注意,数据库中可以同时存在多个工作线程。他们将把当前正在清空的表存储在共享内存中,这样其他工作人员就可以避免在等待该表的真空锁时被阻塞。它们还将在清空每个表之前重新加载pgstats数据,以避免清空刚被另一个工作线程清空的表,从而不再记录在共享内存中。然而,有一个窗口(由pgstat延迟引起),工作人员可以在该窗口上选择一个已被清空的表;这是当前设计中的一个缺陷。
在主线程中的使用
在GaussDbThreadMain中通过thread_role进入autovacuum线程
代码理解
1.autovacuum launcher
autovacuum launcher不能自己启动工作进程,因为这样做会导致健壮性问题(即,在异常条件下无法关闭它们,而且,由于启动器连接到共享内存,因此容易损坏,所以它不如postmaster健壮)。所以它把这项任务留给了postmaster。
有一个自动清理共享内存区域,启动程序在其中存储有关它想要清理的数据库的信息。当它需要一个新的工作线程启动时,它会在共享内存中设置一个标志,并向postmaster发送一个信号。那么postmaster只知道它必须启动一个线程;所以它fork出一个新的孩子,这个孩子变成了一个线程。这个新线程连接到共享内存,在那里它可以检查启动器设置的信息。
如果fork()调用在postmaster中失败,它会在共享内存区域设置一个标志,并向启动器发送一个信号。启动程序在注意到该标志后,可以通过重新发送信号再次尝试启动工作程序。注意,故障只能是暂时的(由于高负载、内存压力、太多进程等导致的fork故障);稍后会在worker中检测到更多永久性问题,如连接数据库失败,并通过让worker正常退出来处理。根据计划,启动程序稍后将再次启动一个新的工作线程。
当worker完成清理后,它会将SIGUSR2发送到启动器。如果日程安排太紧,立即需要一个新的worker,那么启动程序就会唤醒,并能够启动另一个worker。此时,启动线程还可以平衡各种剩余worker基于成本的清理延迟功能的设置。
(1)AutoVacLauncherMain——autovacuum launcher线程的主回路
NON_EXEC_STATIC void AutoVacLauncherMain()
{
sigjmp_buf local_sigjmp_buf;
/* 我们现在是postmaster子流程 */
IsUnderPostmaster = true;
t_thrd.role = AUTOVACUUM_LAUNCHER;
/* 重置_thrd.proc_cxt.MyProcPid */
t_thrd.proc_cxt.MyProcPid = gs_thread_self();
/* 记录日志记录的开始时间 */
t_thrd.proc_cxt.MyStartTime = time(NULL);
t_thrd.proc_cxt.MyProgName = "AutoVacLauncher";
u_sess->attr.attr_common.application_name = pstrdup("AutoVacLauncher");
/* 通过ps识别我自己 */
init_ps_display("autovacuum launcher process", "", "", "");
ereport(LOG, (errmsg("autovacuum launcher started")));
if (u_sess->attr.attr_security.PostAuthDelay)
pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L);
SetProcessingMode(InitProcessing);
/*
* 设置信号处理程序。我们对数据库的操作非常类似于常规后端,
* 所以我们使用相同的信号处理。参见tcop/postgres.c中的等效代码。
*/
gspqsignal(SIGHUP, avl_sighup_handler);
gspqsignal(SIGINT, StatementCancelHandler);
gspqsignal(SIGTERM, avl_sigterm_handler);
gspqsignal(SIGQUIT, quickdie);
gspqsignal(SIGALRM, handle_sig_alarm);
gspqsignal(SIGPIPE, SIG_IGN);
gspqsignal(SIGUSR1, procsignal_sigusr1_handler);
gspqsignal(SIGUSR2, avl_sigusr2_handler);
gspqsignal(SIGFPE, FloatExceptionHandler);
gspqsignal(SIGCHLD, SIG_DFL);
/* 早期初始化 */
BaseInit();
/*
* 在共享内存中创建每后端PGPROC结构,但在EXEC_backend情况下除外,
* 该情况是在SubPostmasterMain中完成的。在使用LWLocks之前,
* 我们必须这样做(在EXEC_BACKEND情况下,我们已经必须使用LWLocks做一些事情)。
*/
#ifndef EXEC_BACKEND
InitProcess();
#endif
t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(NULL, InvalidOid, NULL);
t_thrd.proc_cxt.PostInit->InitAutoVacLauncher();
SetProcessingMode(NormalProcessing);
/* 如果退出,请首先尝试并清理连接和内存 */
on_proc_exit(autoVacQuitAndClean, 0);
/*
* 创建一个我们将在其中完成所有工作的内存上下文。
* 我们这样做是为了在错误恢复期间重置上下文,从而避免可能的内存泄漏。
*/
t_thrd.autovacuum_cxt.AutovacMemCxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "Autovacuum Launcher",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
(void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt);
/*
* 如果遇到异常,将在此处继续处理。
*
* 此代码是PostgresMain错误恢复的精简版本。
*/
int curTryCounter;
int* oldTryCounter = NULL;
if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
gstrace_tryblock_exit(true, oldTryCounter);
/* 由于未使用PG_TRY,因此必须手动重置错误堆栈 */
t_thrd.log_cxt.error_context_stack = NULL;
t_thrd.log_cxt.call_stack = NULL;
/* 清除时防止中断 */
HOLD_INTERRUPTS();
/* 忽略任何挂起的QueryCancel请求 */
t_thrd.int_cxt.QueryCancelPending = false;
disable_sig_alarm(true);
t_thrd.int_cxt.QueryCancelPending = false; /* 发生超时时再次*/
/* 向服务器日志报告错误 */
EmitErrorReport();
/* 中止当前事务以恢复 */
AbortCurrentTransaction();
/* lsc持有的释放资源 */
AtEOXact_SysDBCache(false);
/*
* 现在返回正常的顶级上下文,并清除ErrorContext以备下次使用。
*/
(void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt);
FlushErrorState();
/* 刷新顶级上下文中的所有泄漏数据 */
MemoryContextResetAndDeleteChildren(t_thrd.autovacuum_cxt.AutovacMemCxt);
/* 不要让悬空指针指向释放的内存 */
t_thrd.autovacuum_cxt.DatabaseListCxt = NULL;
t_thrd.autovacuum_cxt.DatabaseList = NULL;
/*
* 确保pgstat也认为我们的stat数据已经丢失。
* 注意:此处不能使用autovac_refresh_stats。
*/
pgstat_clear_snapshot();
/* 现在我们可以再次允许中断 */
RESUME_INTERRUPTS();
/* 如果处于关闭模式,则无需进一步操作;走开 */
if (t_thrd.autovacuum_cxt.got_SIGTERM)
goto shutdown;
/*
* 出现任何错误后至少休眠1秒。我们不想尽快填写错误日志。
*/
pg_usleep(1000000L);
}
oldTryCounter = gstrace_tryblock_entry(&curTryCounter);
/*我们现在可以处理安装(错误) */
t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf;
/* 在调用rebuild_database_list之前必须取消阻止信号 */
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
gs_signal_unblock_sigusr2();
/*
* 在autovac进程中强制关闭zero_damaged_pages,即使在postgresql.conf中设置了它。我们确实不希望以非交互方式应用这样一个危险的选项。
*/
SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
/*
* 强制statement_timeout为零,以避免超时设置阻止执行定期维护。
*/
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
/*
* 强制将default_transaction_isolation设置为READ COMMITTED。
* 我们不想支付序列化模式的开销,也不想增加导致死锁或延迟其他事务的风险。
*/
SetConfigOption("default_transaction_isolation", "read committed", PGC_SUSET, PGC_S_OVERRIDE);
/*
* 在紧急模式下,只需启动worker(除非要求关闭)并离开。
*/
if (!AutoVacuumingActive()) {
if (!t_thrd.autovacuum_cxt.got_SIGTERM)
do_start_worker();
proc_exit(0); /* done */
}
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid = t_thrd.proc_cxt.MyProcPid;
/*
* 创建初始数据库列表。我们希望这个列表保持不变的是,它是通过减少next_time来排序的。
* 一旦条目更新到更高的时间,它就会被移到前面(这是正确的,因为唯一的操作是将
* autovacuum_naptime添加到条目中,并且时间总是增加)。
*/
rebuild_database_list(InvalidOid);
/* 循环直到关闭请求 */
while (!t_thrd.autovacuum_cxt.got_SIGTERM) {
struct timeval nap;
TimestampTz current_time = 0;
bool can_launch = false;
Dlelem* elem = NULL;
int rc;
/*
* 这个循环与WaitLatch的正常使用有点不同,因为我们希望在第一次启动子进程之前休眠。
* 所以是WaitLatch,然后是ResetLatch。然后检查唤醒条件。
*/
launcher_determine_sleep((t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers != NULL), false, &nap);
/*
* 等到naptime过期或我们收到某种类型的信号
* (所有信号处理程序都会通过调用SetLatch来唤醒我们)。
*/
rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
(nap.tv_sec * 1000L) + (nap.tv_usec / 1000L));
ResetLatch(&t_thrd.proc->procLatch);
/* 处理睡眠时发生的sinval catchup中断*/
ProcessCatchupInterrupt();
/*
* 如果postmaster去世,紧急救援。这是为了避免手动清理postmaster的所有孩子。
*/
if (((unsigned int)rc) & WL_POSTMASTER_DEATH)
proc_exit(1);
/* 正常停机情况 */
if (t_thrd.autovacuum_cxt.got_SIGTERM)
break;
if (t_thrd.autovacuum_cxt.got_SIGHUP) {
t_thrd.autovacuum_cxt.got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
/* 配置文件中是否请求关闭? */
if (!AutoVacuumingActive())
break;
/* 在默认成本参数发生变化时重新平衡*/
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
autovac_balance_cost();
LWLockRelease(AutovacuumLock);
/* 如果naptime更改,则重新生成列表 */
rebuild_database_list(InvalidOid);
}
/*
* worker已完成,或postmaster发出无法启动worker的信号
*/
if (t_thrd.autovacuum_cxt.got_SIGUSR2) {
t_thrd.autovacuum_cxt.got_SIGUSR2 = false;
/* 如果需要,重新平衡成本限制 */
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacRebalance]) {
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacRebalance] = false;
autovac_balance_cost();
LWLockRelease(AutovacuumLock);
}
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed]) {
/*
* 如果postmaster未能启动新worker,我们会睡一会儿,然后重新发送信号。
* 新worker线程的状态仍在内存中,因此这就足够了。之后,我们重新启动主循环。
*
* XXX我们应该限制重试次数吗?我认为这没有多大意义,
* 因为一个worker未来的起步将继续以同样的方式失败
*/
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed] = false;
pg_usleep(1000000L); /* 1s */
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER);
continue;
}
}
/*
* 在尝试启动启动器之前,我们需要检查一些条件。
* 首先,我们需要确保有可用的启动器插槽。
* 其次,我们需要确保没有其他worker在启动时失败
*/
current_time = GetCurrentTimestamp();
LWLockAcquire(AutovacuumLock, LW_SHARED);
can_launch = (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers != NULL);
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) {
int waittime;
WorkerInfo worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker;
/*
* 当另一个worker线程仍在启动(或在启动时失败)时,我们无法启动另一个线程,
* 所以只需再睡一会儿;那个worker一准备好就会把我们叫醒。然而,我们只会等待
* autovacuum_naptime秒(最多60秒),以实现这一点。请注意,
* 连接到特定数据库的失败在这里不是问题,因为在尝试连接之前,worker会将自己从
* startingWorker指针中删除。postmaster检测到的问题(如fork()故障)也会
* 以不同的方式报告和处理。可能导致此代码触发的唯一问题是,在worker从
* startingWorker指针中删除WorkerInfo之前,AutoVacWorkerMain的前面
* 部分中存在错误。
*/
waittime = Min(u_sess->attr.attr_storage.autovacuum_naptime, 60) * 1000;
if (TimestampDifferenceExceeds(worker->wi_launchtime, current_time, waittime)) {
LWLockRelease(AutovacuumLock);
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
/*
* 没有其他进程可以将worker线程置于启动模式,因此如果在交换锁后
* startingWorker仍然无效,我们假设它与上面看到的相同
* (因此我们不重新检查启动时间)。
*/
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) {
worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker;
worker->wi_dboid = InvalidOid;
worker->wi_tableoid = InvalidOid;
worker->wi_parentoid = InvalidOid;
worker->wi_sharedrel = false;
worker->wi_proc = NULL;
worker->wi_launchtime = 0;
worker->wi_links.next = (SHM_QUEUE*)t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers;
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = worker;
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = NULL;
ereport(WARNING, (errmsg("worker took too long to start; canceled")));
}
} else
can_launch = false;
}
LWLockRelease(AutovacuumLock); /* either shared or exclusive */
/* 如果我们什么也做不了,就回去睡觉吧*/
if (!can_launch)
continue;
/* 我们可以开始一个新worker了 */
elem = DLGetTail(t_thrd.autovacuum_cxt.DatabaseList);
if (elem != NULL) {
avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem);
/*
* 如果next_worker现在或过去就启动一个worker
*/
if (TimestampDifferenceExceeds(avdb->adl_next_worker, current_time, 0))
launch_worker(current_time);
} else {
/*
* 列表为空时的特殊情况:立即启动worker。这涵盖了初始情况,
* 即pgstats中没有数据库(因此列表为空)。请注意,
* launcher_detemine_sleep中的约束使我们不能过快地启动worker线程
* (当列表为空时,最多每个autovacuum_naptime启动一次)。
*/
launch_worker(current_time);
}
}
/* autovac launcher的正常出口在这里 */
shutdown:
ereport(LOG, (errmsg("autovacuum launcher shutting down")));
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid = 0;
proc_exit(0); /* done */
}
(2)launch_worker
来自launcher的开始一个worker的封装。除了实际启动它之外,更新数据库列表以反映下次需要在选定数据库上启动另一个数据库的时间。实际的数据库选择留给do_start_worker。
如果所选数据库以前不在列表中,则此例程还将在数据库列表中插入一个条目。
static void launch_worker(TimestampTz now)
{
Oid dbid;
Dlelem* elem = NULL;
dbid = do_start_worker();
if (OidIsValid(dbid)) {
/*
* 浏览数据库列表并更新相应条目。如果数据库不在列表中,我们将重新创建列表。 */
elem = (t_thrd.autovacuum_cxt.DatabaseList == NULL) ? NULL : DLGetHead(t_thrd.autovacuum_cxt.DatabaseList);
while (elem != NULL) {
avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem);
if (avdb->adl_datid == dbid) {
/*
* 将autovacumnaptime秒添加到当前时间,并将其用作此数据库的新“nextworker”字段。
*/
avdb->adl_next_worker =
TimestampTzPlusMilliseconds(now, u_sess->attr.attr_storage.autovacuum_naptime * 1000);
DLMoveToFront(elem);
break;
}
elem = DLGetSucc(elem);
}
/*
* 如果数据库列表中不存在该数据库,则重新生成该列表。
* 数据库可能无论如何都不会进入列表,例如,如果它是一个没有pgstat条目的数据库,
* 但这不是问题,因为我们不想在任何情况下定期安排工作人员进入这些条目。
*/
if (elem == NULL)
rebuild_database_list(dbid);
}
}
2.autovacuum worker
AutoVacWorkerMain——主函数
NON_EXEC_STATIC void AutoVacWorkerMain()
{
sigjmp_buf local_sigjmp_buf;
Oid dbid;
char user[NAMEDATALEN];
/* 我们现在是postmaster子流程 */
IsUnderPostmaster = true;
t_thrd.role = AUTOVACUUM_WORKER;
/* 重置t_thrd.proc_cxt.MyProcPid */
t_thrd.proc_cxt.MyProcPid = gs_thread_self();
/* 记录日志记录的开始时间 */
t_thrd.proc_cxt.MyStartTime = time(NULL);
t_thrd.proc_cxt.MyProgName = "AutoVacWorker";
/* 通过ps识别我自己 */
init_ps_display("autovacuum worker process", "", "", "");
SetProcessingMode(InitProcessing);
/*
* 设置信号处理程序。我们对数据库的操作非常类似于常规后端,
* 所以我们使用相同的信号处理。参见tcop/postgres.c中的等效代码。
*
* 目前,我们不关注postgresql。conf在单个守护进程迭代期间发生的更改,
* 因此我们可以忽略SIGHUP。
*/
/*
* SIGINT用于信号取消当前工作台的真空;
* SIGTERM意味着中止和干净地退出,SIGQUIT意味着弃船。
*/
(void)gspqsignal(SIGINT, StatementCancelHandler);
(void)gspqsignal(SIGTERM, die);
(void)gspqsignal(SIGQUIT, quickdie);
(void)gspqsignal(SIGALRM, handle_sig_alarm);
(void)gspqsignal(SIGPIPE, SIG_IGN);
(void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler);
(void)gspqsignal(SIGUSR2, SIG_IGN);
(void)gspqsignal(SIGFPE, FloatExceptionHandler);
(void)gspqsignal(SIGCHLD, SIG_DFL);
(void)gspqsignal(SIGHUP, SIG_IGN);
/* 早期初始化 */
BaseInit();
/*
* 在共享内存中创建每后端PGPROC结构,但在EXEC_backend情况下除外,
* 该情况是在SubPostmasterMain中完成的。在使用LWLocks之前,我们必须这样做
* (在EXEC_BACKEND情况下,我们已经必须使用LWLocks做一些事情)。
*/
#ifndef EXEC_BACKEND
InitProcess();
#endif
/* 如果退出,请首先尝试并清理连接和内存 */
on_proc_exit(autoVacQuitAndClean, 0);
/*
* 如果遇到异常,将在此处继续处理。
*
* 请参阅postgres.c中的注释,关于此编码的设计。
*/
int curTryCounter;
int* oldTryCounter = NULL;
if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
gstrace_tryblock_exit(true, oldTryCounter);
/* 清除时防止中断 */
HOLD_INTERRUPTS();
/* 向服务器日志报告错误 */
EmitErrorReport();
/* lsc持有的释放资源 */
AtEOXact_SysDBCache(false);
/*
* 我们现在可以走了。注意,因为我们调用了InitProcess,
* 所以注册了一个回调来执行ProcKill,这将清理必要的状态。
*/
proc_exit(0);
}
oldTryCounter = gstrace_tryblock_entry(&curTryCounter);
/* 我们现在可以处理安装(错误) */
t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf;
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
/*
* 在autovac进程中强制关闭zero_damaged_pages,即使在postgresql.conf中设置了它。
* 我们确实不希望以非交互方式应用这样一个危险的选项。
*/
SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
/*
* 强制statement_timeout为零,以避免超时设置阻止执行定期维护。
*/
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
/*
* 强制将default_transaction_isolation设置为READ COMMITTED。
* 我们不想支付序列化模式的开销,也不想增加导致死锁或延迟其他事务的风险。
*/
SetConfigOption("default_transaction_isolation", "read committed", PGC_SUSET, PGC_S_OVERRIDE);
/*
* 获取有关我们将要处理的数据库的信息。
*/
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
/*
* 当心启动worker无效;这通常不会发生,但如果一个worker在分叉之后失败,
* 在分叉之前,启动程序可能会决定将其从队列中删除并重新启动。
*/
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) {
t_thrd.autovacuum_cxt.MyWorkerInfo = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker;
dbid = t_thrd.autovacuum_cxt.MyWorkerInfo->wi_dboid;
t_thrd.autovacuum_cxt.MyWorkerInfo->wi_proc = t_thrd.proc;
/* 插入运行列表 */
SHMQueueInsertBefore(
&t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, &t_thrd.autovacuum_cxt.MyWorkerInfo->wi_links);
/*
* 从“starting”指针中删除,以便启动程序可以在需要时启动新的worker
*/
t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = NULL;
LWLockRelease(AutovacuumLock);
on_shmem_exit(FreeWorkerInfo, 0);
on_shmem_exit(PGXCNodeCleanAndRelease, 0);
/* 唤醒launcher */
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid != 0)
gs_signal_send(t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid, SIGUSR2);
} else {
/* 没有worker进入,走开 */
ereport(WARNING, (errmsg("autovacuum worker started without a worker entry")));
dbid = InvalidOid;
LWLockRelease(AutovacuumLock);
}
if (OidIsValid(dbid)) {
char dbname[NAMEDATALEN];
MemoryContext oldcontext = NULL;
/*
* 向stats收集器报告autovac启动。我们在Init openGauss之前故意这样做,
* 以便即使连接尝试失败,last_autovac_time也会得到更新。
* 这是为了防止autovac反复“卡住”选择一个不可打开的数据库,
* 而不是在它可以连接的内容上取得任何进展。
*/
pgstat_report_autovac(dbid);
AUTOVAC_LOG(LOG, "report autovac startup on database %u to stats collector", dbid);
/*
* 连接到选定的数据库
*
* 注意:如果我们选择了一个刚刚删除的数据库(由于使用了陈旧的统计信息),
* 我们将失败并退出此处。
*/
t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(NULL, dbid, NULL);
t_thrd.proc_cxt.PostInit->InitAutoVacWorker();
t_thrd.proc_cxt.PostInit->GetDatabaseName(dbname);
SetProcessingMode(NormalProcessing);
set_ps_display(dbname, false);
ereport(LOG, (errmsg("start autovacuum on database \"%s\"", dbname)));
if (u_sess->attr.attr_security.PostAuthDelay)
pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L);
/*
* 创建我们将在主循环中使用的内存上下文。
*
* t_thrd.mem_cxt。msg_memcxt在主循环的每次迭代中重置一次,
* 即在处理完来自客户端的每个命令消息后重置。
*/
t_thrd.mem_cxt.msg_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "MessageContext",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
t_thrd.mem_cxt.mask_password_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "MaskPasswordCtx",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
/*
*创建一个资源所有者来跟踪我们的资源(目前只有缓冲管脚)。
*/
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "AutoVacuumWorker",
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE));
oldcontext = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE));
if (u_sess->proc_cxt.MyProcPort->database_name)
pfree_ext(u_sess->proc_cxt.MyProcPort->database_name);
if (u_sess->proc_cxt.MyProcPort->user_name)
pfree_ext(u_sess->proc_cxt.MyProcPort->user_name);
u_sess->proc_cxt.MyProcPort->database_name = pstrdup(dbname);
u_sess->proc_cxt.MyProcPort->user_name = (char*)GetSuperUserName((char*)user);
(void)MemoryContextSwitchTo(oldcontext);
/* 获取节点Oid的分类列表,以便在协调器中进行分析。 */
exec_init_poolhandles();
/* 并做适当的工作 */
t_thrd.autovacuum_cxt.recentXid = ReadNewTransactionId();
t_thrd.autovacuum_cxt.recentMulti = ReadNextMultiXactId();
do_autovacuum();
}
/*
* 如果我们成功获得了一个worker槽,启动程序将收到我在ProcKill中死亡的通知
*/
/* All done, go away */
proc_exit(0);
}
3.autovac_init()——初始化 autovacuum subsystem
在postmaster初始化时调用。
void autovac_init(void)
{
if (u_sess->attr.attr_storage.autovacuum_start_daemon && !u_sess->attr.attr_common.pgstat_track_counts)
ereport(WARNING, (errmsg("autovacuum not started because of misconfiguration"),
errhint("Enable the \"track_counts\" option.")));
}