openGauss autovacuum autoanalyze代码走读

d1974c85-c3f5-49bb-9285-984db33a43d3.gif

一些概念

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大同小异



本文分享自微信公众号 - openGauss(openGauss)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“ OSC源创计划 ”,欢迎正在阅读的你也加入,一起分享。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

openGauss社区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值