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()
PM线程
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
关键函数 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大同小异