pg13特性:仅插入表上的autovacuum

pg13特性:仅插入表上的autovacuum

在这里插入图片描述

大多数人都知道autovacuum对于解决死元组是必要的。这些死元组是PostgreSQL的MVCC实现的副作用。所以很多人在PostgreSQL v13看到这个特性时会感到困惑,commit b07642dbc 增加了autovacuum对仅插入表(也被叫做“append-only”表的回收。
本文解释了这背后的原因,并就如何最好地使用新特性提供了一些建议。它还将解释如何在较旧的PostgreSQL版本中实现类似的功能。
请注意,我在这里所说的仅插入表也适用于主要是插入的表,这些表仅接收很少的更新和删除。

insert触发的autovacuum是怎么工作的

从 v13 开始,PostgreSQL 将收集自表上次VACUUM以来插入了多少行的统计信息。您可以在catalog视图pg_stat_all_tables的新列“n_ins_since_vacuum”(以及pg_stat_user_tables和pg_stat_sys_tables)中看到此新值。
每当该计数超过特定值时,autovacuum就会在表上运行。此值由两个新参数“autovacuum_vacuum_insert_threshold”(默认值 1000)和“autovacuum_vacuum_insert_scale_factor”(默认值 0.2)计算得出,如下所示:

insert_threshold + insert_scale_factor * reltuples

其中 reltuples 是表行数的估值,取自pg_class目录。
与其他autovacuum参数一样,您可以使用单个表的同名存储参数覆盖autovacuum_vacuum_insert_threshold和autovacuum_vacuum_insert_scale_factor。您可以通过将autovacuum_vacuum_insert_threshold设置为 -1 来禁用新功能。
可以使用“toast.autovacuum_vacuum_insert_threshold”和“toast.autovacuum_vacuum_insert_scale_factor”更改关联的 TOAST 表的参数。

案例1:vacuum防止仅插入表回卷

为什么仅插入表需要VACUUM?

PostgreSQL将事务ID存储在xmin和xmax系统列中,以确定哪个行版本对哪些查询可见。这些事务ID是无符号的4字节整数,因此在略高于40亿个事务后,计数达到上限。然后它“环绕”并从3再次开始。
如这篇博文所述,该方法将导致大约20亿次事务后数据丢失。因此,在此之前,必须“冻结”旧表行(标记为无条件可见)。这是autovacuum守护程序的众多工作之一。

为什么在仅插入表上做防止回卷的vacuum是一个问题

问题在于,如果没有仅插入表的autovaccum机制,PostgreSQL只有在最旧的unfrozen表超过2亿个事务时才会触发这种“防止回卷”动作。对于仅插入表,这通常是第一次在表上运行autovacuum。这有两个潜在的问题:

  • 防回卷autovacuum没有及时完成。那么,在数据损坏前的一百万个事务前,PostgreSQL将不接受任何新的事务。您必须在单用户模式下启动它并手动运行VACUUM。您可以在此博客和此博客中找到此类案例的描述。
  • 与其他autovacuum的运行不同,防回卷autovaccum在阻塞并发事务时不会选择放弃。这将阻止需要访问独占锁( ACCESS EXCLUSIVE )的短操作(如表上的 DDL 语句)。此类操作又将阻止对表的所有其他访问,以至于表上的处理将停止。您可以在此博客中找到描述的此类案例。

如何保护自己免受破坏性的防回卷autovacuum

从PostgreSQL v13开始,默认设置应该已经可以保护您免受此问题的影响。这确实是新功能背后的动机。
对于早于 v13 的 PostgreSQL 版本,您可以通过更早地触发防回卷vacuum来实现类似的效果,从而减少中断业务访问的情况。例如,如果想要每100000个事务vacuum表,则可以设置以下存储参数:

ALTER TABLE mytable SET (
autovacuum_freeze_max_age = 100000
);

如果数据库中的所有表都是仅insert,则可以通过将vacuum_freeze_min_age设置为0来减少autovacuum的开销,以便在首次vacuum表时立即冻结元组。

案例2:仅插入表的仅索引扫描

postgresql的仅索引扫描是怎么工作的

如上所述,每一行都包含可见性的事务信息。但是,索引不包含此信息。现在,如果您考虑这样的 SQL 查询:

SELECT count(*) FROM mytables WHERE id < 100;

在ID上有索引的地方,您需要的所有信息都可以在索引中找到。因此,您不需要获取实际的表行(“heap fetch”),这是索引扫描中昂贵的部分。但不幸的是,无论如何您都必须访问表行,只是为了检查索引条目是否可见。
为了解决这个问题,PostgreSQL有一个快捷方式,可以使仅索引扫描成为可能:visibility map。此数据结构每8kB表块存储两个位,其中一个指示块中的所有行是否对所有事务可见。如果查询扫描索引条目并发现包含引用的表行的块是完全可见的,则可以跳过检查该条目的可见性。
因此,如果表的大多数块在visibility map中标记为全部可见,则可以在 PostgreSQL 中进行仅索引扫描。

仅插入表上的仅索引扫描的问题

由于VACUUM删除了使表块全部可见所必需的死元组,因此 VACUUM 也会更新visibility map。因此,为了使大多数块全部可见以获得仅索引扫描,VACUUM 需要足够频繁地在表上运行。
现在,如果表收到足够的 UPDATE 或 DELETE,则可以将autovacuum_vacuum_scale_factor设置为较低的值,例如 0.005。然后autovacuum将使visibility map保持良好的状态。
但是对于仅插入表,在PostgreSQL v13之前获取仅索引扫描并不那么简单。与此相关的问题的报告在这里

在仅插入表上怎么做到仅索引扫描

从 PostgreSQL v13 开始,您所要做的就是降低表上的autovacuum_vacuum_insert_scale_factor:

ALTER TABLE mytable SET (
autovacuum_vacuum_insert_scale_factor = 0.005
);

在较旧的PostgreSQL版本中,这更加困难。您有两种选择:

  • 使用cron或其他调度程序安排定期vacuum
  • 将该表的autovacuum_freeze_max_age设置得更低,以便更频繁的触发autovacuum

案例3:仅索引扫描上的hintbits

在PostgreSQL中,读取新创建的行的第一个查询必须查阅提交日志(commit log),以确定创建该行的事务是否已提交。然后,它在保留该信息的行上设置一个提示位(hintbits)。这样,第一个读取后省去了将来的读取时检查提交日志的工作量。
结果,新行的第一个读取“弄脏”了(在内存中修改)包含它的块。如果最近在表中插入了大量行,则可能会导致第一个读取的性能下降。因此,在PostgreSQL中,在插入(或复制)大量行后对表进行VACUUM处理被认为是一种很好的做法。
但人们并不总是遵循这个建议。此外,如果要编写支持多个数据库系统的软件,则必须为单个系统添加特殊情况很烦人。借助新功能,PostgreSQL 会在大量插入后自动vacuum仅插入表,因此您少了一件需要担心的事情。

未来的工作

在讨论新特性的过程中,我们看到还有很大的改进空间。autovacuum已经相当复杂(只需查看一下很多的配置参数就知道),并且仍然不能做所有事情。例如,真正的仅插入表将受益于冻结行。另一方面,对于接收某些更新或删除的表,以及生存时间不足而达到回卷年龄的表分区,这种aggressive冻结可能会导致不必要的 I/O。
Andres Freund 传播一个有前途的想法,每当块变脏时,就冻结块中的所有元组,也就是,反正都不得不写。
根本问题是autovacuum有许多不同的用途。基本上,它是解决PostgreSQL的MVCC架构所有问题的灵丹妙药。这就是为什么它如此复杂。但是,需要对具体情况进行全新的设计才能达到这种效果。

结论

虽然乍一看似乎有些矛盾,但仅插入表的autovacuum缓解了大型数据库过去遇到的几个问题。
在人们收集“大数据”的世界里,保持这些数据库的平稳运行变得更加重要。细心的调整,这甚至在PostgreSQL v13之前就能实现。但autovacuum并不容易调优,许多人缺乏所需的知识。因此,拥有新的autovacuum功能可以自动处理更多潜在问题是件好事。

译自https://www.cybertec-postgresql.com/en/postgresql-autovacuum-insert-only-tables/
Laurenz Albe发布于2020年

猜你喜欢

转载自blog.csdn.net/qq_40687433/article/details/130873963