Postgresql - Vacuuming

vacuum是PG中很重要的知识点,值得好好学习,自己写的没有官方文档的详细,最后还是选择翻译。

以下内容翻译自官方文档

https://www.postgresql.org/docs/11/static/routine-vacuuming.html

*********************************************************************************************************

PostgreSQL 需要定期做 vacuum 维护。在一般情况下,使用 autovacuum 进程执行 vacuum 就足以满足。

如果向手动管理vacuum命令补充或替换守护进程的话,可以使用cron 或 scheduler 脚本调度执行。

1. vacuuming basics

定期执行 vacuum 命令有以下原因

  1. 恢复并重用由已更新或删除行占用的磁盘空间。
  2. 更新数据库查询计划器使用的数据统计
  3. 更新 visibility map,加快索引扫描
  4. 防止由于事务ID包装或多版本事务ID包装造成的非常久的数据丢失。

这些原因中的每个都规定执行频率和范围不同的vacuum操作。

vacuum有两种操作:标准 vacuum 和 vacuum full。

vacuum full 可以回收更多的磁盘空间,但是运行速度要慢的多。

标准 vacuum 可以与生产数据库操作并行运行。(可以增删改查,不能alter table。)

vacuum 会导致大量的I/O操作。

2. recovering disk space

PG中,对行的更新或者删除不会立刻删除旧版本,这种方法是多版本并发控制(MVCC)的好处,在对其他事务仍可见的情况下,不删除行版本。但最终,过时或删除的行版本对事务无用。必须把占用的空间回收以供新行重用,避免磁盘空间无限的增长。这是通过 vacuum 来完成的。

标准的vacuum 移除了表和索引中的过期版本,标记了可用于将来重用的空间。然后空间并不会返回给操作系统,除非表格末尾的一个或多个页面变得完全空闲且可以容易的获得独占表锁的特殊情况除外。相比之下 vacuum full 通过在没有过期版本的情况下编写完整的新版本的表文件来压缩表。这是最小化了表的大小,但需要很长时间,还需要为标的新副本添加额外的磁盘空间,直到操作完成为止。

通常 vacuum是经常做 标准 vacuum,避免需要 vacuum full。autovacuum 守护进程以这种方式工作,事实上将永远不会 vacuum full。这种方法中,不是想将表保持在最小的大小,而是保持磁盘空间的稳步使用,每个表占用的空间相当于最小空间,但vacuum的时候消耗了大量空间。虽然vacuum full可以用来将表缩小到最小,并将此盘返回给操作系统,但是如果表在将来再次增长,则没有什么意义。因此,对于维护经常更新的表,中等频率的标准vacuum 运行比不频繁的vacuum full运行时更好的方法。

有些管理员喜欢自己安排 vacuum。根据固定时间对标进行vacuum的困难在于,如果表的更新活动中出现意外的峰值,那么它可能会膨胀到某个点, vacuum full 对于回收空间确实是必要的。使用 autovacuum deamon可以缓解这个问题,因为守护进程根据更新活动动态的调度真空。除非有一个非常可预测的工作量,否则完全禁用守护进程是不明智的。可能的这种方案是设置守护进程的参数,以便它只会对浴场繁重的更新活动做出反应,防止失控。

对于不适用 autovacuum 的人来说,典型的方式是在低使用期内每天调度一次数据库范围的VACUUM,并根据需要更频繁的清空经常被更新的表。如果在集群中有多个数据库,不要忘记对每个数据库进行VACUUM;vacuumdb程序可能有帮助。

当表由于大量更新或删除活动而包含大量旧行版本时,纯VACUUM可能不令人满意。如果有这样一个表,并且需要回收它占用的超额磁盘空间,那么需要使用VACUUM FULL,或者使用CLUSTER或ALTER TABLE的表重写。这些命令重写表的整个新副本并为其创建新索引。所有这些选项都需要排他锁。注意,它们还临时使用与表的大小大致相等的额外磁盘空间,因为表的旧副本和索引在新副本完成之前无法释放。

3. updating planner statistics

