一些概念
autovacuum、autoanalyze是后台自动触发vacuum、analyze的一套逻辑,到达了什么样的条件时,自动触发vacuum与analyze。后台有常驻线程AVClauncher。这个线程每隔一定的间隔会同志PM线程来唤起一些AVCWorker线程,由AVCWorker进行实际的vacuum与analyze任务。涉及到的时间间隔、唤起的worker的数量等,都有相关的GUC参数可配。
部分数据结构变量
struct WorkerInfoData:
表示一个avcworker的结构体,存放了avcworker的一些信息、工作状态等。内部有一个SHM_QUEUE,可以将一批worker连城一个双链表。
t_thrd.autovacuum_cxt.DatabaseList :
AVClauncher的一个双链表,存放了数据库列表,且顺序能表示某种时间的old?来源与pgstats相关。
t_thrd.autovacuum_cxt.AutoVacuumShmem :
一块共享内存,存放一些avc的全局信息,包括一些槽位、状态等。受AutovacuumLock保护。luncher与avcworker通过这几个槽位进行信息传递,其中主要的几个槽位有:
-
av_freeWorkers:空闲的worker,是个队列。
-
av_startingWorker:正在启动的worker。在这里只当成一个元素用,所以worker的启动只能是串行的,可能这样子比较安全或者实现比较简单吧。
-
av_runningWorkers:正在运行的worker,是个队列。
t_thrd.autovacuum_cxt.MyWorkerInfo:
当前worker自己的信息,存放例如正在vacuum的表。受AutovacuumScheduleLock保护。
AVClauncher 线程:AutoVacLauncherMain()
AutoVacuumingActive(),如果没有开启autovcacuum或track_count的话,则退出。
rebuild_database_list(): 维护 t_thrd.autovacuum_cxt.DatabaseList,也是数据库的vacuum顺序。
launcher_determine_sleep() 计算sleep时间。要考虑是不是有空闲的worker,有可能所有worker都干不完活。如果没有就得多睡会。
WaitLatch 间隔sleep
ResetLatch
t_thrd.autovacuum_cxt.got_SIGHUP: 配置重加载
t_thrd.autovacuum_cxt.got_SIGUSR2:两种作用,1、avcworker完成工作通知,2、pm启动线程avcworker失败通知。然后进行相关的处理。
LWLockAcquire(AutovacuumLock, LW_SHARED);
if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) {
判断是不是有正在启动的avcworker。如果有且是启动时间过长的话,就LW_EXCLUSIVE,取消掉。直接重置相关槽位。
LWLockRelaese(AutovacuumLock);
如果没有 av_freeWorkers ,(同时还不能有正在启动的avcworker),全都在忙,就continue。
launch_worker(current_time) :启动一个worker {
dbid = do_start_worker() :启动一个worker {
刷新avc的统计计数信息。
dblist = get_database_list();
获取xidForceLimit,multiForceLimit。受参数autovacuum_freeze_max_age控制,表示一个事务号最多只能活多久,超过这个年龄,就得被冻结或清理。
avdb:根据dblist、DatabaseList、xidForceLimit等,选出来一个最近最少avc的或需要循环clog的、还得有统计信息的、扒拉扒拉的。
LOCK,在av_freeWorkers取出一个worker变成av_startingWorker,并将avdb、时间等信息传给特。RELEASE
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER): 发送信号给pm进行启动。
返回选中的avdb的oid。
}
遍历DatabaseList,把对应dbid的元素信息更新,并移到链表最头上。
}
PM线程
信号:PMSIGNAL_START_AUTOVAC_WORKER,处理StartAutovacuumWorker()。
启动AUTOVACUUM_WORKER线程。
AVCWorker线程:AutoVacWorkerMain()
主代码
启动的一些blblblb的东西
取出av_startingWorker的信息,如dbid,将自己插入到av_runningWorkers。注册一些推出hook,置空av_startingWorker.
初始化一下blblblbl的东西。啥连接数据库、内存上下文、数据库名、recentXid等。
do_autovacuum():做 autovacuum {
一些cutoff相关的点
default_freeze_min_age,default_freeze_table_age:获取。?干啥的?为啥不能用oldestxmin呢?
local_autovacuum = true, freeze_autovacuum = false 单机下一直是这俩,分布式下有啥区别?
pg_class_desc:获取pgclass的tupledesc
获取计数统计信息(非数据特征计划生成用到的统计信息),这些信息来自于pgstat模块,描述了死元组数量、上次analyze后更新元组数量等,通过pgstat的很多视图或函数都也可以直接查到。
dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId); 一个数据库的所有表的计数统计信息
shared = pgstat_fetch_stat_dbentry(InvalidOid); 获取共享表的计数统计信息。
收集需要处理的对象,整个流程是先收集,再处理,收集的结果存放在这几个结构里。
table_oids:一个list,存放需要vacuum的表的vacuum_object列表。
partitioned_tables_map、toast_table_map、table_relopt_map:三个分区表,键都是表oid,值是表的不同的附件。分区、toast、reloption
开始收集需要处理的对象:
【1、扫描 pg_class ,SnapshotNow,将需要vacuum的表、分区列出来构造vacuum_object插入table_oids】
while ((tuple = (HeapTuple) tableam_scan_getnexttuple(relScan, ForwardScanDirection)) != NULL) {
仅vacuum表或物化视图,同时不能是临时表或gtt。
ustore分区表的话忽略某几个表参数。
判断表支不支持autovacana,需不需要vac ana。
relopts = extract_autovac_opts(tuple, pg_class_desc);
tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, InvalidOid, shared, dbentry);
relation_support_autoavac(tuple, &enable_analyze, &enable_vacuum, &is_internal_relation);
relation_needs_vacanalyze(relid, relopts, rawRelopts, classForm, tuple, tabentry, enable_analyze, enable_vacuum,
false, &dovacuum, &doanalyze, &need_freeze);
=> enable_vacuum,enable_analyze,dovacuum,doanalyze,need_freeze
需要则构造vacuum_object插入table_oids
记录dovacuum,doanalyze,need_freeze等到三个哈希表
at_partitioned_table* apentry
av_relation* ar_entry
av_toastid_mainid* at_entry
}
【2、扫描 pg_partition ,SnapshotNow, 仅扫'p', 即一级分区,将需要vacuum的分区列出来构造vacuum_object插入table_oids】
while (NULL != (partTuple = (HeapTuple) tableam_scan_getnexttuple(partScan, ForwardScanDirection))) {
没relfilenode的跳过(二级分区的p)
前边扫主表的时候没在table_relopt_map哈希表记录的跳过。 ??why(只要有toast或者partition,前边都会进。后边都会找到)
判断是否需要做autovacana
...
partition_needs_vacanalyze
=> enable_vacuum,enable_analyze,dovacuum,doanalyze,need_freeze(分区表不需要doanalyze)
需要则构造vacuum_object插入table_oids
分区表的toast插入 av_toastid_mainid
}
【3、扫描 pg_partition ,SnapshotNow, 仅扫's', 即二级分区, 将需要vacuum的二级分区列出来构造vacuum_object插入table_oids】
while (NULL != (partTuple = (HeapTuple) tableam_scan_getnexttuple(partScan, ForwardScanDirection))) {
前边扫主表的时候没在table_relopt_map哈希表记录的跳过。 ??why(只要有toast或者partition,前边都会进。后边都会找到)
判断是否需要做autovacana
...
partition_needs_vacanalyze
=> enable_vacuum,enable_analyze,dovacuum,doanalyze,need_freeze(分区表不需要doanalyze)
需要则构造vacuum_object插入table_oids
分区表的toast插入 av_toastid_mainid
和一级分区基本一致。
}
【3、扫描 pgclass ,SnapshotNow, 仅扫't', 即toast, 将需要vacuum的toast列出来构造vacuum_object插入table_oids】
while ((tuple = (HeapTuple) tableam_scan_getnexttuple(relScan, ForwardScanDirection)) != NULL) {
跳过临时表gtt
搜索toast_table_map。
at_entry = (av_toastid_mainid*)hash_search(toast_table_map, &(relid), HASH_FIND, &found);
不需要vacuum跳过。
}
bstrategy = GetAccessStrategy(BAS_VACUUM); 获取一个buffer相关的啥策略。有一个ring size,根据buffer avc max worker算出来的
【收集完成,遍历提前筛选出来的 table_oids,逐个vacuum】
(为啥不能边遍历边vacuum?可能是由于又有表又有索引又有分区又toast的,有些得综合处理?)
foreach (cell, table_oids) {
X AutovacuumScheduleLock
S AutovacuumLock
遍历所有的worker,检测当前这个表是不是有人在vacumm。是的话skip。
更新MyWorkerInfo。(包括表oid、表的vacuum cost等。)
重新检测这个表 or 分区 or 二级分区是否还需要vacuumanalyze。
计算更新limit、delay。与参数autovacuum_vacuum_cost_limit、vacuum_cost_limit、delay等相关。目的是通过这俩属性来均匀分配IO。
autovac_balance_cost():{
遍历runningworker,获取总的cost_total。
遍历runningworker,更新每个worker的新wi_cost_limit。
理解:limit为某种资源的使用上限(例如IO次数or流量之类的吧),delay为时间,那么limit/delay就是资源最大消耗速度。
获取总的cost_total时用的是wi_cost_limit_base wi_cost_delay 相加,那么应该也就是将资源最大小消耗速度又摊给了每个worker。结合函数vacuum_delay_point() 应该也能说明。
}
获取要vacuum对象的更多信息,表空间、表名等。
【正真的vacuum】
try:
开事务,拿快照
RowExclusiveLock * DatabaseRelationId * MyDatabaseId 需要锁住pg_database的当前行。
enable_sig_alarm()像是某个啥autoanalyze的超时判断,autoanalyze_timeout
autovacuum_local_vac_analyze(){
构造一个struct VacuumStmt, flag需要设置VACOPT_NOWAIT,按需设置VACOPT_VACUUM和VACOPT_ANALYZE.
调用vacuum函数来完成对单一关系的vacuum或analyze操作。
vacuum(vacstmt, 关系oid, 不toast, bstrategy策略, isTop=true)
}
处理一点二级分区的特殊情况。
disable_sig_alarm():像是退出某个啥autoanalyze的超时判断,autoanalyze_timeout
catch:
.....
vacuum完了一张表,更新MyWorkerInfo。(置空)
}
vac_update_datfrozenxid:更新下数据库的datfrozenxid,截断clog等。
CommitTransactionCommand:提交事务。
}
关键函数 relation_support_autoavac():判断表是不是支持做autoavac
入参:pg class的一行
出参:enable_analyze,enable_vacuum, is_internal_relation
{
得打开 autovacuum 参数
如果是内部表(系统表),则都不支持。除非他是matviewmap_或者mlog_打头的,估计是物化视图的东西,都支持。
列存表仅analyze,不支持vacuum
行村表都支持。
pg_statistic仅支持vacuum,不需要analyze。
toast表仅支持vacuum,不需要analyze。
看参数autovacuum、autovacuummode的设置。仅作vacuum、仅作analyze、还是mix都做
临时表不支持analyze
外表不支持analyze等。
}
关键函数 relation_needs_vacanalyze:判断表是不是需要做autoavac
入参:表oid、reloption、pgclass行等的信息,计数统计信息tabentry,是否支持analyze或vacuum(allowAnalyze,allowVacuum),
出参:dovacuum, doanalyze, need_freeze
-
autovacuum_vacuum_threshold(u_sess->attr.attr_storage.autovacuum_vac_thresh):设置触发VACUUM的阈值。当表上被删除或更新的记录数超过设定的阈值时才会对这个表执行VACUUM操作
-
autovacuum_vac_scale(u_sess->attr.attr_storage.autovacuum_vac_scale):设置触发一个VACUUM时增加到autovacuum_vacuum_threshold的表大小的缩放系数
-
autovacuum_anl_scale(u_sess->attr.attr_storage.autovacuum_anl_scale):设置触发一个ANALYZE时增加到autovacuum_analyze_threshold的表大小的缩放系数
-
autovacuum_analyze_threshold(u_sess->attr.attr_storage.autovacuum_anl_thresh):设置触发ANALYZE操作的阈值。当表上被删除、插入或更新的记录数超过设定的阈值时才会对这个表执行ANALYZE操作。
{
determine_vacuum_params():获取vacuum相关参数,那一大坨阈值GUC,还有xidForceLimit等。
阈值有两处可以设置,一个是表的参数,另一个是guc参数。表参数优先。
获取表的relfrozenxid。在入参信息内获取,可能是pg_class行等。
force_vacuum。如果relfrozenxid < xidForceLimit, 则表示有些已经超过年龄了,需要强制vacuum。
delta_vacuum:列存的话,分析是不是需要将delta表的行转移到CU。deltaTabentry->n_live_tuples >= rawRelopts)->delta_rows_threshold
计算阈值
reltuples = classForm->reltuples; pg_class内的预估行数。
vacthresh = (float4)vac_base_thresh + vac_scale_factor * reltuples;
意思:vacuum阈值 = 来自guc或表的设置的行数 + 弹性比例 * 表行数。
anlthresh = (float4)anl_base_thresh + anl_scale_factor * reltuples;
意思:analyze阈值 = 来自guc或表的设置的行数 + 弹性比例 * 表行数。
可以看出 行数threshold与弹性scale是相加的关系,用于处理不同的表大小。如果设置了100,但表十分巨大的话,不至于太频繁vacuum。
获取当前情况数据:
在入参的计数统计信息tabentry里,直接取:
anltuples:自上次analyze之后的元组改变数量,change_since_analyze。
vactuples:死元组数量,dead_tuples
判断是否需要vacuum analyze。先默认认为
dovacuum = force_vacuum || delta_vacuum;
doanalyze = false;
默认值不行的话,再看阈值是不是到了,以及是不是表支持做这些东西。
还有用户可以针对某个表在表参数里单独关闭avc的情况。这时也不会去dovacuum,但前提是不force_vacuum。
所以可以看到force_vacuum的优先级是最高的,这玩意分析应该主要是为了防止事务回卷做的。
}
关键函数 partition_needs_vacanalyze:判断分区是不是需要做autoavac
与relation_needs_vacanalyze大同小异