openGauss autovacuum autoanalyze代码走读

autovacuum autoanalyze代码骨架。

一些概念

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大同小异
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值