查询计划器依赖于关于表内容的统计信息,以便为查询生成好的计划。这些统计数据由ANALYZE 命令收集,该命令可以为自己调用,也可以作为 vacuum中的可选步骤。重要的是要有合理准确的统计数据,否则糟糕的计划选择可能会降低数据库性能。

如果启用“autovacuum”守护进程,则在表内容充分更改时自动发出“ANALYZE”命令。然而,管理员可能更喜欢依赖手动调度的 ANAYZE 操作,特别是如果已知表上的更新活动不会影响“interesting”列的统计信息。守护进程严格按照插入或更新的行数来调度ANALYZE;它不知道这是否会导致有意义的统计变化。

对于空间恢复来说,频繁更新统计信息对于更新频繁的表比很少更新的表更有用。但是,即使对于更新频繁的表,如果数据的统计分布变化不大,可能也不需要统计更新。一个简单的经验法则是考虑表中列的最小值和最大值的变化。例如,包含行更新时间的时间戳列随着行的添加和更新将具有不断增加的最大值;这样的列可能需要比包含网站上访问的页面的URL的列更频繁的统计更新。URL列可能同样频繁地接收更改,但是其值的统计分布可能变化相对较慢。

可以在特定表上甚至只在表的特定列上运行ANALYZE,因此如果应用程序需要,可以灵活地更新一些统计数据,比其他的更频繁。然而,实际上,分析整个数据库通常是最好的,因为它是一种快速操作。分析使用统计随机抽样表的行,而不是读取每一行。

4. updating the visibility map

vacuum 为每个表维护 visibility map,以跟踪哪些页面只包含所有活动事务都可见的元组(以及所有未来事务,直到页面再次被修改)。

这有两个目的。首先,vacuum 本身可以跳过这些页面在下一次运行,因为没有任何清理。

第二,它允许在没有引用基础表的情况下仅使用索引来执行一些查询。由于PostgreSQL索引不包含元组可见性信息,所以通常的索引扫描为每个匹配的索引条目获取堆元组,以检查当前事务是否应该看到它。另一方面,仅索引扫描首先检查 visibility map 。如果知道页面上的所有元组是可见的,则可以跳过堆获取。这在可视数据可以防止磁盘访问的大型数据集上非常有用。 visibility map 大大小于堆,因此即使堆很大,也可以很容易地被缓存。

5. preventing transaction id wraparound failures

PostgreSQL的MVCC事务语义依赖于能够比较事务ID(XID)号:插入XID大于当前事务XID的行版本是“将来的”,不应该对当前事务可见。但是,由于事务ID的大小有限(32位),长时间运行的集群(超过40亿个事务)将遭受事务ID封装:XID计数器将封装为零,并且过去所有的突然事务都出现在将来,这意味着它们的输出变得不可见。简言之,灾难性数据丢失。(实际上,数据仍然存在,但如果您无法获得,那将是一种冷淡的安慰。)为了避免这种情况,必须至少每20亿次事务中抽真空一次每个数据库中的每个表。

定期VACUUM解决此问题的原因是,VACUUM将把行标记为冻结的,这表示它们是由在过去提交了足够多的事务插入的,插入事务的影响肯定对所有当前和将来的事务可见。使用 modulo-232 算法对正常XID进行比较。这意味着,对于每个正常的XID,有20亿个“旧”XID和20亿个“新”XID;另一种说法是,正常的XID空间是圆形的,没有终点。因此,一旦使用特定的正常XID创建了行版本,那么对于接下来的20亿个事务,无论我们讨论的是哪一个正常XID,行版本看起来都将是“过去的”。如果在超过二十亿次事务之后仍然存在行版本,那么它将突然出现在将来。为了防止这种情况,PostgreSQL保留了一个特殊的XID,FrozenTransactionId,它不遵循正常的XID比较规则,并且总是被认为比每个正常的XID都老。冻结的行版本被当作插入XID是 FrozenTransactionId 来对待,因此不管包装问题如何,对于所有正常事务,它们看起来都是“过去的”,所以无论多长时间,此类行版本在删除之前都是有效的。

