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;
小结和思考
核心要点
始终计算评分:不管 autovacuum 是否启用,评分都会计算,便于监控
elevel参数:允许控制日志详细程度,从调试到警告灵活切换为未来视图奠基:这些改动是未来
pg_stat_autovacuum_per_table视图的前置工作
对 DBA 的实际价值
主动监控:不必等待 autovacuum 触发,可以通过评分预测问题 调优依据:可以根据评分调整 autovacuum_vac_scale_factor等参数减少紧急情况:提前 VACUUM 避免在业务高峰时触发自动清理
配置建议
autovacuum_vac_threshold | ||
autovacuum_vac_scale_factor | ||
autovacuum_analyze_threshold | ||
autovacuum_analyze_scale_factor |
对于大表(> 1GB),建议设置较小的 scale_factor(如 0.01),使触发阈值更可预测:
ALTERTABLE big_table SET (autovacuum_vac_scale_factor = 0.01);