pg_repack(存储空间整理)

PolarDB PostgreSQL版支持通过pg_repack插件对表空间进行重新“包装”,回收碎片空间,有效解决因对表大量更新、删除等操作引起的空间膨胀问题。pg_repack获取排它锁的时间较短,多数时间不阻塞读写,相比CLUSTER或VACUUM FULL操作更加轻量化。

前提条件

支持pg_repack插件的PolarDB PostgreSQL版的版本如下:

  • PolarDB PostgreSQL版 15,且内核小版本需为15.7.2.0及以上。

  • PolarDB PostgreSQL版 14,且内核小版本需为14.10.16.0及以上。

  • PolarDB PostgreSQL版 11,且内核小版本需为1.1.36及以上。

说明

您可通过如下语句查看PolarDB PostgreSQL版的内核小版本号:

  • PostgreSQL 15PostgreSQL 14

    select version();
  • PostgreSQL 11

    show polar_version;

注意事项

  • 不支持使用-a/--all选项repack所有的数据库。

  • 不支持repack整个数据库,必须通过--table/--parent-table/--index选项指定需要repack的表或者索引。

  • 不支持使用-c/--schema选项repack整个schema。

  • 不支持使用-s/--tablespace指定的表空间,因为PolarDB PostgreSQL版不支持您直接创建表空间,而是使用默认的表空间。

  • pg_repack需要临时占用额外的存储空间来保存新表和日志表,因此集群剩余存储空间大小至少应为repack表大小的两倍。

  • pg_repack会占用较大的磁盘I/O,使用前请评估是否会影响业务。以PL1级别的ESSD云盘为例,对一个100 GB的表进行repack时会达到I/O吞吐上限(250 MB/s)。

使用方法

pg_repack作为安装在PolarDB PostgreSQL版侧的插件作为服务端,并提供专用的客户端给用户,两者需要搭配使用。

安装插件

CREATE EXTENSION pg_repack;

查看插件版本。

SELECT extversion FROM pg_extension WHERE extname = 'pg_repack';

如果已经安装了老版本插件,可以通过先卸载原有插件再重新创建的方式升级到最新版。

DROP EXTENSION pg_repack;
CREATE EXTENSION pg_repack;
说明

如果服务端没有安装pg_repack插件,直接运行pg_repack命令客户端会报错:ERROR:pg_repack failed with error: pg_repack x.x.x is not installed in the database

安装客户端

pg_repack客户端工具随PolarDB-Tools工具包发布,下载安装PolarDB-Tools后即可使用其中的pg_repack客户端。下载地址及安装方法请参见PolarDB-Tools

说明
  • pg_repack客户端的版本必须匹配pg_repack插件的版本,否则执行客户端会报错:ERROR:pg_repack failed with error: extension 'pg_repack x.x.x' required, found 'pg_repack y.y.y'; please drop and re-create the extensionERROR:pg_repack failed with error: program 'pg_repack x.x.x' does not match database library 'pg_repack y.y.y'

  • 请从上述链接中选择与PolarDB集群版本相匹配的PolarDB-Tools工具包进行下载和使用。

Repack普通表和分区表分区

pg_repack支持对普通表或者分区表的某个分区进行repack,其作用类似于CLUSTERVACUUM FULL操作,清理表中多余的空闲空间,同时重建表上的索引,适用于表空间膨胀的场景

说明
  • repack表必须有主键或唯一索引。

  • 不支持对临时表进行repack操作。

  • 不支持对带有Global Index的分区进行repack操作。

语法说明一

  • 通过--table参数指定表名,默认情况下效果等同于CLUSTER,repack过程中对之前执行过CLUSTER操作的列进行排序:

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --table schema1.table1
  • 如果希望对指定的列进行排序,可以使用--order-by参数来指定列名:

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --table schema1.table1 --order-by <列名>
  • 如果不希望进行排序,即希望pg_repack的效果等同于VACUUM FULL,可以使用--no-order参数:

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --table schema1.table1 --no-order
  • 如果数据库集群的CPU和I/O资源充裕,可以使用--jobs参数加速repack操作,它会启动多个进程并发重建索引,适用于表上有多个索引的场景:

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --table schema1.table1 --jobs <并发数量>

Repack分区表和继承表

pg_repack支持对分区表(包括声明式分区表和继承式分区表)进行操作,它会自动找到父表的所有分区,并对每个分区依次进行repack操作。适用于分区表的所有分区都存在空间膨胀的场景

语法说明

  • 通过--parent-table参数指定分区表的表名

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --parent-table schema1.table1
    说明
    • 除了--parent-table参数以外,分区表的其他参数用法与普通表基本相同。

    • 如果只是单个分区存在空间膨胀,则无需对整个分区表进行repack,使用语法说明一中(--table参数)对单个分区进行repack操作即可。

    • 不支持对带有Global Index的分区表进行repack操作。