vacuum_freeze_min_age 控制XID值在绑定XID的行将被冻结之前的值。如果否则将被冻结的行很快将被再次修改,则增加此设置可以避免不必要的工作,但是减少此设置会增加在必须再次清空表之前可能经过的事务的数量。

vacuum 使用可视图来确定必须扫描表的哪些页。通常,它将跳过没有任何死行版本的页面,即使那些页面可能仍然具有具有旧XID值的行版本。因此,正常的 vacuum 不会总是冻结表中的所有旧行版本。定期地,VACUUM 将执行积极的vacuum ,只跳过那些既不包含 dead rows ,也不包含任何未冻结XID或MXID值的页面。vacuum_freeze_table_age 控制VACUUM何时执行此操作:如果自上次扫描以来通过的事务数量大于vacuum_freeze_table_age 减去 vacuum_freeze_min_age,则扫描所有可见但不是全部冻结的页面。设置vacuum_freeze_table_age 为0强制 vacuum 使用更积极的策略扫描。

一个表可以空闲的最大时间是20亿次事务减去上一次积极 vacuum 时的 vacuum_freeze_min_age值。如果长时间没有真空,就会造成数据丢失。为了确保不会发生这种情况,在可能包含XID大于配置参数autovacuum_freeze_max_age 指定的 age 的未冻结行的任何表上调用 autovacuum_freeze_max_age。

这意味着,如果没有对表进行其他真空处理,则每隔autovacuum_freeze_max_age 减去 vacuum_freeze_min_age transactions事务后,将在其上调用一次autovacuum。对于经常为空间回收目的而vacuum 的表,这一点并不重要。然而,对于静态表(包括接收插入但未更新或删除的表),不需要 vacuum 进行空间回收,因此尝试最大化非常大静态表上的强制 autovacuums 之间的间隔是有用的。显然,一个人可以通过增加 autovacuum_freeze_max_age 或减少 vacuum_freeze_min_age 来做到这一点。

vacuum_freeze_table_age 的有效最大值为0.95*autovacuum_freeze_max_age ;高于该值的设置将被限制为最大值。高于 autovacuum_freeze_max_age 的值是没有意义的,因为反包装的autovacuum 无论如何都会在那个时间点被触发,并且0.95 乘法器在发生这种情况之前留下一些 空间来运行手动VACUUM。根据经验法则,vacuum_freeze_table_age 应该设置为稍微低于autovacuum_freeze_max_age 的值,留下足够的间隙,以便在该窗口中运行定期调度的VACUUM或由正常删除和更新活动触发的 autovacuum 。设置太接近可能会导致反包装的 autovacuum ,即使最近桌子是vacuum 回收空间,而较低的值导致更频繁的积极 vacuum 。

增加 autovacuum_freeze_max_age (和增加vacuum_freeze_table_age) 的唯一缺点是数据库集群的 pg_xact 和 pg_commit_ts 子目录将占用更多的空间,因为它必须存储提交状态和(如果启用了 track_commit_timestamp )时间戳所有事务存储到autovacuum_freeze_max_age 范围。提交状态每个事务使用2位,因此如果 autovacuum_freeze_max_age 被设置为最大允许值20亿,那么 pg_xact 可以预期增长到大约半千兆字节,pg_commit_ts 可以增长到大约20GB。如果与数据库总大小相比这很小,建议将 autovacuum_freeze_max_age 设置为其最大允许值。否则,根据您愿意允许的 pg_xact 和pg_commit_ts 存储设置它。(默认的,2亿个事务,转换为大约50MB的 pg_xact 存储和大约2GB的 pg_commit_ts 存储)。

减少 vacuum_freeze_min_age 的一个缺点是可能导致VACUUM做无用的工作:如果此后不久对行进行修改(导致它获得新的XID),则冻结行版本是浪费时间。因此,设置应该足够大,行不会被冻结,直到它们不太可能再发生变化。

为了跟踪数据库中最老的未冻结XID的版本,VACUUM将XID统计信息存储在系统表 pg_class 和 pg_database 中。特别是,表的 pg_class 行的重新冻结列包含该表的上一个主动 VACUUM 使用的冻结截止XID。由XID插入的事务所插入的所有行都超出了此XID XID。类似地,数据库的 pg_database 行的 datfrozenxid 列是出现在该数据库中的未冻结XID的下限——它只是数据库中每个表重新冻结值的最小值。检查这些信息的一种方便的方法是执行查询,例如:

