autovacuum不是敌人

autovacuum不是敌人

一个常见的误区是,PostgreSQL中的大量读写工作负载不可避免地会导致数据库效率低下。我们听说过这样的情况:用户遇到每秒仅执行几百次写入的速度变慢的情况,并出于沮丧而转向Dynamo或Cassandra等系统。但是,只要配置正确,PostgreSQL就可以毫无问题地处理这些工作负载。
这些问题源于所谓的“膨胀”,即PostgreSQL和其他MVCC数据库的一种现象,导致空间使用增加和性能下降。我们将看到autovacuum,一种对抗膨胀的工具,通常是如何被误解和被错误配置的。通过对PostgreSQL内部的低级研究,我们将得到一个更好的autovacuum配置。最后,我们将考虑如何以在像Citus这样的PostgreSQL集群中分发数据时对抗膨胀。

MVCC天堂的麻烦

这就是问题开始的地方:数据库设计者希望数据库中的只读事务返回结果,而不会阻止并发更新。这样做可以减少读取密集型工作负载(如 Web 应用程序常见的工作负载)中请求的延迟。
但是,在没有暂停的情况下连续读取,需要为某些请求维护不同的全局快照,并最终协调差异。这些善意的谎言会导致所有骗子都熟悉的惩罚——你需要一个良好的内存来保证这些事情运行流程!
PostgreSQL和其他现代关系数据库使用一种称为多版本并发控制(MVCC)的技术来跟踪每个事务的快照,MVCC的空间损失称为膨胀。PostgreSQL是一台膨胀的机器,会毫无顾忌地创建它。PostgreSQL需要一个名为“vacuum”的外部工具的帮助,才有机会进行清理。
出于我们稍后将介绍的原因,臃肿的表和索引不仅浪费空间,而且会减慢查询速度。因此,这不是获得巨大硬盘驱动器并忘记膨胀的问题。有更新的地方就会膨胀,由你来vacuum。
它不像以前那么糟糕了。在遥远的过去(PostgreSQL 8之前),DBA必须手动清理。他们必须平衡vacuum的资源消耗与现有的数据库负载,以决定何时执行此操作,以及何时中断它。如今,我们可以配置“autovacuum”守护进程,以便在最合适的时间为我们清理。
正确配置时,autovacuum效果很好。但是,它的默认配置适用于几百兆字节大小的数据库,对于较大的数据库来说不够激进。在生产环境中,它开始落后。
一旦vacuum落后,它在运行时会消耗更多资源,并干扰正常的查询操作。这可能导致一个恶性循环,即数据库管理员错误地重新配置“资源占用”autovacuum,使其运行频率降低或根本不运行。autovacuum不是敌人,关闭它是灾难性的。

瘦子在膨胀

PostgreSQL用递增的标识符(txid)对每个新事务进行编号。表中的所有行都有隐藏列(xmin、xmax),记录允许查看该行的最小和最大事务 ID。你可以想象 SELECT 语句隐式包含 WHERE xmin <= txid_current() AND (xmax = 0 OR txid_current() < xmax)。任何活动事务或未来事务都无法看到的行被视为“死”。这意味着 xmin ≤ txid < xmax 没有活动事务。
新插入或更新的行使用为其 xmin 创建它们的事务的 txid,已删除的行使用 xmax 设置为删除它们的 txid。
快速说明:

begin;

select txid_current(); -- suppose it returns 1
create table foo (bar integer);
insert into foo (bar) values (100);

select xmin, xmax from foo;

commit;

返回

┌──────┬──────┐
│ xmin │ xmax │
├──────┼──────┤
│ 	10 │
└──────┴──────┘

如果更新行xmin会增加

begin;

update foo set bar = 200;
select xmin, xmax from foo;

commit;

结果

┌──────┬──────┐
│ xmin │ xmax │
├──────┼──────┤
│ 	20  │
└──────┴──────┘

这没有显示的是表中现在有一个死行。更新一行实际上会删除它,并插入一个具有更改值的新行。我们看到的行是新插入的(由 txid 2 插入),原始行挂在磁盘上,xmin=1,xmax=2。我们可以通过询问有关此表中元组(行)的信息来确认。

create extension pgstattuple;

select tuple_count, dead_tuple_count from pgstattuple('public.foo');
┌─────────────┬──────────────────┐
│ tuple_count │ dead_tuple_count │
├─────────────┼──────────────────┤
│ 			11 │
└─────────────┴──────────────────┘