Repack索引

pg_repack支持仅对索引进行repack操作,它的作用是重建索引,清理索引中的空闲空间,适用于索引空间膨胀的场景

说明
  • 如果索引空闲空间过多,推荐使用REINDEX CONCURRENTLY进行在线索引重建,无需使用pg_repack。pg_repack支持该能力的原因是老版本的PostgreSQL不支持REINDEX CONCURRENTLY,从而只能借助pg_repack来实现。

  • 由于pg_repack社区的特性,暂不支持对声明式分区表进行repack索引的操作,同样可以使用REINDEX CONCURRENTLY来代替。

语法说明

  • 使用--index参数指定需要repack的索引名:

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --index schema1.table1
  • 使用--only-indexes参数repack表上的所有索引:

    pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --table schema1.table1 --only-indexes

更多用法

使用pg_repack --help可以查看pg_repack的更多用法。

常见问题

Dry Run

正式执行pg_repack之前建议使用--dry-run选项运行一次,该选项不操作表中的数据,仅验证命令是否合法、流程是否可以跑通。待命令验证成功后,再去掉该选项正式运行pg_repack。

pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --echo --table schema1.table1 --dry-run

权限问题

  • 必须使用高权限账号运行pg_repack,不能以普通账号身份运行,否则会报错:must be polar_superuser or superuser to use xx function

  • 如果遇到You must be a superuser to use pg_repack报错,则需要在pg_repack命令中增加--no-superuser-check选项来绕过超级用户检查。

超时问题

如果需要repack的表或索引过大,repack过程耗时过长,则可能遇到FATAL: terminating connection due to idle-in-transaction timeoutFATAL: terminating connection due to idle-session timeout之类的错误。这表示pg_repack客户端与PolarDB PostgreSQL版集群的连接因为超时而断开,本次repack失败。

解决方法是在pg_repack命令之前增加PGOPTIONS选项设置超时相关参数来关闭超时功能,以防止超时导致连接断开。

# PolarDB PostgreSQL 14:
PGOPTIONS="-c idle_session_timeout=0 -c idle_in_transaction_session_timeout=0 -c statement_timeout=0" pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --table schema1.table1

# PolarDB PostgreSQL 11:
PGOPTIONS="-c polar_idle_session_timeout=0 -c idle_in_transaction_session_timeout=0 -c statement_timeout=0" pg_repack -h <host> -p <port> -d <db> -U <user> --no-superuser-check --table schema1.table1

残留对象清理

如果pg_repack在执行过程中异常退出,则repack失败,被repack的表上可能残留了repack过程中创建的对象,需要及时清理,否则可能影响表的使用:

  • 被repack的表上可能残留repack_trigger触发器,需要使用DROP TRIGGER命令删除。

  • 被repack的表上可能残留临时索引index_<oid>,需要使用DROP INDEX CONCURRENTLY命令删除。

  • repack模式下残留临时表repack_<oid>与日志表log_<oid>,需要使用DROP TABLE命令删除。

  • repack模式下残留新的类型pk_<oid>,需要使用DROP TYPE命令删除。

原理介绍

pg_repack插件支持对全表和索引进行repack操作。

  • 对全表进行repack的实现原理如下:

    1. 创建日志表,记录repack期间对原表的变更。

    2. 在原表上创建触发器,将原表的INSERTUPDATEDELETE操作记录到日志表中。

    3. 创建原表结构相同的新表并将原表数据导入其中。

    4. 在新表中创建与原表相同的索引。

    5. 将日志表里的变更(即repack期间表上产生的增量数据)应用到新表。

    6. 在系统catalog交换新旧表。

    7. 删除旧表。

    说明
    • pg_repack会在第1、2、6、7步短暂持有原表的排它锁并阻塞读写。其余步骤pg_repack只需要持有原表的ACCESS SHARE锁,不阻塞对原表的INSERTUPDATEDELETE操作,但会阻塞DDL操作。

  • 对索引进行repack的实现原理如下:

    1. CREATE INDEX CONCURRENTLY方式创建新索引。

    2. 在系统catalog交换新旧索引(需持有排它锁,短暂阻塞读写)。

    3. DROP INDEX CONCURRENTLY的方式删除旧索引。

    说明

    pg_repack效果等同于REINDEX CONCURRENTLY,但是比REINDEX CONCURRENTLY更为复杂,如果使用REINDEX CONCURRENTLY,只需要一步就能完成。pg_repack支持该能力的原因是老版本的PostgreSQL不支持REINDEX CONCURRENTLY,从而只能借助pg_repack来实现。

相关参考

pg_repack的更多信息可参考pg_repack官方帮助文档