暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

调试Postgres autovacuum问题:13 个技巧

原创 Lily 2023-02-03
1104

如果你已经运行过PostgreSQL,你一定就听说过autovacuum。是的,autovacuum,一个每个人都教你不要关掉,一个被用来保持数据库干净并自动减少膨胀的东西。

可是-想象一下:晴朗的一天,你看到数据库的大小比你期望的要大,数据库的IO负载已经增长,负载上没有任何调整,速度也变慢了。你开始调查发生了什么。你运行优秀的膨胀查询语句。然后你发现你有很多膨胀。那么在你的postgres数据库,你手动运行VACCUM来清除膨胀。 这很棒!

随后,你对房间的大象讲道:为什么Postgres的autovacuum没有第一时间清理膨胀?上面的故事听起来很熟悉,是不是?好,你不是一个人。

Autovacuum和Vacuum提供了一串设置参数去调整,以适应你的负载,但挑战在于确定要调整哪些。在这篇文章-基于我的optimizing autovacuum中谈了Citus Con:An Event for Postgres-你将学会找出问题所在,以及如何调整以使其更好。

更具体地说,你会学着怎么排查-怎么修复这三个常见的autovacuum问题:

  • 问题1:Autovacuum没有足够地触发vacuum

  • 问题2:Vacuum太慢了

  • 问题3:Vacuum没有清查失效行

autovacuum另一个常见的问题是关于事务ID回卷相关的,对postgres来说,本来就是一个很重要的话题。未来,我打处写一篇单独写一篇关于那个话题的后续文章。

纵观这篇博文的13个autovacuum贴士

这个autovacuum贴士的备忘录能让你看到这篇博文中postgres的所有autovacuum问题修复的概览。

调试postgres Autovacuum的备忘录


图1: Postgres中3种最常见的autovacuum问题的13种不同类型的autovacuum修复方法示意图

Autovacuum介绍

如果你还不熟悉,当并发访问数据时,Postgres使用多路并发控制(MVCC)事保障隔离性。这也意味着在数据库中会同时存在一行多个版本。所以,当行被删除时,旧版本仍然保留着,以防旧事务还会访问这些版本。

一旦所有需要这一行的事务完成,这些行就会被移除。这就是VACUUM做的事。现在,VACUUM可以手动运行而不需要你去监控,也不需要对各类的事做决定,如:什么时候运行vacuum,哪一张表需要vacuum,vacuum多久跑一次等。

为了让你的生活更简单,PostgreSQL有一个autovacuum工具:

  • 每autovacuum_naptime秒唤醒

  • 检查那些已经"显著修改"的表

  • 在这些表上开启更多的worker进程去进行VACUUM和ANALYZE工作。

总体来说,从 Joe Nelson不错的博文"为什么Autovacuum不是敌人"中,你能学到更多关于autovacuum的知识。

现在,在上面提到的第2项“显著修改”的定义—并行时需要VACUUM的数量—很大程度上取决于你的工作负载,事务频率和硬件。我们从最平常一个autovacuum问题着手研究调试autovacuum—autovacuum未“显著修改”vacumm的表。

问题1:Autovacuum不会经常触发vacuum

对表来说,如果非事务id回卷相关,Vacuum通常会被触发

  • 过期的元组> autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * 元组数量或

  • 插入元组的数> autovacuum_vacuum_insert_threshold+autovacuum_vacuum_insert_scale_factor* 元组数量

如果你发现膨胀增长远远超过所期望的且你需要手动运行VACUUM清理膨胀,这其实是一种暗示,autovacuum没有经常对表vacuum。

你可通过通过表pg_stat_user_tables检查表何时被vacuum过,vacuum的频率是多少。如果你的大表以低自动vacuum数量显示,并且以前last_autovacuum运行的很好,那是另一种迹象,表明autovacuum并没有在正确的时间vacuum你的表。

SELECT last_autovacuum, autovacuum_count, vacuum_count FROM
pg_stat_user_tables;
复制

为了vacuum表以正确的频率,你该基于表大小和表的增长速率来调整autovacuum_vacuum_scale_factor 和autovacuum_vacuum_insert_scale_factor。

作为一个示例,一张表有1B的行,默认的比例因子会在表的行数变化至200M时触发vacuum,这是相当大的膨胀了。

为了使其达到更合理的值,根据变化率和大小,将其设置为0.02甚至0.002可能更明智。

问题2:Vacuum太慢

你也许会遇到第2个问题,vacuum的表太慢。这表明膨胀的增长是因为你清理膨胀的速度慢于你的事务速度。或者,你在系统里检查pg_stat_activity时会发现vacuum进程一直在运行。