PostgreSQL 还提供了一个低级API来查看有关物理数据页(存储在磁盘上的表空间块)的信息。此 API 允许我们查看所有行的 xmin 和 xmax,尽管出于安全考虑,已删除行的值不可见。

create extension pageinspect;

select t_xmin, t_xmax from heap_page_items(get_raw_page('foo', 0));
┌────────┬────────┐
│ t_xmin │ t_xmax │
├────────┼────────┤
│ 	12 	  │
│ 	20     │
└────────┴────────┘

此时,你已经可以看到一种生成膨胀的方法:只需不断更新表中的大量行。如果关闭autovacuum,即使可见行数保持不变,表大小也会继续增长。另一种膨胀的方法是在事务中插入大量行,但回滚而不是提交。
如果autovacuum正在运行,它可以清理死行,除非…删除的行阻止清理!在这个恐怖电影场景中,一个事务长时间保持运行(例如分析查询),其 txid 可防止行被标记为死,即使它们被另一个语句删除也是如此。长时间运行的查询甚至不必查询已删除的行,查询开始时行的存在可确保无法删除它们。混合使用 OLTP 和长时间运行的分析查询是一种危险的鸡尾酒。
除了上面棘手的僵尸启示录之外,autovacuum可以通过适当的配置来控制。在考虑autovacuum之前,让我们先看看膨胀的一些后果。

膨胀和查询速度

除了浪费空间之外,膨胀还会损害查询速度。每个表和索引都存储为固定大小的页面(通常为 8KB)。当查询请求行时,数据库会将这些页加载到内存中。每页的死行越多,加载期间浪费的 I/O 就越多。例如,顺序扫描必须加载并传递所有死行。
膨胀还降低了查询的活元组一次放入内存的可能性。膨胀使每个物理页面的实时元组更稀疏,因此相同数量的活动行需要内存中的更多页面。这会导致swap,并使某些查询计划和算法不符合执行条件。
一个令人讨厌的表膨胀例子是PostgreSQL自己的catalog。catalog可能会膨胀,因为它们也是表。发生这种情况的一种解决办法是循环访问临时表,不断创建和销毁它们。这会导致catalog表不断更新。当catalog臃肿时,管理功能会变慢,甚至在 psql 中运行 \d 之类的事情也会变慢。
索引也会变得臃肿。索引是从数据键值到元组标识符的映射。这些标识符是命名为“堆”(也称为表)的页面以及该页面内的偏移量。每个元组都是一个独立的对象,需要自己的索引条目。行更新始终为行创建全新的索引条目。
由于一些原因,索引的性能下降不如表的性能下降严重。指向死元组的索引项可以标记为死元组。这会使索引在大小上膨胀,但不会导致不必要的堆查找。此外,对堆中不影响索引列的元组的更新使用一种称为 HOT 的技术来提供从死元组到其替身的指针。这允许查询通过跟踪堆中的指针来重用旧索引条目。
索引膨胀的大小考虑因素也很重要。例如,btree 索引由页面的二叉树组成(与你在堆中保存元组的页面大小相同)。叶节点页包含键值和元组标识符。统一的随机表更新倾向于使 btree 索引保持比较好的状态,因为它可以重用页面。但是,在保留一些散落的条目的同时影响树的一侧的不平衡插入/更新可能会导致许多大部分空白页面。
要查看 btree 索引是否有效地利用了其页面空间,你可以询问 pgstatindex。平均叶密度是索引叶页面使用的百分比:

SELECT avg_leaf_density FROM pgstatindex('btree_index_name');

调试autovacuum

