openGauss autovacuum autoanalyze代码走读

2024年 3月 18日 74.6k 0

一些概念

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

              相关文章

              Oracle如何使用授予和撤销权限的语法和示例
              Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
              下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
              社区版oceanbase安装
              Oracle 导出CSV工具-sqluldr2
              ETL数据集成丨快速将MySQL数据迁移至Doris数据库

              发布评论