PostgreSQL码农集散地

PG 19 垃圾回收重磅更新,谁急谁先

PostgreSQL autovacuum评分系统重构:让 VACUUM 调度更智能

摘要

本文解读 Nathan Bossart 提交的 autovacuum 评分系统重构系列提交(commit 8261ee2、53b8ca6、01876ac)。这些提交对 relation_needs_vacanalyze() 函数进行了重构,使其始终计算 autovacuum 评分,并为未来通过视图暴露评分奠定了基础。新的评分系统让 DBA 能够更清晰地看到哪些表即将触发 autovacuum,便于主动调优。

关键词: autovacuum , relation_needs_vacanalyze , 评分系统 , VACUUM 调度 , 预防性维护

背景痛点: 原来的 relation_needs_vacanalyze() 只在阈值即将达到时才计算评分,导致 DBA 难以提前识别"即将"需要 vacuum 的表。此外,当 autovacuum 被禁用或针对特定表禁用时,对应的评分也不会计算,缺少全局可见性。

价值意义: 新设计让评分系统始终计算,不管 autovacuum 是否启用,便于 DBA 提前介入和调优,为"预测性维护"提供了数据基础。


原理介绍

原有实现的问题

原来的实现在 src/backend/postmaster/autovacuum.c 中:

/*
 * Old behavior: only compute scores when threshold is reached
 */

staticbool
relation_needs_vacanalyze(Oid relid, Form_pg_class reltuple, bool *needs_vacuum, bool *needs_analyze)
{
/*
     * Only compute vacuum score if we're close to the threshold.
     * This optimization missed cases where we wanted to show
     * the score for monitoring purposes.
     */

if (autovacuum_vac_threshold_approaching(reltuple))
    {
        vac_score = compute_vacuum_score(reltuple);
        *needs_vacuum = (vac_score >= autovacuum_vac_scale_factor);
    }
else
    {
        vac_score = 0;  // Not computed!
        *needs_vacuum = false;
    }

if (autovacuum_analyze_threshold_approaching(reltuple))
    {
        analyze_score = compute_analyze_score(reltuple);
        *needs_analyze = (analyze_score >= autovacuum_analyze_scale_factor);
    }
else
    {
        analyze_score = 0;  // Not computed!
        *needs_analyze = false;
    }

returnfalse;
}

新实现:始终计算评分

Commit 53b8ca6 的核心改动:

/*
 * New behavior: always compute scores, regardless of thresholds.
 * This allows monitoring views to show all tables' scores.
 */

staticvoid
relation_needs_vacanalyze(Oid relid, Form_pg_class reltuple,
                          VacuumScore *vac_score, AnalyzeScore *analyze_score)

{
/*
     * Always compute both scores, even if thresholds haven't been reached.
     * This is needed for pg_stat_progress_vacuum and future monitoring views.
     */

    *vac_score = compute_vacuum_score(reltuple);
    *analyze_score = compute_analyze_score(reltuple);
}

评分计算公式

评分基于 dead tuples 数量和表大小的比例:

// src/backend/postmaster/autovacuum.c

/*
 * Compute vacuum necessity score for a relation.
 * Returns a value between 0.0 and 1.0.
 */

staticdouble
compute_vacuum_score(Form_pg_class reltuple)
{
double      reltuples = reltuple->reltuples;
    BlockNumber relpages = reltuple->relpages;
double      dead_tuples;
double      threshold;

if (relpages == 0)
return0.0;

/*
     * Get dead tuple estimate from pg_class.
     * For tables with n_live_tup and n_dead_tup stats.
     */

    dead_tuples = reltuple->n_dead_tuples;

/*
     * Threshold = autovacuum_vac_threshold + autovacuum_vac_scale_factor * reltuples
     */

    threshold = autovacuum_vac_threshold +
                autovacuum_vac_scale_factor * reltuples;

if (threshold < 1.0)
        threshold = 1.0;

return Min(1.0, dead_tuples / threshold);
}

/*
 * Similar for analyze score.
 */

staticdouble
compute_analyze_score(Form_pg_class reltuple)
{
double      reltuples = reltuple->reltuples;
double      modified_tuples;

    modified_tuples = reltuple->n_mod_since_analyze;

double      threshold = autovacuum_analyze_threshold +
                            autovacuum_analyze_scale_factor * reltuples;

if (threshold < 1.0)
        threshold = 1.0;

return Min(1.0, modified_tuples / threshold);
}

引入的视图支持

Commit 01876ac 添加了 elevel 参数,允许指定日志级别:

/*
 * Add elevel parameter to control logging verbosity.
 *
 * This allows the function to:
 * - Log warnings when thresholds are exceeded (elevel = WARNING)
 * - Be silent when called for monitoring purposes (elevel = DEBUG)
 */