SELECT c.oid::regclass as table_name, greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age FROM pg_class c LEFT JOIN pg_class t ON c.reltoastrelid = t.oid WHERE c.relkind IN ('r', 'm'); SELECT datname, age(datfrozenxid) FROM pg_database;

age 列测量从截止XID到当前事务的XID的事务数量。

VACUUM通常只扫描自上次真空以来已经修改过的页面,但是重新冻结的页面只有在扫描可能包含未冻结XID的表的每个页面时才能被推进。当重新冻结的事务比 vacuum_freeze_table_age 事务旧时,当使用 VACUUM 的 FREEZE 选项时,或者当尚未完全冻结的所有页碰巧需要真空来删除死行版本时,就会发生这种情况。当 VACUUM 扫描表中尚未完全冻结的每个页面时,它应该将age(重新冻结)设置为比所使用的 vacuum_freeze_min_age 设置稍微多一点的值(更多地设置为VACUUM启动后启动的事务数量)。如果直到达到 autovacuum_freeze_max_age 为止,没有在表上发出重新冻结前进的VACUUM,那么表很快就会被强制进行 autovacuum。

如果由于某种原因 autovacuum 无法从表中清除旧的XID,那么当数据库最旧的XID从wraparound 点达到1000万个事务时,系统将开始发出这样的警告消息:

WARNING: database "mydb" must be vacuumed within 177009986 transactions HINT: To avoid a database shutdown, execute a database-wide VACUUM in "mydb".

如提示所示,手动VACUUM应该可以解决这个问题;但是请注意,VACUUM必须由超级用户执行,否则它将无法处理系统目录,从而无法推进数据库的 datfrozenxid 。)如果这些警告被忽略,系统将关闭并拒绝启动任何新的事务,只要剩下不到100万个事务,直到结束

ERROR: database is not accepting commands to avoid wraparound data loss in database "mydb" HINT: Stop the postmaster and vacuum that database in single-user mode.

存在100万事务安全范围,以便通过手动执行所需的VACUUM命令,管理员可以恢复而不会丢失数据。但是,因为一旦进入安全关闭模式,系统就不会执行命令,所以唯一的方法是停止服务器,在单用户模式下启动服务器来执行VACUUM。在单用户模式下不执行关机模式。

Multixact IDs 用于支持多个事务的行锁定。由于元组头中只有有限的空间来存储锁信息,所以只要存在多个事务同时锁定一行,该信息就被编码为“多个事务ID”,或简称为“多精确ID”。关于哪些事务ID包含在任何特定的多精度ID中的信息分别存储在 pg_multixact 子目录中,并且只有多精度ID出现在元组头部的xmax字段中。与事务ID一样,多精度ID被实现为32位计数器和相应的存储,所有这些都需要仔细的老化管理、存储清理和包装处理。有一个单独的存储区域,它保存每个多精度中的成员列表,它也使用32位计数器,并且还必须对其进行管理。

每当VACUUM扫描表的任何部分时,它将用另一个值替换它所遇到的比 vacuum_multixact_freeze_min_age 旧的任何多精度ID,该值可以是零值、单个事务ID或更新的多 multixact ID。对于每个表,pg_class.relminmxid 存储仍然存在于该表的任何元组中的最早的可能的多 multixact ID 。如果这个值比 vacuum_multixact_freeze_table_age 更大,则强制真空。正如前面部分所讨论的,积极的真空意味着只跳过那些已知被完全冻结的页面。mxid_age() 可用于 pg_class.relminmxid ,以查找其age。

积极的 VACUUM 扫描,不管它们是什么原因,都能提高该表的价值。最后,当所有数据库中的所有表都被扫描,并且它们的最老的多精度值被提升时,可以删除旧多精度的磁盘存储。

