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 15和PostgreSQL 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 extension
或ERROR:pg_repack failed with error: program 'pg_repack x.x.x' does not match database library 'pg_repack y.y.y'
。上述下载地址只提供最新版的pg_repack客户端,如果pg_repack插件版本落后于客户端工具的版本,则需要升级插件版本。
如果插件版本升级后仍然低于客户端版本,则需要先在控制台进行PolarDB PostgreSQL版内核小版本升级,然后再升级插件版本。
Repack普通表和分区表分区
pg_repack支持对普通表或者分区表的某个分区进行repack,其作用类似于CLUSTER
或VACUUM 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 timeout
、FATAL: 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的实现原理如下:
创建日志表,记录repack期间对原表的变更。
在原表上创建触发器,将原表的
INSERT
、UPDATE
和DELETE
操作记录到日志表中。创建原表结构相同的新表并将原表数据导入其中。
在新表中创建与原表相同的索引。
将日志表里的变更(即repack期间表上产生的增量数据)应用到新表。
在系统catalog交换新旧表。
删除旧表。
说明pg_repack会在第1、2、6、7步短暂持有原表的排它锁并阻塞读写。其余步骤pg_repack只需要持有原表的ACCESS SHARE锁,不阻塞对原表的
INSERT
、UPDATE
和DELETE
操作,但会阻塞DDL
操作。
对索引进行repack的实现原理如下:
以
CREATE INDEX CONCURRENTLY
方式创建新索引。在系统catalog交换新旧索引(需持有排它锁,短暂阻塞读写)。
以
DROP INDEX CONCURRENTLY
的方式删除旧索引。
说明pg_repack效果等同于
REINDEX CONCURRENTLY
,但是比REINDEX CONCURRENTLY
更为复杂,如果使用REINDEX CONCURRENTLY
,只需要一步就能完成。pg_repack支持该能力的原因是老版本的PostgreSQL不支持REINDEX CONCURRENTLY
,从而只能借助pg_repack来实现。
相关参考
pg_repack的更多信息可参考pg_repack官方帮助文档。