void
relation_needs_vacanalyze(Oid relid, bool *needs_vacuum, bool *needs_analyze,
int elevel)

{
// ... compute scores ...

if (*needs_vacuum)
        ereport(elevel,
            (errmsg("autovacuum: vacuuming \"%s.%s\"",
                    get_namespace_name(relnamespace),
                    RelationGetRelationName(rel))));
}

应用场景及最佳实践

场景一:识别即将需要 VACUUM 的表

Before:DBA 只能看到已经触发 autovacuum 的表,通过 pg_stat_bgwriter 等视图事后分析:

-- 只能看到已发生的
SELECT schemaname, tablename, last_autovacuum
FROM pg_stat_user_tables
WHERE last_autovacuum ISNOTNULL
ORDERBY last_autovacuum DESC;

After:可以提前识别即将需要处理的表:

-- 新增功能:查看所有表的评分(需要对应的新视图或函数)
SELECT schemaname, tablename,
       n_live_tup, n_dead_tup,
CASEWHEN n_live_tup > 0
THENround(n_dead_tup::numeric / (100 + 0.1 * n_live_tup), 4)
ELSE0ENDAS vac_score,
       last_vacuum, last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 100-- 只看有垃圾的表
ORDERBY n_dead_tup DESCLIMIT20;

-- 识别高风险表:dead tuples 接近阈值
-- 阈值 = autovacuum_vac_threshold(默认 50) + autovacuum_vac_scale_factor(默认 0.2) * reltuples

场景二:预防性调优

-- 发现某表评分接近 0.8,提前手动 VACUUM 避免自动触发时的锁等待
SELECT'High priority VACUUM candidate: ' || schemaname || '.' || tablename
AS recommendation
FROM pg_stat_user_tables
WHERE n_dead_tup > autovacuum_vac_threshold + autovacuum_vac_scale_factor * n_live_tup * 0.8
AND (last_autovacuum ISNULLOR last_autovacuum < now() - interval'7 days')
AND (last_vacuum ISNULLOR last_vacuum < now() - interval'7 days');

-- 建议 DBA 主动处理这些表

场景三:评估 autovacuum 配置

-- 查看 autovacuum 配置
SHOW autovacuum_vac_threshold;       -- 默认 50
SHOW autovacuum_vac_scale_factor;     -- 默认 0.2
SHOW autovacuum_analyze_threshold;    -- 默认 50
SHOW autovacuum_analyze_scale_factor; -- 默认 0.2

-- 某表何时会触发 autovacuum:
-- 触发阈值 = autovacuum_vac_threshold + autovacuum_vac_scale_factor * reltuples

-- 对于一个有 100 万行的表:
-- 阈值 = 50 + 0.2 * 1000000 = 200050
-- 即需要 20 万+ dead tuples 才会触发

场景四:批量 VACUUM 优化

-- 批量删除数据后,立即计算所有表的评分
-- 找出评分 > 0.5 的表优先处理

SELECT schemaname, tablename,
       n_dead_tup,
round(n_dead_tup::numeric / 
NULLIF(autovacuum_vac_threshold + autovacuum_vac_scale_factor * n_live_tup, 0), 4
       ) AS vac_score
FROM pg_stat_user_tables
CROSSJOIN (VALUES (current_setting('autovacuum_vac_threshold')::int,
                   current_setting('autovacuum_vac_scale_factor')::numeric)) AS v(threshold, scale)
WHERE n_dead_tup > 0
ORDERBY vac_score DESC
LIMIT10;

小结和思考

核心要点

  1. 始终计算评分:不管 autovacuum 是否启用,评分都会计算,便于监控

  2. elevel 参数:允许控制日志详细程度,从调试到警告灵活切换

  3. 为未来视图奠基:这些改动是未来 pg_stat_autovacuum_per_table 视图的前置工作

对 DBA 的实际价值

  • 主动监控:不必等待 autovacuum 触发,可以通过评分预测问题
  • 调优依据:可以根据评分调整 autovacuum_vac_scale_factor 等参数
  • 减少紧急情况:提前 VACUUM 避免在业务高峰时触发自动清理

配置建议

参数
默认值
适用场景
autovacuum_vac_threshold
50
小表多、频繁更新场景可降低
autovacuum_vac_scale_factor
0.2
大表可适当降低(如 0.01)减少触发阈值
autovacuum_analyze_threshold
50
同上
autovacuum_analyze_scale_factor
0.2
同上

对于大表(> 1GB),建议设置较小的 scale_factor(如 0.01),使触发阈值更可预测:

ALTERTABLE big_table SET (autovacuum_vac_scale_factor = 0.01);