有许多方法可以加快vacuum:这些建议可以用在自动vacuum和手动触发VACUUM.

减少cost limiting的影响

首先你该检查你是否设置允许cost liming。当vacuum在运行时,系统维护一个计数器,跟踪不同I/O操作的估计成本。当成本超过autovacuum_vacuum_cost_limit (或者 vacuum_cost_limit),进程会休眠 autovacuum_vacuum_cost_delay (或者vacuum_cost_delay) 毫秒。这称为cost limiting,用来减少vacuum在其他进程的影响。

如果你注意到vacuum正在下降,你可以disable cost limiting(设autovacuum_vacuum_cost_delay为0),要么通过减小autovacuum_vacuum_cost_delay或要么增加autovacuum_vacuum_cost_limit到一个更高值(如10000)来降低影响。

提高并行worker的数量

自动vacuum只能并行vacuum autovacuum_max_workers个表。所以,如果有数百个表正在被主动写入(并且需要vacuum),那么一次vacuum 3个表可能需要一段时间(3是autovacuum_max_workers的默认值)。

因此,在有大量的主动表的情况下,增加autovacuum_max_workers 到一个更高值也许是值得的-假设你有足够的预算来运行更多的自动vacuum worker。

在增加自动vacuum worker前,确认不会被cost limiting限制。Cost limit被所有活动的自动vacuum worker共享,所以只增加长并行worker的数量也许没有帮助,会使他们每个worker开始做更少的工作。

在怎么调试上想要更多的办法,也许有必要研究一下pg_stat_progressvacuum,以了解您正在进行的vaccum处理处于什么阶段,以及如何改进它们的性能。我们看一些例子,它也许会提供一些有用的见解。

通过预存和缓存提高扫描速度

要看到正在进行的vaccum有多快,你可以比较heap_blks_scanned和heap_blks_total在pg_stat_progress_vacuum表中的耗时。如果你看到进程缓慢并且phase是正在进行scanning heap,这意味着vacuum需要扫描大量堆块才能完成。

在这个例子里,你可以通过使用像pg_prewarm或者增加shared_buffers在内存中预取更大的表以便更快的扫描堆。

增加内存来存储更多的死元组

扫描堆时,vacuum在内存中收集死元组。死元组数量的收集取决于maintenance_work_mem(或者autovacuum_work_mem,如课设置了)。一旦元组的最大数量被收集,vacuum必定切换去vacuum索引,然后在索引和堆被vacuum后,再返回扫描堆(即在索引vacuum周期之后)。

所以,如果你注意到pg_stat_progress_vacuum表中的index_vacuum_count非常高,这意味着vacuum必须经历许多这样的索引清理周期。

为了减少清空周期需要使它更快,你可以增大autovacuum_work_mem以便在每个周期vacuum可以存储更多的死元组。

并行清理索引

如果看到pg_stat_progress_vacuum表的phase在vacuuming indexes很长时间,你该检查表上是否有很多索引被清理过。

如果有很多索引,你可通过增加max_parallel_maintenance_workers 并行处理索引使清理更快。注意,这个设置的改置只在你手动运行vaccum命令时有用(不幸地是,并行清理目前不支持自动清理)。

有了以上这些建议,你可以显著地加快清理的速度。但是,如果你的清理及时完成,你仍然注意到死元组没有降下来呢?在接下来的章节,我们将这新一类的问题尝试发现原因及解决办法:清理已经完成,但是没成清干净死行。

问题3:Vacuum没有清除掉死行

Vacuum只清除不再被事务需要的行版本。但是,如果Postgres认为确认的行仍然被“需要”,他们就不会被清除。

我们来探索4个vacuum不会清理行的常规场景(以及这些场景要怎么办)

  • 长时间运行的事务

  • 在备库上hot_standby_feedback = on长时间运行的事务

  • 无用的复制槽

  • 没有提交的预备事务

长时间运行的事务

如果有一个事务已经运行了好几个小时或好几天,这个事务也许保留了行,不允许vcuum清理这些行。你可通过运行以下SQL找到长运行事务:

SELECT pid, datname, usename,
state, backend_xmin
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC;
复制

为了阻止长运行事务阻塞vcuum运行,需要通过在PID上运行pg_terminate_backend() 来中断他们。

以积极主动的方式来处理长运行事务,可以

  • 设置一个大的statement_timeout来自动过期长查询,或者

  • 设置idle_in_transaction_session_timeout来过期那些在一个开放事务中空闲的会话,或者

  • 设置log_min_duration_statement至少记录长时间运行查询以便可以在这些查询中设置预警并能手动kill他们。

