为什么 postgreSQL 在执行 vacuum full table 时等待? 4T table 数据

Why is the postgreSQL waiting while executing vacuum full table? 4T table data

我有一个臃肿的table,它的名字叫"role_info"。 每天大约有20K插入操作和大量更新操作,没有删除操作。 table 现在大约是 4063GB。 我们已经使用dump将table迁移到另一个数据库,而新的table大约有62GB,所以旧数据库上的table膨胀非常严重。

PostgreSQL 版本:9.5.4

table 架构如下:

CREATE TABLE "role_info" (
  "roleId" bigint NOT NULL,
  "playerId" bigint NOT NULL,
  "serverId" int NOT NULL,
  "status" int NOT NULL,
  "baseData" bytea NOT NULL,
  "detailData" bytea NOT NULL,
  PRIMARY KEY ("roleId")
);
CREATE INDEX "idx_role_info_serverId_playerId_roleId" ON "role_info" ("serverId", "playerId", "roleId");

字段 'detailData' 的平均大小约为每行 13KB。

下面有SQL个执行结果:

1)

SELECT 
    relname AS name,
    pg_stat_get_live_tuples(c.oid) AS lives,
    pg_stat_get_dead_tuples(c.oid) AS deads
FROM pg_class c
ORDER BY deads DESC;

执行结果:

2)

SELECT *, 
       Pg_size_pretty(total_bytes) AS total, 
       Pg_size_pretty(index_bytes) AS INDEX, 
       Pg_size_pretty(toast_bytes) AS toast, 
       Pg_size_pretty(table_bytes) AS TABLE 
FROM   (SELECT *, 
               total_bytes - index_bytes - Coalesce(toast_bytes, 0) AS 
               table_bytes 
        FROM   (SELECT c.oid, 
                       nspname                               AS table_schema, 
                       relname                               AS TABLE_NAME, 
                       c.reltuples                           AS row_estimate, 
                       Pg_total_relation_size(c.oid)         AS total_bytes, 
                       Pg_indexes_size(c.oid)                AS index_bytes, 
                       Pg_total_relation_size(reltoastrelid) AS toast_bytes 
                FROM   pg_class c 
                       LEFT JOIN pg_namespace n 
                              ON n.oid = c.relnamespace 
                WHERE  relkind = 'r') a 
        WHERE  table_schema = 'public' 
        ORDER  BY total_bytes DESC) a; 

执行结果:

3)

我尝试对 table "role_info" 进行 vacuum full,但它似乎被其他进程阻止,根本没有执行。

select * from pg_stat_activity where query like '%VACUUM%' and query not like '%pg_stat_activity%';

执行结果:

select * from pg_locks;

执行结果:

真空参数有:

我有两个问题:

  1. 如何应对table腹胀? autovacuum 似乎不起作用。
  2. 为什么真空完全堵塞了?

根据您的 autovacuum 设置,它每脏 10 页 (200 cost_limit / 20 cost_dirty) 就会休眠 20 毫秒。甚至更多,因为还会有 cost_hit 和 cost_miss。按照这个速度,自动清理 4063GB table 需要超过 12 天的时间,这主要是需要清理页面。那只是节流时间,不包括实际工作时间,也不包括重复扫描索引。所以实际的 运行 时间可能是几个月。 autovacuum 一次完成 运行 而不会被某些事情打断的机会可能非常低。您的数据库是否经常重启?你经常在这个 table 上建立和删除索引,或者添加和删除分区,或者 运行 ALTER TABLE?

请注意,在 v12 中,autovacuum_vacuum_cost_delay 的默认设置降低了 10 倍。这不仅仅是因为对 v12 中的代码进行了一些更改,而是因为我们意识到默认设置是只是对现代硬件不敏感。因此,如果不走得更远,将此更改反向移植到您现有的数据库中可能是有意义的。在 12 之前,你不能降低到小于 1 毫秒,但你可以将它降低到 1 毫秒,也可以增加 autovacuum_vacuum_cost_delay 或降低 vacuum_cost_page_* 设置。

现在这个分析是基于table已经非常臃肿的。为什么 autovacuum 一开始就没有阻止它变得臃肿,回到 table 小到足以在合理的时间内自动清理?这很难说。我们真的没有证据证明当时发生了什么。也许您的设置比现在更受限制(虽然不太可能,因为看起来您只是接受了默认设置),也许它经常被某些东西打断。 pg_stat_all_tables 中 table 的 "autovacuum_count" 和它的 toast table 是什么?

Why did the vacuum full blocked?

因为这就是它的工作原理,as documented。这就是为什么首先要避免陷入这种情况很重要。 VACUUM FULL 需要在最后交换文件节点,并且需要一个 AccessExclusive 锁来做到这一点。它可以先使用较弱的锁,然后尝试升级到 AccessExclusive,但锁升级有很大的死锁风险,因此它会预先使用所需的最强锁。

您需要维护 window,因为没有其他人在使用 table。如果您认为自己已经处于 window 中,那么您应该查看执行阻塞的进程的查询文本。因为已经持有的锁是ShareUpdateExclusive,持有它的不是正常的query/DML,而是某种DDL或维护操作。

如果您现在不能进行维护 window,那么您至少可以在没有 FULL 的情况下进行手动 VACUUM。这需要一个弱得多的锁。它可能不会显着缩小 table,但至少应该释放 space 供内部重用,这样当您确定何时可以安排维护 window 或你接下来的其他步骤是什么。