autovacuum修整数据库并保持数据库够快速。当满足某些可配置条件时,它开始工作。并在检测到它对常规查询过于侵略性时休息一下。
对于集群中的每个数据库,autovacuum尝试每autovacuum_naptime启动一次新工作线程(默认为 1 分钟)。它一次最多运行autovacuum_max_workers(默认为 3 个)。
每个worker都寻找需要帮助的表。worker搜索PostgreSQL的统计记录找到发生了足够多的行更改的表。具体是,worker查找 [估计无效行数] ≥ autovacuum_vacuum_scale_factor * [估计表大小] + autovacuum_vacuum_threshold 的表。
worker开始从表中删除死元组并压缩页面。当所有worker继续工作时,autovacuum会保留他们正在消耗的I/O“积分”。不同类型的操作计入不同的配额(值可配置)。当使用的积分超过autovacuum_vacuum_cost_limit时,autovacuum会暂停所有worker autovacuum_vacuum_cost_delay毫秒。
vacuum是与时间的赛跑。压缩页面时,vacuum的worker会扫描堆以查找死行并将其添加到列表中。它使用该列表首先删除指向这些行的索引条目,然后从堆中删除这些行。如果有许多行需要清理并且maintenance_work_mem受到限制,则worker将无法在每次工作中处理尽可能多的死行,并且将不得不浪费时间更频繁地重复此过程。
这解释了autovacuum可能落后的一种方式:当许多死行累积并且autovacuum没有足够的维护工作内存来快速删除它们时,还会受到vacuum_cost_limit的限制。这可能在数据库中不成比例的大表上很明显。默认数据库值autovacuum_vacuum_scale_factor=0.2可能适用于小型表,但对于大型表来说太大。你可以配置每个表的参数:

ALTER TABLE <tablename>
SET autovacuum_vacuum_scale_factor = 0.01;

对于百万行表,这意味着autovacuum将在一万行失效后开始,而不是二十万行。它有助于防止膨胀失控。
当要清洁的臃肿表多于autovacuum_max_workers并且所有表都在继续膨胀时,autovacuum也会落后。worker无法绕过所有表。
以下是合理的autovacuum调整。当然,它们不会适用于所有数据库,但会让你朝着正确的方向前进。

参数 默认值 建议值
autovacuum max workers 3 5 or 6
maintenance work mem 64MB 系统内存* 3/(8 * autovacuum max workers)
autovacuum vacuum scale factor 0.2 Smaller for big tables, try 0.01 大表调小,试试0.01
autovacuum vacuum threshold 50 小表应该调大
autovacuum vacuum cost limit 200 也许不动它
autovacuum vacuum cost delay 20ms vacuum IO负载可以更高时调小

Variable PG Default Suggested
autovacuum max workers 3 5 or 6
maintenance work mem 64MB system ram * 3/(8 * autovacuum max workers)
autovacuum vacuum scale factor 0.2 Smaller for big tables, try 0.01
autovacuum vacuum threshold 50 Could be larger for small tables
autovacuum vacuum cost limit 200 Probably leave it alone
autovacuum vacuum cost delay 20ms Can turn it down if OK with more vacuum I/O load

时刻观察它

调整autovacuum后,你可以等待并观察数据库的响应方式。事实上,在调整autovacuum之前,你可能希望观察数据库随时间的变化,以避免过早优化。你正在寻找表和索引中的变化率和膨胀百分比。
使用以下脚本收集测量值:pgexperts/pgx_scripts。在cron作业中执行它们以每周跟踪进度。

扩展工作

大的繁忙表有很大的膨胀潜力,无论是从对vacuum比例因子的低灵敏度,还是由于行流失的程度。将大表水平拆分为较小的表非常有用,尤其是在有大量vacuum表的情况下,因为一次只有一个worker可以vacuum一个表。即便如此,运行更多的工作线程需要更多的维护工作内存。一种既可以拆分大型表又可以增加运行vacuum worker容量的解决方案是使用由多个物理 PostgreSQL 服务器和分片表组成的分布式数据库。
在分布式数据库中扩展的不仅仅是用户查询,还有vacuum。公平地说,如果查询在单个 PostgreSQL 实例上扩展良好并且膨胀是唯一的问题,那么切换到分布式系统是矫枉过正的;还有其他方法可以积极修复急性膨胀。然而,额外的vacuum能力是分布式的一个令人愉快的副作用。使用Citus Community Edition等开源工具分发PostgreSQL数据库比以往任何时候都容易。
或者,你可以更进一步,忘记使用云中的托管 Postgres 数据库服务自行配置autovacuum。2022 年 10 月更新:我的团队的 Citus 数据库(横向扩展 Postgres)在云中作为托管服务提供,作为适用于Azure Cosmos DB for PostgreSQL

译自:https://www.citusdata.com/blog/2016/11/04/autovacuum-not-the-enemy/
发布于2016年11月4日

猜你喜欢

转载自blog.csdn.net/qq_40687433/article/details/130874016
今日推荐