Radim Marek:REPACK(CONCURRENTLY)运行期间的代价

Radim Marek:REPACK(CONCURRENTLY)运行期间的代价

💡 原文英文,约3500词,阅读约需13分钟。
📝

内容提要

PostgreSQL 19 新增 REPACK 命令,整合 VACUUM FULL 与 CLUSTER,其 CONCURRENTLY 模式可在线重写表,无需扩展,托管服务可用。实测比 pg_repack、pg_squeeze 更快、WAL 更少、失败后无残留。但运行期间会阻碍 VACUUM 清理死元组,末尾换表需短暂 ACCESS EXCLUSIVE 锁,更新删除超约 1.05 亿行会内存耗尽失败,旧快照可能读到空表。

🔎

延伸解读

在线重写期间 VACUUM 为何被抑制

REPACK (CONCURRENTLY) 依赖逻辑解码获取一致性快照,该快照在整个运行期间必须保持有效。因此 PostgreSQL 无法清理旧快照可能仍需要的死元组,导致 VACUUM 无法回收空间。更关键的是,REPACK 使用的临时复制槽属于整个服务器,会阻止所有数据库中的死元组清理,而 pg_repack 仅影响当前数据库。运行期间应监控繁忙表的 n_dead_tup,避免同时安排大批量作业。

末尾换表锁的代价与风险

REPACK 在最后阶段需要短暂的 ACCESS EXCLUSIVE 锁来重放变更并交换文件。若存在长查询或空闲事务,所有新查询将排队等待,造成服务中断。设置 lock_timeout 可限制等待时间,但一旦超时,整个重写工作(可能已进行数十分钟)将被丢弃。与 pg_repack 不同,REPACK 不会重试,因此需权衡锁超时与重跑成本。

并发变更的内存硬上限

REPACK 后端为运行期间每一行被更新或删除的记录保留约 50 字节内存,且不受 maintenance_work_mem 限制。当变更行数达到约 1.05 亿时,内部结构翻倍将超出 PostgreSQL 单次分配上限,导致失败。若内存不足,可能触发 OOM 杀死后端并导致集群重启。运行前应估算表的更新删除速率,确保总变更行数远低于 1 亿。

旧快照可能读到空表

REPACK (CONCURRENTLY) 在 PostgreSQL 19 中不是 MVCC 安全的。一个在重写前启动的 REPEATABLE READ 只读事务,在换表后读取该表会看到 0 行,因为新副本由 REPACK 的事务写入,对旧快照不可见。pg_repack 通过等待所有事务来缩小窗口,但并非完全安全。若存在长 REPEATABLE READ 导出任务,应提前告知或安排避开重写。

❓

Q&A

PostgreSQL 19 的 REPACK (CONCURRENTLY) 和 pg_repack、pg_squeeze 相比,性能上有什么优势?

REPACK (CONCURRENTLY) 在每次测试中都是最快的在线重打包工具,写入时产生的 WAL 最少,并且失败后不会留下任何残留对象。pg_repack 的 WAL 是它的 1.8 倍,pg_squeeze 在写入下需要更多磁盘。

运行 REPACK (CONCURRENTLY) 时,为什么 VACUUM 无法清理死元组?

因为在线重打包需要保持操作开始时的表快照,PostgreSQL 因此不能移除旧快照可能仍需要的行版本。VACUUM 仍会运行,但无法清理这些行版本。

REPACK (CONCURRENTLY) 在运行期间对并发更新和删除有什么硬性限制?

每个被其他会话更新或删除的行会在 REPACK 后端内存中保留约 50 字节,直到提交。当更新和删除的总行数超过约 1.05 亿时,会因内存分配请求过大而失败,且原表保持不变。

REPACK (CONCURRENTLY) 在最后换表时会对表加什么锁?会有什么影响?

最后换表时需要短暂的 ACCESS EXCLUSIVE 锁。如果此时有长查询或事务持有锁,REPACK 会等待,导致所有新查询排队;若设置了 lock_timeout,超时会导致整个运行被丢弃。

使用 REPACK (CONCURRENTLY) 需要满足哪些前提条件?

表需要有一个副本标识索引(主键或 REPLICA IDENTITY USING INDEX),不支持可延迟主键、REPLICA IDENTITY FULL 和 NOTHING。不能在分区表的父表上运行,只能对单个分区运行。需要空闲的复制槽(受 max_repack_replication_slots 限制,默认 5)和后台工作进程槽(max_worker_processes)。wal_level = replica 即可。

运行 REPACK (CONCURRENTLY) 前应该做哪些准备和检查?

首先估算运行时长,确保更新和删除速率乘以预计时长远低于 1 亿行。检查 pg_stat_activity 中的长事务和长查询,确保磁盘能容纳表的第二份副本、索引和 WAL,决定是否设置 lock_timeout,并告知可能运行长 REPEATABLE READ 事务的用户。

🏷️

标签

➡️

继续阅读