表膨胀的处理

如果只是测试数据库,或者业务允许每天有很长的停机维护时间,那么简单地在数据库中执行VACUUM FULL就可以了。但VACUUM FULL需要表上的排它读写锁,但对于需要不间断运行的数据库,我们就需要用到pg_repack来处理表的膨胀。

要求

  • PostgreSQL 版本

PostgreSQL 9.4、9.5、9.6、10、11、12、13、14、15

  • 磁盘

执行全表重新打包需要大约两倍于目标表及其索引的可用磁盘空间。例如,如果要重组的表和索引的总大小为 1GB,则需要额外的 2GB 磁盘空间。

pg_repack安装

虽然pg_repack 可以使用 yum 进行安装,但我不推荐这种安装方式。因为不知道版本与 postgresql 版本之间的兼容性。我们老老实实去 github 下载就行了。https://github.com/reorg/pg_repack

export PGHOME=/opt/pgsql13.2
export PATH=$HOME/bin:$PGHOME/bin:$PATH

cd pg_repack-1.4.8/
make
make install

安装后,在你要处理的数据库中加载pg_repack 扩展。pg_repack 被打包为一个扩展,所以你可以执行:

CREATE EXTENSION pg_repack

使用pg_repack 的注意事项

  • 目标表必须有一个 PRIMARY KEY,或者至少有一个 NOT NULL 列上的 UNIQUE 索引。

  • 重整开始之前,最好取消掉所有正在进行的Vacuum任务。

  • 对索引做重整之前,最好能手动清理掉可能正在使用该索引的查询。

  • 如果出现异常的情况(警如中途强制退出),有可能会留下未清理的垃圾,需要手工清理。可能包括:

  1. 临时表与临时索引建立在与原表/索引同一个schema内

  2. 临时表的名称为:${schema_name}.table_${table_oid}

  3. 临时索引的名称为:${schema_name}.index_${table_oid}}

  4. 原始表上可能会残留相关的触发器,需要手动清理。

  • 当完成重整,进行重命名替换时,会产生巨量的WAL,有可能会导致复制延迟,而且无法取消。

  • 重整特别大的表时,需要预留至少与该表及其索引相同大小的磁盘空间,需要特别小心,手动检查。

  • 如果遇到写入速度非常快的,最后阶段replylog的时候,只能等。

  • 数据库的插件与服务端的版本要保持一致。

  • 默认是使用套接字,如果没有配置套接字就要使用 TCP(IP 和 port 写上) 进行连接。

pg_repack的原理

pg_repack的原理相当简单,它会为待重建的表创建一份副本。首先取一份全量快照,将所有活元组写入新表,并通过触发器将所有针对原表的变更同步至新表,最后通过重命名,使用新的紧实副本替换老表。而对于索引,则是通过PostgreSQL的CREATE(DROP) INDEX CONCURRENTLY完成的。

重整表

  1. 创建一张与原表模式相同,但不带索引的空表。

  2. 创建一张与原始表对应的日志表,用于记录pg_repack工作期间该表上发生的变更。

  3. 为原始表添加一个行触发器,在相应日志表中记录所有INSERT,DELETE,UPDATE操作。

  4. 将老表中的数据复制到新的空表中。

  5. 在新表上创建同样的索引

  6. 将日志表中的增量变更应用到新表上

  7. 通过重命名的方式切换新旧表

  8. 将旧的,已经被重命名掉的表DROP掉。

重整索引

  1. 使用CREATE INDEX CONCURRENTLY在原表上创建新索引,保持与旧索引相同的定义。

  2. Analyze新索引,并将旧索引设置为无效,在数据目录中将新旧索引交换。

  3. 删除旧索引。

使用参数详解

pg_repack [选项] ... [数据库名称]
可以在OPTIONS中指定以下选项。

选项:

-a, --all 重新打包所有数据库

-t, --table=TABLE 仅重新打包特定表

-I, --parent-table=TABLE 重新打包特定的父表及其继承者

-c, --schema=SCHEMA 仅在特定模式中重新打包表

-s, --tablespace=TBLSPC 将重新打包的表移动到新的表空间

-S, --moveidx 将重新打包的索引也移动到TBLSPC

-o, --order-by=COLUMNS 按列而不是簇键排序

-n, --no-order 做 vacuum full 而不是 cluster

-N, --dry-run 打印那些表支持

-j, --jobs=NUM 为每个表使用这么多并行作业

-i, --index=INDEX 只移动指定的索引

-x, --only-indexes 只移动指定表的索引

-T, --wait-timeout=SECS 超时以取消冲突的其他后端

-D, --no-kill-backend 超时时不要杀死其他后端

-Z, --no-analyze 最后不分析

-k, --no-superuser-check 跳过客户端中的超级用户检查

-C, --exclude-extension 不要重新打包属于特定扩展名的表

连接选项:

-d, --dbname=DBNAME 要连接的数据库

-h, --host=HOSTNAME 数据库服务器主机或套接字目录

-p, --port=PORT 数据库服务器端口

-U, --username=USERNAME 连接的用户名

-w, --no-password 从不提示输入密码

-W, --password 强制密码提示

通用选项:

-e, --echo 回显查询

-E, --elevel=LEVEL 设置输出消息级别

–help 显示此帮助,然后退出

–version 输出版本信息,然后退出

使用案例

# 完全清理整个数据库,开5个并发任务,超时等待10秒
pg_repack -d <database> -j 5 -T 10

# 清理mydb中一张特定的表mytable,超时等待10秒
pg_repack mydb -t public.mytable -T 10

# 清理某个特定的索引 myschema.myindex,注意必须使用带模式的全名
pg_repack mydb -i myschema.myindex

# 俺一般喜欢补全
 ./pg_repack --dbname tpch --dry-run --username postgres --host 192.168.31.170 --port 5432 --password  -j 2

参考链接https://pigsty.cc/blog/admin/bloat/#%E8%86%A8%E8%83%80%E7%8E%87%E7%9A%84%E4%BC%B0%E7%AE%97