快速回顾一下,autovacuum
后台进程清理死行,例如旧的已删除行版本。您也可以通过运行手动执行清理VACUUM
,但autovacuum
会根据表中死行的数量在适当的时候自动执行清理,不要太频繁但足够频繁以控制“垃圾”的数量。
一般来说,autovacuum
不能运行得太频繁,只有在表中累积了一定数量的死行后才会执行清理。但它可能会由于各种原因而延迟,导致表和索引变得比预期的要大,这正是这篇文章的主题。那么常见的罪魁祸首是什么以及如何识别它们呢?
节流
autovacuum worker
被限制为每个时间间隔只能执行一定数量的工作。默认限制相当低,大约 4MB/s 的写入,8MB/s 的读取。这适用于像 Raspberry Pi 这样的微型机器或 10 年前的小型服务器,但现在的机器更强大(在 CPU 和 I/O 方面)并且处理更多的数据。
假设您有几张大表和几张小表。如果所有三个autovacuum
工作人员都开始清理大表,则无论小表累积了多少死行,都不会清理任何小表。假设您有足够的监控,识别这并不是特别困难。寻找所有autovacuum
工作人员都忙碌而尽管累积了许多死行但表未被清理的时间段。
所有必要的信息都在 pg_stat_activity
( autovacuum worker
进程数) and pg_stat_all_tables
(last_autovacuum
and n_dead_tup
)。
增加autovacuum
工人数量不是解决方案,因为总工作量保持不变。您可以指定每个表的限制,从总限制中排除该工作人员,但这仍然不能保证在需要时会有可用的工作人员。
正确的解决方案是调整节流,使用与硬件配置和工作负载模式相关的合理限制。如果你能减少数据库中产生的死行数量,那将是一个理想的解决方案。
从这一点来看,我们假设节流不是问题,即工作autovacuum
人员不会长时间饱和,并且在所有表上触发清理而不会出现不合理的延迟。
长事务
所以,如果定期清理表,肯定不会积累很多死行,对吧?很不幸的是,不行。这些行在被删除后实际上并不是立即“可移动”的,但只有在没有可能看到它们的事务时才可以。确切的行为取决于其他事务正在(正在)做什么和序列化级别,但一般来说:
读已提交
- 运行查询块清理
- 空闲事务仅在执行写入时才阻止清理
- 空闲事务(没有任何写入)不会阻止清理(但无论如何保留它们不是一个好习惯)
可序列化
- 运行查询块清理
- 空闲事务块清理(即使它们只进行了读取)
在实践中它当然更细微,但解释所有不同的位需要首先解释 XID 和快照是如何工作的,这不是本文的目标。您真正应该从中得出的结论是,长事务不是一个好主意,尤其是如果这些事务可能已完成写入。
当然,您可能需要长时间保留事务是有充分正当理由的(例如,如果您需要确保所有更改的 ACID)。但要确保它不会在不必要的情况下发生,例如由于应用程序设计不佳。
一个有点出乎意料的结果是高 CPU 和 I/O 使用率,由于autovacuum
一遍又一遍地运行,没有清除任何死行(或只是其中的一些)。因此,这些表仍然有资格在下一轮进行清理,弊大于利。
如何检测这个?首先,您需要监控长时间运行的事务,尤其是空闲事务。您需要做的就是从pg_stat_activity
. 视图定义随着 PostgreSQL 版本发生了一些变化,因此您可能需要稍微调整一下:
SELECT xact_start, state FROM pg_stat_activity;
-- count 'idle' transactions longer than 15 minutes (since BEGIN)
SELECT COUNT(*) FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND (now() - xact_start) > interval '15 minutes'
-- count transactions 'idle' for more than 5 minutes
SELECT COUNT(*) FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND (now() - state_change) > interval '5 minutes'
您也可以简单地使用一些现有的监控插件,例如check_postgres.pl。那些已经包括这种类型的健全性检查。您必须决定什么是合理的事务/查询持续时间,这是特定于应用程序的。
从 PostgreSQL 9.6 开始,您还可以使用idle_in_transaction_session_timeout
自动终止空闲时间过长的事务。同样,对于长查询,有statement_timeout
。
另一个有用的东西是VACUUM VERBOSE
它实际上会告诉你有多少死行还不能被删除:
db=# VACUUM verbose z;
INFO: vacuuming "public.z"
INFO: "z": found 0 removable, 66797 nonremovable row versions in 443 out of 443 pages
DETAIL: 12308 dead row versions cannot be removed yet.
...
它不会告诉您哪个后端阻止了清理,但它是正在发生的事情的一个非常清楚的标志。
注意:您无法轻松地从中获取此信息,autovacuum
因为它仅在默认情况下记录DEBUG2
(并且您肯定不希望在生产中使用该日志级别运行)。
热备长查询
让我们假设表被及时清理,但没有删除死元组,导致表和索引膨胀。您正在监控pg_stat_activity
并且没有长时间运行的事务。可能是什么问题?
如果您有一个流式副本,那么问题很可能就在那里。如果副本使用hot_standby_feedback=on
,则副本上的查询几乎就像主服务器上的事务一样,包括阻塞清理。当然,hot_standby_feedback=on
在对副本运行长查询(例如分析和 BI 工作负载)时恰好使用它,以防止由于复制冲突而取消。
遗憾的是,您必须做出选择,要么保留hot_standby_feedback=on
并接受清理延迟,要么处理取消的查询。您也可以使用它max_standby_streaming_delay
来限制影响,尽管这并不能完全阻止取消(因此您仍然需要重试查询)。
实际上,现在还有第三种选择,逻辑复制。您可以使用 PostgreSQL 10 中提供的新逻辑复制来复制更改,而不是对 BI 副本使用物理流复制。逻辑复制放松了主副本之间的耦合,并使集群大部分独立(独立清理等等)。
这解决了与物理流复制相关的两个问题,延迟清理主数据库或取消 BI 副本的查询。不过,对于服务于 DR 目的的副本,流式复制仍然是正确的选择,但是那些副本没有(或不应该)运行长时间查询。
注意:虽然我提到逻辑复制将在 PostgreSQL 10 中可用,但基础设施的很大一部分在以前的版本中可用(特别是 PostgreSQL 9.6)。因此,您甚至可以在旧版本上执行此操作(我们为我们的一些客户这样做),但 PostgreSQL 10 将使它更加方便和舒适。
糟糕的autoanalyze
您可能会错过的一个细节是,autovacuum
工人实际上执行两项不同的任务。首先是清理(就像运行一样VACUUM
),而且还收集统计信息(就像运行一样ANALYZE
)。并且这两个部分都使用autovacuum_cost_limit
。
但是在处理事务上有很大的不同。每当VACUUM
部分到达时autovacuum_cost_limit
,工作人员释放快照并休眠一段时间。但是ANALYZE
必须在单个快照/事务中运行,这会阻止清理。
这是搬起石头砸自己脚的优雅方式,特别是如果你也这样做的话:
- 增加
default_statistics_target
以从更大的样本中构建更准确的统计数据 - 降低
autovacuum_analyze_scale_factor
以更频繁地收集统计信息
意想不到的后果当然是ANALYZE
会更频繁地发生,需要更长的时间并且(与VACUUM
部分不同)会阻止清理。解决方案通常相当简单,不要降低autovacuum_analyze_scale_factor
太多。每次运行ANALYZE
10% 的表更改在大多数情况下应该绰绰有余。
n_dead_tup
我想提及的最后一件事是关于pg_stat_all_tables.n_dead_tup
值的变化。您可能认为该值是一个简单的计数器,每当创建新的死元组时递增,并在清除时递减。但它实际上只是对死元组数量的估计,由ANALYZE
. 对于小表(小于 240MB),差别不大,因为ANALYZE
读取整个表,所以非常准确。然而,对于大型表,它可能会发生很大变化,具体取决于对表的哪个子集进行采样。降低autovacuum_vacuum_scale_factor
使其更加随机。
n_dead_tup
所以在监控系统中查看时要小心。值的突然下降或增加可能仅仅是由于ANALYZE
重新计算不同的估计,而不是由于实际清理和/或表中出现新的死元组。
总结
总结为几个简单的要点:
autovacuum
只有在没有可能需要死元组的事务的情况下才能完成它的工作。- 长时间运行的查询会阻止清理。考虑使用
statement_timeout
以限制查询。 - 长时间运行的事务可能会阻止清理,确切的行为取决于隔离级别或事务中发生的事情,监视它们并在可能的情况下终止它们。
- 对副本的长时间运行查询
hot_standby_feedback=on
也可能会阻止清理。 autoanalyze
也受到限制,但与VACUUM
不同,它保留单个快照(因此会阻止清理)。n_dead_tup
只是ANALYZE
维护的估计值,因此预计会有一些波动(尤其是在大表上)。