如果您运行PostgreSQL有一段时间了,您一定听说过autovacuum。是的,autovacuum,这是每个人都要求你不要关闭的东西,它应该保持你的数据库干净,并自动减少膨胀。
然而,想象一下:在一个晴朗的日子里,您看到数据库的大小比您预期的要大,数据库上的I/O负载增加了,并且在工作负载没有太大变化的情况下,事情变慢了。你开始调查可能发生的事情。您运行了极好的Postgres膨胀查询,发现有很多膨胀。因此,您可以手动运行VACUUM命令来清除Postgres数据库中的膨胀。
但是,你必须解决空间里的复杂的事物:为什么Postgres autovacuum不首先清理膨胀?上面的故事听起来熟悉吗?
Autovacuum和VACUUM提供了许多配置参数来调整它以适应您的工作负载,但挑战是确定要调整哪些参数。在这篇文章中,基于我在Citus大会上的演讲:一个为博士后举办的活动,你将学会找出问题所在,以及调整什么使其更好。
更具体地说,您将学习如何调查和修复这3种常见的自动真空问题:
-
问题#1:Autovacuum没有足够频繁地触发Vacuum
-
问题#2:Vacuum太慢
-
问题#3:真空清除器无法清理死排
另一种常见的自动真空问题是事务id环绕相关问题,这本身就是一个内容丰富的主题。在未来,我计划写一篇单独的后续博客文章来关注这个话题。
本文概述了所有13个autovacuum提示
这张“autovacuum提示”的备忘单图表概述了您将在本博客文章中了解的所有Postgres Autovacum修复:

Autovacuum简介
如果您还不熟悉,Postgres使用多版本并发控制(MVCC)来保证隔离,同时提供对数据的并发访问。这意味着数据库中可以同时存在一行的多个版本。因此,当删除行时,旧版本仍然保留,因为旧事务可能仍在访问这些版本。
一旦所有需要行版本的事务完成,就可以删除这些行版本。这可以通过真空命令完成。现在,真空可以手动运行,但这需要您监控并决定各种事情,例如:何时运行真空,哪些表格需要真空,多久进行一次真空等。
为了让您的生活更轻松,PostgreSQL有一个autovacuum实用程序,它可以:
-
每隔autovacuum_naptime秒唤醒一次
-
检查“显著修改”的表
-
启动更多工人运行真空,并并行分析这些表上的作业。
你可以从Joe Nelson关于为什么autovacuum不是敌人的文章中了解更多关于Autovacuma的信息。
现在,上面项目符号#2中“显著修改”的定义以及并行抽真空的程度在很大程度上取决于您的工作量、事务速率和硬件。让我们开始用最常见的自动真空问题之一来调试autovacuum Autovacum,Autovacun没有对“显著修改”的表进行真空处理。
问题#1:Autovacuum没有足够频繁地触发真空
如果(非事务id环绕相关),则通常会为表触发清空
废弃元组>aautovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor*元组数
或
插入的元组数>autovacuum_vacuum_insert_threshold + autovacuum_vacuum_insert_scale_factor*元组数。
如果您看到膨胀超过预期,并且发现自己需要手动运行真空清除器来清除膨胀,这表明autovacuum没有足够频繁地对表格进行真空清除。
您可以通过检查pg_stat_user_tables来检查清空表的时间和频率。如果您的大表显示在这里,自动真空计数较低,并且上次自动真空良好是在过去,这是另一个迹象,表明自动真空没有在正确的时间对您的表进行清除。
SELECT last_autovacuum, autovacuum_count, vacuum_count FROM
pg_stat_user_tables;
为了以正确的频率对表格进行真空处理,您应该根据表格的大小和增长率调整autovacuum_vacuum_scale_factor和autovacuum_vacuum_insert_scale_factor。
- 例如,对于具有1B行的表,当200M行发生变化时,默认比例因子将导致触发真空,这是相当大的膨胀。为了使其达到更合理的值,根据变化率和大小将其设置为0.02甚至0.002可能更明智。
问题#2:真空太慢
您可能会遇到的第二个问题是,您的表被清空的速度太慢。这可能表现为膨胀的增长,因为清理膨胀的速率低于事务速率。或者,当您检查pg_stat_activity时,您会看到真空进程在您的系统上不断运行。
有几种方法可以加快抽真空:这些建议适用于自动抽真空和手动触发抽真空。
降低成本限制的影响
您应该检查的第一件事是是否启用了成本限制。真空运行时,系统维护一个计数器,跟踪不同输入/输出操作的估计成本。当该成本超过autovacuum_vacuum_cost_limit(或vacuum_cost_limit)时,进程休眠以获得autovacuum_vacuum_cost_delay(或vacuum_cost_delay)毫秒。这被称为成本限制,用于减少抽真空对其他进程的影响。
如果您注意到真空度落后,您可以禁用成本限制(通过将autovacuum_vacuum_cost_delay设置为0),或通过将autovacuum_vacuum_cost_delay减小或将autovacuum_vacuum_cost_limit增大到高值(如10000)来减少其影响。
增加并行工作者的数量
Autovacuum只能并行抽真空autovacuum_max_workers表。因此,如果有数百个表正在被主动写入(并且需要清空),一次执行3个可能需要一段时间(3是autovacuum_max_workers的默认值)。
因此,在具有大量活动表的场景中,如果您有足够的计算来支持运行更多的autovacuum worker,则可能需要将Autovacum_max_worker增加到更高的值。
在增加自动真空清除器工人数量之前,请确保您没有受到成本限制。成本限制在所有活跃的autovacuum工人之间共享,因此仅仅增加并行工人的数量可能没有帮助,因为他们每个人都会开始做更少的工作。
为了找到更多关于调整内容的想法,可能有必要研究pg_stat_progress_vacuum,以了解您正在进行的真空处于什么阶段,以及如何提高其性能。让我们看几个例子,从中可以得出有用的见解。
通过预取和缓存提高扫描速度
为了了解真空的进展速度,您可以在pg_stat_progress_vacuum中比较heap_blks_scanned和heap_blks_total随时间的变化。如果您看到进度很慢,并且阶段是扫描堆,这意味着真空需要扫描许多堆块才能完成。
在这种情况下,可以通过使用pg_prewarm之类的东西或通过增加shared_缓冲区来预取内存中较大的关系,从而更快地扫描堆。
增加内存以存储更多的死元组
扫描堆时,真空收集内存中的死元组。它可以存储的死元组的数量由maintenance_work_mem(或autovacuum_work_mem,如果设置)决定。一旦收集到最大数量的元组,vacuum必须切换到抽真空索引,然后在索引和堆被抽真空后(即在索引抽真空周期后)再次返回扫描堆。
所以,如果你注意到pg_stat_progress_真空中的index_vacuum_计数很高,这意味着真空必须经历许多这样的指数真空循环。
为了减少真空需要的循环数并使其更快,可以增加autovacuum_work_mem,以便真空可以在每个循环中存储更多的死元组。
并联真空指数
如果您看到pg_stat_progress_vacuum中的阶段很长时间都在清空索引,那么您应该检查表上是否有很多索引正在清空。
如果有许多索引,可以通过增加max_parallel_maintenance_workers 来并行处理索引,从而加快清理速度。请注意,只有手动运行真空命令时,此配置更改才会有所帮助。(遗憾的是,自动真空目前不支持并行真空。)
有了所有这些建议,你应该能够显著加快清除速度。但是,如果真空及时完成,您仍然注意到死元组没有下降,该怎么办?在接下来的段落中,我们将试图找到这种新型问题的原因和解决方案:真空完成,但无法清理死角。
问题#3:真空清除器无法清理死行
真空只能清理其他事务不需要的行版本。但是,如果博士后认为某些行是“需要的”,他们将不会被清理。
让我们探索真空无法清理行的4种常见场景(以及如何处理这些场景!)
-
长期运行事务
-
长时间运行的待命事务,热备份反馈=开启
-
未使用的复制插槽
-
未承诺的准备交易
如果您有一个已运行数小时或数天的事务,则该事务可能保留在行上,不允许真空清理行。您可以通过运行以下命令来查找长期运行的事务:
SELECT pid, datname, usename,
state, backend_xmin
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC;
为了防止长时间运行的事务阻塞清空,可以通过在其PID上运行pg_terminate_backend()来终止它们。
要以主动的方式处理长期运行的事务,您可以:
- 设置大statement_timeout以自动超时长查询,或
将idle_in_transaction_session_timeout设置为超时打开事务中空闲的会话,或
将log_min_duration_statement设置为至少记录长时间运行的查询,以便您可以对它们设置警报并手动终止它们。
待机状态下长时间运行的查询,热备份反馈=开启
通常,Postgres可以在任何事务看不到行版本时立即清理该行版本。如果您在具有备用节点的主节点上运行Postgres,那么真空可能会清理主节点上的行版本,这是备用节点上的查询所需要的。这种情况称为“复制冲突”——当检测到这种情况时,备用节点上的查询将被取消。
为了防止由于复制冲突而取消对备用节点的查询,可以将hot_standby_feedback设置为on,这将使备用节点向主节点通知其上运行的最旧事务。因此,主服务器可以避免清理仍由备用服务器上的事务使用的行。
然而,将hot_standby_feedback设置为on也意味着,备用服务器上长时间运行的查询能够阻止主服务器上的行被清理。
要获得所有备用项的xmin界限,您可以运行:
SELECT pid, client_hostname,
state, backend_xmin
FROM pg_stat_replication
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC;
为了避免由于备用服务器上的长时间运行事务而导致主服务器过度膨胀,可以采取以下方法之一:
继续处理复制冲突,并将hot_standby_feedback设置为off。
将vacuum_defer_cleanup_age设置为更高的值,以便将清理主服务器上的行的时间延迟到vacuum_defer_cleanu_ age事务结束,从而使备用查询有更多时间完成,而不会发生复制冲突。
最后,您还可以跟踪和终止备用服务器上的长时间运行查询,就像我们在上面的“长时间运行事务”一节中讨论的主服务器上的查询一样。
为了避免由于备用服务器上的长时间运行事务而导致主服务器过度膨胀,可以采取以下方法之一:
-
继续处理复制冲突,并设置hot_standby_feedback = off。
-
将vacuum_defer_cleanup_age设置为更高的值,以便将清理主服务器上的行的时间延迟到vacuum_defer_cleanup_age事务结束,从而使备用查询有更多时间完成,而不会发生复制冲突。
-
最后,您还可以跟踪和终止备用服务器上的长时间运行查询,就像我们在上面的“长时间运行事务”一节中讨论的主服务器上的查询一样。
未使用的复制插槽
Postgres中的复制槽存储副本追赶主副本所需的信息。如果复制副本停机或严重落后,则无法在主服务器上清空复制插槽中的行。
只有当hot_standby_feedback=on时,物理复制才会出现这种额外的膨胀。对于逻辑复制,您将只看到目录表的膨胀。
您可以运行下面的查询来查找包含要保留的旧事务的复制插槽。
SELECT slot_name, slot_type,
database, xmin, catalog_xmin
FROM pg_replication_slots
ORDER BY age(xmin),
age(catalog_xmin) DESC;
找到它们后,可以通过运行pg_drop_replication_slot()删除非活动或不需要的复制插槽。您还可以应用本节中关于如何管理物理复制的热备份反馈的知识。
未承诺的准备交易
Postgres支持两阶段提交(2PC),它有两个不同的步骤。首先,使用PREPARE transaction准备事务,然后使用COMMIT prepared提交事务。
2PC是弹性事务,旨在容忍服务器重启。因此,如果出于某种原因,您有任何准备好的事务悬而未决,那么它们可能会保留到行中。您可以通过运行以下命令来查找已准备好的旧事务:
SELECT gid, prepared, owner,
database, transaction AS xmin
FROM pg_prepared_xacts
ORDER BY age(transaction) DESC;
您可以通过运行手动准备的回滚来删除挂起的2PC事务。
另一种可能性:清除会被反复终止
Autovacuum知道这是一个系统过程,其优先级低于用户查询。因此,如果由autovacuum触发的进程无法获取其需要抽真空的锁,则该进程将自行结束。这意味着,如果某个表几乎一直在运行DDL,真空可能无法获取所需的锁,因此死行将不会被清理。
如果您注意到无法获得正确的锁会导致膨胀,您可能需要做以下两件事之一:
-
手动清除(好消息是手动清除不会自行终止)或
-
管理该表上的DDL活动,为autovacuum提供清理死行的时间
关于这个主题的另一个你可能会发现有用的资源是Laurenz Albe的博客文章,关于为什么真空不能从表中删除死行的四个原因。
调整Postgres autovacuum的备忘单
现在,您已经了解了调试Postgres autovacuum问题的原因和13个提示,您应该能够处理以下问题:(1)Autovacum触发真空的频率不够;(2)真空过慢;(3)真空没有清理死排。
如果您已经解决了所有这些问题,而autovacuum仍然无法跟上您的交易率,那么可能是时候将您的Postgres服务器升级到更大的硬件,或者使用Citus扩展您的数据库了。
下面,我提供了一个参考表,总结了我们在这篇文章中提到的优化autovacuum的所有不同Postgres配置。
用于调试Postgres Autovacuum的配置参数



*更改此配置可能会影响除autovacuum之外的查询。要了解更多的含义,请参阅Postgres文档。
**对超时的这些更改将适用于所有事务,而不仅仅是保留死行的事务。
旁注:您可能遇到的另一类自动真空问题与事务id环绕真空有关。它们是基于不同的标准触发的,其行为与常规真空不同。因此,他们应该有自己的博客文章。我很快会写这篇博客文章的第2部分,重点是什么是事务ID环绕自动真空,是什么使它们不同,以及如何处理它们运行时遇到的常见问题。敬请期待!
另一方面,我在Citus大会上发表的博士后演讲:博士后的活动都是关于优化自动真空。如果你有问题或反馈,你可以在推特上通过@samay_sharma联系我。
原文标题:Debugging Postgres autovacuum problems: 13 tips
原文作者:Samay Sharma
原文链接:https://www.citusdata.com/blog/2022/07/28/debugging-postgres-autovacuum-problems-13-tips/