作为安全装置,对于 multixact-age 大于 autovacuum_multixact_freeze_max_age 的任何表,都会进行积极的 vacuum 扫描。如果使用的成员存储空间的数量超过可寻址存储空间的50%,则对所有表也逐步进行积极的 vacuum 扫描,从具有最老的 multixact-age 的表开始。这两种攻击扫描都会发生,即使 autovacuum 名义上被禁用。

6. autovacuum daemon

有一个可选的、但强烈推荐的特性,autovacuum,它的目的是自动执行 VACUUM 和 ANALYZE 命令。启用时,autovacuum检查具有大量插入、更新、删除的表。这些检查使用统计收集设施,因此,除非 track_counts 被设置为true,否则不能使用autovacuum。在默认配置中,启用 autovacuum ,并适当设置相关配置参数。

autovacuum daemon 实际上是由多个进程组成的。有一个称为 autovacuum launcher 的持久守护进程,负责启动所有数据库的 autovacuum 工作进程。launcher 将跨时间分配工作,尝试在每个数据库中每隔autovacuum_naptime 秒启动一个worker。因此,如果安装有N个数据库,则每隔 autovacuum_naptime/N 秒将启动一个新worker。允许同时运行最大量的 autovacuum_max_workers 进程。每个工作进程将检查其数据库中的每个表,并根据需要执行 VACUUM and/or ANALYZE。可以设置log_autovacuum_min_duration 来监视 autovacuum workers 的活动。

如果几张大表都能够在短时间内 vacuum ,那么所有的 autovacuum workers 就可能会长时间忙于vacuum。这将导致其他表和数据库在worker 可用之前不会被清空。在一个数据库中可能有多少worker 没有限制,但是worker 确实试图避免重复其他worker 已经完成的工作。注意,worker 的数量不指向max_connections 或 superuser_reserved_connections限制。

relfrozenxid 重新冻结值大于autovacuum_freeze_max_age事务旧的表总是被清空的。(这也适用于那些通过存储参数修改了 relfrozenxid 冻结最大age的表)。否则,如果自上次 vacuum 以来废弃的元组数量超过 "vacuum threshold ",则表被vacuum。vacuum threshold 定义为:

vacuum threshold = vacuum base threshold + vacuum scale factor * number of tuples

其中,vacuum base threshold 是autovacuum_vacuum_threshold,vacuum scale factor 是autovacuum_vacuum_scale_factor,number of tuples 是 pg_class.reltuples。过时的元组的数量是从统计信息收集器中获得的;它是由每个UPDATE和DELETE操作更新的半精确计数。(它只是半精确的,因为某些信息可能在重负载下丢失。)如果表的relfrozenxid值大于vacuum_freeze_table_age旧事务,则执行积极的 vacuum 来冻结旧元组,并推进 relfrozenxid ;否则,只扫描自上次 vacuum 以来已修改的页。

analyze使用类似的条件:

analyze threshold = analyze base threshold + analyze scale factor * number of tuples

与自上次分析以来插入、更新或删除的元组总数进行比较。

临时表不能通过 autovacuum 访问。因此,应通过会话SQL命令执行适当的 vacuum and analyze 操作。

默认阈值和比例因子取自postgresql.conf,但是可以在每个表的基础上覆盖它们(和许多其他 autovacuum 控制参数)。如果通过表的存储参数更改了设置,则在处理该表时使用该值;否则使用全局设置。

当多个 worker 在运行时,自动真空成本延迟参数在所有正在运行的 worker 中“平衡”,从而不管实际运行的 worker 的数量如何,对系统的总I/O影响是相同的。然而,在平衡算法中,没有考虑任何处理表中每表autovacuum_vacuum_cost_delay 或autovacuum_vacuum_cost_limit 存储参数的worker。

有关autovacuum的参数

autovacuum

log_autovacuum_min_duration

autovacuum_max_workers

autovacuum_naptime

autovacuum_vacuum_threshold

autovacuum_analyze_threshold

autovacuum_vacuum_scale_factor

autovacuum_analyze_scale_factor

autovacuum_freeze_max_age

autovacuum_multixact_freeze_max_age

autovacuum_vacuum_cost_delay

autovacuum_vacuum_cost_limit

猜你喜欢

转载自blog.csdn.net/chuckchen1222/article/details/82906955