在设置了 hot_standby_feedback = on的备库上的长运行查询

典型地,Postgres会尽快清理一个行版本在它对其他事务不可见时。如果你运行Postgres 在一主一备节点上,有可能在主节点上vacuum要清理的行正是备库查询需要的。这种情形称为"复制冲突"-并且当它被发现时,在备节点上的查询会被取消。

为防止在备库上的查询因为“复制冲突”被取消,可设置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_cleanup_age事务过去,给备库更多的时间完成查询而不会遇到复制冲突。

  • 最后,可追踪并中断备库上的长运行查询,就像我们上面讨论的主库在长运行事务的章节一样。

未用的复制槽

Postgres 的复制槽存储了副本追赶主库所需的信息。如果副本宕了,或者严重滞后,复制槽中的行无法在主库被清理。

当设置了hot_standby_feedback = on,额外的膨胀只会发生物理复制上。对于逻辑复制来说,你会看到膨胀只在目录表上。

你可以运行以下SQL来发现复制槽上仍保留的旧事务。

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()删除不活动的或者不需要复制槽。你也可以应用在“在物理复制上如何管理hot_standby_feedback”部分学到的知识。

未提交的预备事务

Postgres支持两阶段提交(2PC),两阶段提交有两个不同的步骤。第一,事务通过PREPARE TRANSACTION 准备。第二,事务通过COMMIT PREPARED提交。

2PC是灵活的事务,意味着它允许服务重新启动。所以,如果你因为一些原因导致任何PREPARED事务遗留,他们也许会在行上锁住。可通过运行以下SQL发现旧的预备事务:

SELECT gid, prepared, owner,
database, transaction AS xmin
FROM pg_prepared_xacts
ORDER BY age(transaction) DESC;
复制

可通过手动运行ROLLBACK PREPARED来移除hang住的2PC事务。

另一种可能性:重复Vacuum获得中断

Autovacuum知道它是一个系统进程并且它的优先级低于用户的查询。所以,如果一个进程被autovacuum触发,它是不可能获得它需要的锁来vacuum,进程会结束自己。这意味着,如果一个特定的表几乎一直有DDL在其上运行,vacuum也许不能获得需要的锁,因此过期的行不会被清除。

你是否注意到不能获得正确的锁导致膨胀增加,你必须做以下两件事中的其中一件:

  • 手动VACUUM表(好消息是手动VACUUM不会自动结束进程自己)或者

  • 管理好在那张表上的DDL任务,给autovacuum时间来清除过期行

在这个主题上你能发现的另一个有用的资源是 Laurenz Albe’s 的博客文章

[为什么不能从一张表上清除过期行的四个原因]  https://www.cybertec-postgresql.com/en/reasons-why-vacuum-wont-remove-dead-rows/ 

Postgres autovacuum速查表

现在你已经了解了原因并且知道了在调试Postgres autovacuum问题上的13个提示,你就能处理这些问题:(1)autovacuum不是足够频繁触发Vacuum

或 (2)vacuum太慢

或 (3)vacuum没有清理过期行。

如果你已经试过了这些,autovacuum仍然没有跟上你的事务速度,那是时候升级你的Postgres服务到更大的硬件—或者用Citus扩展你的数据库到多节点。

下面,我纳入了一张参考表,这张表总结了我们在这篇文章提过的优化 autovacuum的不同的Postgres配置。

调试Postgres Autovacuum的配置参数


  • 更改此配置可能会影响autovacuum以外的查询。要了解更多的含义,请参阅Postgres文档.

  • 对超时的这些更改将应用于所有事务,而不仅仅是保留过期行的事务。

备注:你也许会遇到另一种类型的autovacuum问题和事务id回卷vacuum相关。这些是基于不同的标准触发的,其行为与常规vacuum不同。因此,他们也需要自己的一篇博文。不久我将在这篇文章的第2部分,主要聚集于什么是事务ID回卷autovacuum,是什么导致他们不同,并且当遇到他们在运行时,如何处理遇到的常规问题。继续优化!

我在Citus Con:An Event for Postgres上讲的Postgres都是关于优化autovacuum的。如果你有问题或反馈,你可以随时在Twitter上@samay_sharma联系我。

原文标题:Debugging Postgres autovacuum problems: 13 tips
原文作者:Samay Sharma
原文链接:https://www.citusdata.com/blog/2022/07/28/debugging-postgres-autovacuum-problems-13-tips/
复制



「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论