大云海山数据库(He3DB)AutoVacuum源码解析

11 阅读8分钟

AutoVacuum源码解析

AutoVacuum是数据库基于MVCC(多版本并发控制)机制设计的自动化维护功能,其核心价值在于解决MVCC带来的固有问题,规避手动维护短板,保障数据库长期稳定运行,具体原因如下:

1、 解决表膨胀问题:数据库执行update/delete操作时,不会直接删除旧元组,仅标记为“死元组”以保障并发事务可见性。这些死元组长期堆积会占用大量磁盘空间,导致表和索引体积膨胀,大幅增加查询扫描范围,降低执行效率,AutoVacuum可及时清理此类无效数据。

2、防止事务ID回卷灾难:事务ID为32位整数,采用轮转分配模式,若旧元组的事务ID长期不更新,会导致新事务无法识别数据可见性,触发数据库强制停机保护。AutoVacuum通过冻结旧事务ID,从根源上避免这一致命故障。

3、替代手动维护不足:手动执行Vacuum需精准把控时机,面对多表、高并发场景时,易出现遗漏、维护不及时等问题,且无法实现7×24小时不间断维护。AutoVacuum可自动化适配不同表的写入频率,降低运维人力成本。

4、保障查询优化准确性:AutoVacuum同步执行Analyze操作,实时更新表统计信息(如元组分布、死元组数量),为查询优化器提供精准数据支撑,确保生成高效执行计划,避免因统计信息过期导致的性能劣化。

一、AutoVacuum 关键参数解析

autovacuum:控制数据库是否启用自动清理/分析功能,默认值为on。

autovacuum_max_workers:AutoVacuum工作进程的最大并行数量。

autovacuum_naptime:AutoVacuum调度进程两次调度循环的间隔时间。

autovacuum_vacuum_threshold和autovacuum_vacuum_scale_factor:前者是针对 update/delete操作触发AutoVacuum的基础死元组数量,后者是缩放系数。

autovacuum_vacuum_insert_threshold和autovacuum_vacuum_insert_scale_factor:前者是针对insert操作触发AutoVacuum的基础插入元组数量,后者是缩放系数。

autovacuum_analyze_threshold和autovacuum_analyze_scale_factor:前者是自动Analyze的基础修改元组数量,后者是缩放系数。

autovacuum_freeze_max_age:数据库事务ID(XID)的最大冻结年龄,超过该值会强制触发 AutoVacuum进行XID冻结。

autovacuum_multixact_freeze_max_age:数据库多事务ID(MultiXactID)的最大冻结阈值,防止MultiXactID回卷,保障行级锁正常工作。

autovacuum_vacuum_cost_delay:AutoVacuum工作进程在消耗完指定成本后,暂停时间。

autovacuum_vacuum_cost_limit:AutoVacuum工作进程单次清理的最大成本阈值。

autovacuum_work_mem:单个AutoVacuum工作进程可使用的最大内存上限。

二、AutoVacuum的launcher和worker进程

AutoVacuum自动清理包含两种不同的处理进程:AutoVacuum Launcher(调度进程)和AutoVacuum Worker(工作进程),其中Launcher负责全局调度者、进程管理者,是常驻进程,负责统筹整个数据库的AutoVacuum任务,不直接执行清理操作,仅负责分配任务、启动 Worker、监控状态。Worker是具体执行者、任务落地者,是临时的数据库级进程,由 Launcher按需启动,负责在指定数据库内执行具体的清理任务(死元组回收、统计信息更新、孤儿表删除等),任务完成后自动退出。

如果Launcher进程需要一个Worker进程,空闲Worker进程列表不为空且当前没有正待启动的Worker进程,向Postmaster进程发送启动消息,从空闲Worker进程列表取出进程描述信息并设为启动中状态(Launcher进程同一时间仅允许一个启动中Worker进程)。若启动中的Worker进程超时,则取消该启动任务并重启Worker启动循环。若启动成功,该 Worker进程信息将被加入运行中列表,且已成功连接到按规则选中的目标数据库。

do_autovacuum讲解(以postgresql15.5代码为例进行代码解析)

主表/物化视图扫描

(1)扫描pg_class表:通过heap_getnext按正向扫描方向获取下一条堆元组tuple,直到无更多元组(tuple == NULL)时终止循环;

(2)表类型筛选:提取表元数据结构classForm,仅保留普通用户表(RELKIND_RELATION)和物化视图(RELKIND_MATVIEW),其他表类型直接跳过;

(3)临时表特殊处理:若表为临时表(RELPERSISTENCE_TEMP),调用 checkTempNamespaceStatus检查其所属命名空间状态。仅当命名空间处于闲置状态(即孤儿临时表)时,将表 OID 加入orphan_oids列表待后续删除;

(4)**提取表配置与统计信息:**调用extract_autovac_opts提取该表的AutoVacuum专属配置relopts;调用pgstat_fetch_stat_tabentry_ext提取该表的pgstat统计条目tabentry;

(5)**判断是否需要清理/分析:**调用relation_needs_vacanalyze,传入相关参数,判断该表是否需要Vacuum或Analyze;

(6)**收集待处理表:**若该表需要清理或分析,将其OID加入table_oids待处理列表;

