繁忙表持续产生旧行版本,维护速度需要与这种产生速度匹配。降低自动回收门槛,可以让表更早具备被处理的资格,但“符合条件”并不等于“已经完成”。工作者仍需轮到这张表、获得所需锁,并在积压继续增长之前完成足够的有效清理。调优应观察这个完整循环,不能把一个参数值当成后台吞吐的保证。

坏掉的样子

普通更新和删除的触发条件,由固定门槛加上表行数估计乘比例因子构成;PostgreSQL 18 在启用上限时,还会应用最大触发门槛。插入触发清理、由事务年龄驱动的冻结,又有各自的运行原因。因此,只看一个死元组比例,会漏掉其他维护需求;每看见一次清理就断言更新删除门槛被触发,也可能误判真正原因。

把门槛调低,不能释放仍被旧快照需要的版本,也不能创造额外的磁盘吞吐。增加工作者或降低成本延迟,还可能加剧与前台查询的资源竞争。让死元组估计在每个瞬间都接近零,既不是通用的现实目标,也不能充分说明业务性能良好。真正需要维持的是可持续的清理能力、可接受的文件增长、请求延迟和维护成本。

快速验证

使用 PostgreSQL 18 可丢弃实例,确认自动回收与统计收集已启用、有可用工作者,并使用正常的一分钟数据库检查间隔。通过一个全新的 psql -X -v ON_ERROR_STOP=1 连接执行下面的脚本。它最多等待一百三十秒,核对真实自动执行;如果超时,应把这次观察记为失败,不能手动清理一次就冒充自动任务已经运行。

CREATE SCHEMA churn_demo;
CREATE TABLE churn_demo.jobs (id integer PRIMARY KEY,n integer)
  WITH (autovacuum_enabled=false,
        autovacuum_vacuum_threshold=50,
        autovacuum_vacuum_scale_factor=0.2);
INSERT INTO churn_demo.jobs SELECT g,0 FROM generate_series(1,10000) AS g;
SELECT pg_stat_force_next_flush();
VACUUM (ANALYZE) churn_demo.jobs;
UPDATE churn_demo.jobs SET n=1 WHERE id<=1000;
SELECT pg_stat_force_next_flush();
SELECT n_live_tup,n_dead_tup,vacuum_count,autovacuum_count
FROM pg_stat_user_tables WHERE relid='churn_demo.jobs'::regclass;
SELECT 50+0.2*reltuples AS old_threshold,50+0.01*reltuples AS new_threshold
FROM pg_class WHERE oid='churn_demo.jobs'::regclass;
ALTER TABLE churn_demo.jobs SET
  (autovacuum_enabled=true,autovacuum_vacuum_scale_factor=0.01);
DO $$
DECLARE deadline timestamptz := clock_timestamp()+interval '130 seconds';
BEGIN
  LOOP
    PERFORM pg_stat_clear_snapshot();
    EXIT WHEN (SELECT autovacuum_count>0 FROM pg_stat_user_tables
               WHERE relid='churn_demo.jobs'::regclass);
    IF clock_timestamp()>deadline THEN
      RAISE EXCEPTION 'no automatic vacuum observed within the experiment budget';
    END IF;
    PERFORM pg_sleep(0.25);
  END LOOP;
END $$;
SELECT pg_stat_clear_snapshot();
SELECT n_dead_tup,last_autovacuum IS NOT NULL AS ran,autovacuum_count>0 AS counted
FROM pg_stat_user_tables WHERE relid='churn_demo.jobs'::regclass;
SELECT count(*),count(*) FILTER (WHERE n=1) FROM churn_demo.jobs;
DO $$ BEGIN
  IF (SELECT count(*) FROM churn_demo.jobs)<>10000
     OR (SELECT count(*) FROM churn_demo.jobs WHERE n=1)<>1000 THEN
    RAISE EXCEPTION 'maintenance changed application data';
  END IF;
END $$;
DROP SCHEMA churn_demo CASCADE;

测试表最初只关闭自己的常规自动回收,让初始化与受控更新先完成。手动清理并分析建立统计,同时清掉初始化插入积压,然后再更新一千行。显式统计刷新调用用于同步这个短实验,避免待发布的写入增量落在手动维护更新估计值之后;它不是建议在线业务频繁调用的调优开关。

启用工作者之前,预期存活和死亡元组估计分别为一万和一千,手动清理计数为一,自动清理计数为零。应用数据的准确行数会另外查询核对,不能用估计值取代。固定门槛设为五十时,原比例得到两千零五十,新比例得到一百五十。测试表很小,最大门槛在这里不构成限制,因此可以直接看清比例变化带来的资格差异。

一千个旧版本已经超过新的门槛。脚本随后开启这张表的常规自动回收,并只降低它自己的比例因子,再等待自动计数增加。轮询块每次丢弃统计缓存,避免在同一个观察事务中反复读取旧快照。开启工作者之后,脚本没有再次执行手动清理,所以最终的自动计数必须来自真实后台工作,而不是被实验自己的命令伪造出来。

最后预期死亡元组估计为零,自动清理时间非空,自动计数为正。业务数据仍有一万行,且恰好一千行的值已更新为一。本次测试实例在预算内观察到了后台任务完成,但具体等待时间取决于调度相位和工作者可用情况。因此,断言检查的是事件是否发生与数据是否保持正确,而不是固定等待多少秒就一定成功。

找到真正限制清理的阶段

如果一直没有自动执行记录,应检查表级有效选项、服务器自动回收设置、统计收集、工作者活动和冲突锁。手动维护时间不能代替自动维护时间。如果工作者确实完成了任务,但旧版本仍然保留,应检查长事务和其他可见性边界。如果版本已经允许移除,却产生得比处理更快,才需要进一步评估资源容量、成本节流和写入方式。

针对某张大而繁忙的表调整比例,通常比全局改变所有表更有针对性。修改前保留原选项,在具有代表性的多个负载时段里比较维护计数、文件增长与业务延迟,不能只用一次短暂空闲期得出结论。业务状态、表规模和写入模式改变以后,也应重新评估或恢复覆盖设置。

实验最后删除自己的整张表,所以不会留下调参结果;真实表的配置则需要明确维护。即使更新流量后来降低,事务年龄与冻结需求仍然存在,不能因为死元组图表安静了就长期关闭后台工作。触发资格、执行记录、清理效果和前台代价同时得到验证,才足以判断这次调校是否有效。

来源