六月 20, 2024
摘要:pg_squeeze
是 PostgreSQL 的一个扩展,可自动修复表膨胀问题,而无需对表进行大量锁定。
目录
pg_squeeze 简介
它是一个 PostgreSQL 扩展,用于删除表中未使用的空间,并可选择根据特定索引对元组进行排序(就像在常规读/写运行的同时,执行 CLUSTER 命令一样)。事实上,pg_squeeze 试图用来替换 pg_repack 扩展。
虽然提供的功能非常相似,但pg_squeeze
采用了不同的方法:
- 完全在服务端实现功能。
- 利用 PostgreSQL 数据库服务器最新提供的能力。
与同时使用了服务端和客户端代码的 pg_repack 相比,(1) 不仅简化了配置和使用,还可以使用后台工作进程顺利实现自动化处理。
至于 (2),除了使用后台工作进程,一个重要的区别是,我们使用逻辑解码而不是触发器来捕获并发的修改。
登记表进行常规处理
首先,确保您的表具有主键或唯一约束。这是在pg_squeeze
工作时处理其他事务可能进行的更改所必需的。
要让pg_squeeze
扩展知道该表,需要在squeeze.tables
表中插入一条记录。在添加后,它会定期检查表上的统计信息。只要表满足"收缩"标准,就会向队列中添加一个"任务"。任务会按照创建的顺序依次处理。
最简单的"登记"是这样的:
INSERT INTO squeeze.tables (tabschema, tabname, schedule)
VALUES ('public', 'foo', ('{30}', '{22}', NULL, NULL, '{3, 5}'));
还可以选择指定其他的列,例如:
INSERT INTO squeeze.tables (
tabschema,
tabname,
schedule,
free_space_extra,
vacuum_max_age,
max_retry
)
VALUES (
'public',
'bar',
('{30}', '{22}', NULL, NULL, '{3, 5}'),
30,
'2 hours',
2
);
以下是表的元数据的完整描述。
-
tabschema
和tabname
分别是模式名和表名。 -
schedule
列会指定什么时候应该对表进行检查,并可能进行收缩。schedule 由下面的复合数据类型的值来描述,类似于一个 crontab 条目:CREATE TYPE schedule AS ( minutes minute[], hours hour[], days_of_month dom[], months month[], days_of_week dow[] );
在这里,
minutes
(0-59)和hours
(0-23)指定了一天内的检查时间,而days_of_month
(1-31)、months
(1-12)和days_of_week
(0-7,其中 0 和 7 都代表星期日)则决定了检查的日期。如果
minute
、hour
和month
都与当前时间戳相匹配,则会执行检查,而 NULL 值分别表示任意分钟、小时和月份。至于days_of_month
和days_of_week
,需要至少有一个与当前时间戳匹配,或者两个都为 NULL,才能进行检查。例如,上面的条目告诉我们,应在每周三和周五的 22:30 检查表
public
.bar
。 -
free_space_extra
是触发表处理所需的extra free space
的最小百分比。extra
形容词指的是,从fillfactor
得到的空闲空间,并不会触发表的收缩。例如,如果
fillfactor
等于 60,那么在正常运行期间,每个页面至少应有 40% 的空间是可用的。如果要让 pg_squeeze 处理到表的可用空间能达到 70%,可将free_space_extra
设置为 30(即 70% 的可用空间减去因fillfactor
而产生的 40% 可用空间)。free_space_extra
的默认值为 50。 -
min_size
是表必须占用的最小磁盘空间(以 MB 为单位)。默认值为 8。 -
vacuum_max_age
是自上一次 VACUUM 完成后,认为空闲空间映射表(FSM)已清理的最长时间。一旦过了这个时间间隔,死元组的比例可能会很大,因此需要花费比简单检查 FSM 更多的精力,来评估pg_squeeze
的潜在影响 。默认值为 1 小时。 -
max_retry
是在相应任务第一次处理失败后,额外尝试收缩表的最大次数。重试处理的典型原因是在收缩表时,表定义发生了更改。如果达到重试次数,则认为表的处理已经完成。一旦到达下一个预定时间,就会立即创建下一个任务。max_retry
的默认值为 0(即不重试)。 -
clustering_index
是处理过的表的现有索引。处理完成后,表中的元组将根据该索引的键进行物理排序。 -
rel_tablespace
是表应移动到的现有表空间。NULL 表示表应留在原处。 -
ind_tablespaces
是一个二维数组,其中每一行都指定了索引的表空间映射。第一列和第二列分别代表索引名称和表空间名称。所有未指定映射的索引都将保留在原来的表空间中。关于表空间,有一种特殊情况值得一提:如果为表指定了表空间,但没有为索引指定表空间,则表会被移动到该表空间,但索引会保留在原来的表空间(也就是说,表的表空间不是索引的默认表空间)。
-
skip_analyze
表示在表处理之后不应执行 ANALYZE 命令。默认值为false
,表示默认执行 ANALYZE。
squeeze.table
是用户唯一应该修改的表。如果您想修改其他表,请确保您完全明白自己在做什么。
对任何表进行临时处理
也可以在不登记的情况下手动收缩表(即无需在squeeze.tables
中插入相应的记录),并且无需事先检查实际膨胀的情况。
函数签名:
squeeze.squeeze_table(
tabchema name,
tabname name,
clustering_index name,
rel_tablespace name,
ind_tablespaces name[]
)
执行样例:
SELECT squeeze.squeeze_table('public', 'pgbench_accounts');
启用/禁用表的处理
要处理膨胀的表,请以超级用户运行此语句:
SELECT squeeze.start_worker();
该函数会启动一个后台工作进程(scheduler worker
),定期检查有哪些已登记的表需要进行膨胀检查,并为每个表创建一个任务。只要特定数据库存在任务,就会启动另一个工作进程(squeeze worker
)。
如果当前数据库已在运行scheduler worker
,函数不会报错,但新的工作进程会立即退出。
如果有工作进程正在当前数据库中运行,可以使用下面语句停止它们:
SELECT squeeze.stop_worker();
只有本文档中提到的函数才应视为用户接口。如果您想调用其他函数,请确保您完全明白自己在做什么。
如果希望后台工作进程在整个 PostgreSQL 实例启动时自动启动,请在postgresql.conf
文件中添加如下条目:
squeeze.worker_autostart = 'my_database your_database'
squeeze.worker_role = postgres
下次启动实例时,将为my_database
启动两个或多个工作进程(即一个scheduler worker
,和一个或多个squeeze workers
),并为your_database
启动同样的工作进程。请注意,如果您采用这种方法并满足下面任一条件,任何工作进程都会拒绝启动,或者停止而不做任何工作:
-
pg_squeeze
扩展不存在于数据库中,或 -
squeeze.worker_role
参数指定了不具有超级用户权限的角色。
虽然实际上有两个工作进程,但上述函数/配置变量使用了单数形式的worker
。这是因为在以前的 pg_squeeze 版本中只有一个工作进程,它同时负责任务的调度和执行。在升级过程中,强制所有用户调整配置文件可能并不值得。
控制对其他后端进程的影响
虽然被收缩的表在大部分时间里都可以被其他事务读写,但它在完成处理时需要获取排他锁。如果 pg_squeeze 偶尔看上去阻塞了对表的访问,可以考虑设置 GUC 参数squeeze.max_xlock_time
。例如:
SET squeeze.max_xlock_time TO 100;
设置排他锁的保持时间不应超过 0.1 秒(100 毫秒)。如果最后阶段需要更多时间,pg_squeeze 会释放排他锁,处理中间其他事务提交的更改,并再次尝试最后阶段。如果再次超过锁的持续时间,就会报错。如果出现这种情况,要么增加设置,要么将处理问题表的时间安排在写入活动较少的另一天。
每个数据库运行多个工作进程
如果认为单个收缩工作进程无法应付负载,可考虑将squeeze.workers_per_database
配置变量的值设置为大于 1。这样,pg_squeeze
扩展就能同时处理多个表,每个收缩工作进程处理一个表。不过,请注意这一设置会影响所有使用pg_squeeze
扩展的数据库。实例中所有收缩工作进程(包括"调度工作进程")的总数,不能超过内核配置变量max_worker_processes
。
监控
-
squeeze.log
表中,每个成功收缩的表都会包含一个条目。tabschema
和tabname
这两列标识处理的表。started
和finished
这两列标识处理的开始和完成时间。ins_initial
是在 “初始加载阶段 “插入到新表存储中的元组数,即处理开始前表中存在的元组数。另一方面,ins
、upd
和del
是在表处理过程中由应用程序插入、更新和删除的元组数。(这些 “并发的数据更改” 也必须合入收缩的表,否则就会丢失数据)。 -
squeeze.errors
表中包含了收缩过程中发生的错误。此处报告的一个常见问题是,有人更改了正在进行处理的表的定义(如添加或删除列)。 -
squeeze.get_active_workers()
函数返回一个收缩工作进程的表,这些收缩工作进程正在当前数据库中处理表。pid
列包含了工作进程的系统 PID。其他列的含义与squeeze.log
表中的对应列相同。squeeze.log
表只显示已完成的收缩操作信息,而squeeze.get_active_workers()
函数则可让您检查处理过程中的进度。
取消登记表
如果不再需要对特定表进行定期收缩,只需从squeeze.tables
表中删除相应行即可。
尽管后台工作进程会定期取消登记不存在的表,但取消登记要删除的表也是一种好的做法。
并发性
-
该扩展在处理的某些阶段并不阻止其他事务更改表。如果一个 “破坏性命令”(即
ALTER TABLE
,VACUUM FULL
,CLUSTER
或TRUNCATE
)在收缩完成前提交,squeeze_table()
函数就会中止,对表的所有更改都会回滚。squeeze.tables
表的max_retry
列决定了收缩工作进程重试的次数。此外,更改调度时间也可以避免中断。 -
与 pg_repack 一样,
pg_squeeze
也会改变行的可见性,从而允许出现 MVCC 提醒 第一段中描述的 MVCC 不安全行为。
磁盘空间要求
执行全表收缩所需的可用磁盘空间,大约是目标表及其索引的两倍。例如,如果要收缩的表和索引的总大小为 1GB,则需要额外的 2GB 磁盘空间。