(7)构建TOAST表-主表映射:若该表存在有效TOAST表OID(classForm->reltoastrelid),通过hash_search将TOAST表存入 table_toast_map 哈希表,为第二次 TOAST表扫描做准备。

TOAST表扫描(用于存储超大字段数据的辅助表)

(1)**扫描 TOAST 表:**通过heap_getnext按正向方向获取下一条 pg_class 堆元组(已提前筛选 TOAST表类型),无更多元组时终止循环;

(2)跳过临时 TOAST 表:判断表持久性为临时表(RELPERSISTENCE_TEMP),直接跳过,避免处理其他后端的临时TOAST表;

(3)提取TOAST表OID:获取当前TOAST表的唯一标识 relid(classForm->oid);

(4)获取TOAST表Autovac配置:先调用 extract_autovac_opts提取TOAST表自身的relopts配置;若自身无配置,从前期构建的table_toast_map哈希表中查找对应主表配置,有则复用主表的Autovac配置;

(5)提取 TOAST 表统计信息:调用pgstat_fetch_stat_tabentry_ext获取该TOAST表的pgstat统计条目;

(6)判断是否需要 Vacuum:调用relation_needs_vacanalyze判断清理必要性(TOAST 表仅关注Vacuum,忽略Analyze)。

(7)收集待处理 TOAST 表:若需要执行Vacuum,将该TOAST表OID 加入table_oids 待处理列表,后续与主表一同清理。

执行Vacuum

(1)循环遍历待处理表:通过foreach遍历table_oids列表,提取当前表OID;

(2)表存在性与共享属性校验:从系统缓存查询表信息,表无效则直接跳过;表有效则提取共享表属性,随后释放系统缓存;

(3)加锁校验并发冲突:获取AutovacuumScheduleLock排他锁保护表认领的写操作,防止并发冲突,获取AutovacuumLock共享锁,保护Worker列表的读操作,保证遍历安全。遍历运行中的Worker进程,检查是否有其他Worker正在处理该表,检查完成后释放共享锁。有则标记跳过,没有的话将表OID和共享属性存入自身Worker共享内存,完成表认领,释放排他锁;

(4)清理必要性重校验:切换AutovacMemCxt内存上下文,调用table_recheck_autovac 重校验表是否仍需清理,无需清理则清除认领标记并跳过。从Worker认领表到准备执行清理存在时间差,可能发生两种情况:其他Worker已经抢先清理了该表或者该表被用户删除;

(5)重置单表清理内存:重置 PortalContext,清除上一张表的临时残留数据;

(6)获取表名相关信息:获取表名、命名空间名、数据库名,任一获取失败则跳转到资源清理流程;

(7)异常捕获执行清理:启用PG_TRY异常捕获,切换到PortalContext,调用 autovacuum_do_vac_analyze 执行实际的Vacuum/Analyze操作。

  1. relation_needs_vacanalyze讲解(以postgresql15.5代码为例进行代码解析)

回卷风险判断

(1)**计算并调整 XID 强制冻结阈值:**若xidForceLimit 阈值小于第一个正常事务ID(FirstNormalTransactionId),做边界调整以适配XID环形结构;

(2)判断 XID 回卷风险:同时满足表的relfrozenxid是正常事务 ID和relfrozenxid 早于 xidForceLimit,则标记force_vacuum = true(存在XID回卷风险);

(3)XID 无风险时,计算并调整 MultiXactID 强制冻结阈值:计算 multiForceLimit,

若阈值小于第一个有效MultiXactID(FirstMultiXactId),做边界调整以适配MultiXactID环形结构;

(4)判断 MultiXactID 回卷风险:同时满足表的relminmxid有效和relminmxid早于 multiForceLimit,则标记force_vacuum = true(存在MultiXactID回卷风险)。

  1. 阈值计算与按需清理 / 分析判断(正常场景,非回卷风险)

(1)提取核心统计数据

reltuples:从pg_class提取表预估行数(若< 0则置为 0,表示表从未被清理过);

vactuples:从pgstat统计条目提取当前死元组数量(更新/删除才会产生死元组);

instuples:从pgstat统计条目提取自上次Vaccum后的插入量;

anltuples:从pgstat统计条目提取自上次Analyze后的表数据修改量(插入/更新/删除);

  1. 计算三个核心判断阈值(公式:基础阈值+比例因子×表预估行数)

①死元组清理阈值: vacthresh=autovacuum_vacuum_threshold+autovacuum_vacuum_scale_factor × reltuples;

②插入触发清理阈值: vacinsthresh=autovacuum_vacuum_insert_threshold+autovacuum_vacuum_insert_scale_fact or × reltuples;

③分析阈值:

anlthresh = autovacuum_analyze_threshold+autovacuum_analyze_scale_factor× reltuples;

  1. 判断是否需要 Vacuum,满足任一条件即可

①存在回卷风险(force_vacuum = true);

②死元组数量超过死元组清理阈值(vactuples > vacthresh);

③插入量超过插入触发清理阈值,且插入清理未禁用(autovacuum_vacuum_insert_threshold ≥ 0 且 instuples > vacinsthresh);

  1. 判断是否需要 Analyze

表数据修改量超过分析阈值(anltuples > anlthresh),表示统计信息已过时,需要更新。