SQL PostgreSQL 的 pg_repack vs pg_squeeze 的表重整工具性能与功能对比

1次阅读

pg_repack 更适合高写入、大索引的 oltp 场景,支持主表、索引及 toast 并行重建,不依赖逻辑复制;pg_squeeze 基于逻辑复制,要求 wal_level=logical 且必须有主键或非空唯一约束,不重建索引、对复制延迟敏感、安装配置更复杂。

SQL PostgreSQL 的 pg_repack vs pg_squeeze 的表重整工具性能与功能对比

pg_repack 和 pg_squeeze 都能在线重整形表,但 pg_squeeze 不支持索引重建

pg_repack 是成熟、稳定、功能完整的在线表重整工具,支持主表 + 所有索引 + TOAST 表的并行重建;pg_squeeze 本质是基于逻辑复制(pg_logical)的“替换式”方案,它会新建表和索引再原子切换,但当前版本(v1.0–v1.2)不重建索引——旧索引直接 DROP 后用新表上的 CREATE INDEX,无法复用原索引的排序或部分构建状态。

这意味着:如果表带大索引(比如几百 GB 的 ginBRIN),pg_squeeze 会多出一次完整索引构建耗时,而 pg_repack 可以保留原索引结构并增量整理。实操中,对高写入+大索引的 OLTP 表,pg_repack 更可控。

  • pg_repack 默认启用 --no-order 时按物理顺序重建,适合减少后续 I/O;加 --order-by 可指定排序字段,但会显著拖慢执行时间
  • pg_squeeze 要求源表必须有主键或非空唯一约束(用于逻辑复制标识行),否则启动就报错:Error: table "xxx" has no primary key or non-Nullable unique constraint
  • pg_squeeze 的切换阶段会短暂持有 access EXCLUSIVE 锁(毫秒级),但 pg_repack 在最后切换时也需同等级别锁——两者锁行为差异不大,别信“pg_squeeze 完全无锁”的说法

pg_squeeze 对 WAL 和复制延迟更敏感,容易在备库上卡住

因为 pg_squeeze 依赖 pg_logical 订阅消费变更,所有 DML 都要走逻辑解码 → 复制槽 → 应用到新表。一旦备库落后、复制槽被阻塞、或 max_replication_slots 不足,pg_squeeze 就会 hang 在 waiting for logical replication to catch up 状态,且不会自动超时退出。

pg_repack 不依赖逻辑复制,只靠触发器捕获变更,WAL 压力小,也不受复制槽状态影响。在主从架构复杂、或使用 wal_level = replica(而非 logical)的环境里,pg_squeeze 直接不可用。

  • 启用 pg_squeeze 前必须确认:wal_level = logicalmax_replication_slots >= 2(一个给 squeeze 自用,一个防其他业务占用)、shared_preload_libraries 包含 pg_squeeze
  • pg_repack 的触发器对写入吞吐有可测影响(约 5–15%),但属于恒定开销;pg_squeeze 的逻辑解码压力随变更量陡增,高峰期可能打满 CPU 或拖慢主库解析速度
  • pg_squeeze 的日志里频繁出现 logical decoding output plugin "pg_squeeze_output" produced invalid output,通常是插件版本与 postgresql 小版本不匹配(如 pg_squeeze v1.1 在 PG 15.5 上需手动 patch)

pg_repack 的安装和权限模型更简单,pg_squeeze 需要更多 dba 协调

pg_repack 只需要超级用户或具备 EXECUTE 权限的普通用户(配合 pg_repack 扩展已安装),建扩展后就能跑 pg_repack 命令;pg_squeeze 要求更高:除扩展安装外,还必须由超级用户创建专用复制槽、设置 pg_squeeze 用户角色、并在 pg_hba.conf 中允许该用户连接本地逻辑复制端口(默认 5432)。

更麻烦的是,pg_squeeze 进程本身是外部 Python 脚本(pg_squeeze.py),不是数据库内函数,它要连两次数据库(一次查元数据,一次建订阅),网络、认证、ssl 配置都得单独处理。稍有疏漏就报:could not connect to server: Connection refusedFATAL: database "postgres" does not exist(其实是连到了错误的数据库名)。

  • pg_repack 的二进制包(如 debianpostgresql-15-repack)自带 pg_repack CLI,路径通常在 /usr/lib/postgresql/15/bin/pg_repack
  • pg_squeeze 必须用 pip install pg-squeeze 安装,且 Python 版本需与服务器上 pg_config --bindir 找到的 pg_config 兼容,否则 pg_squeeze.py --version 会直接段错误
  • 运行 pg_squeeze 时若忘记加 --dry-run,它会在后台默默启一 pg_squeeze_worker 进程,杀不干净会导致复制槽残留,最终撑爆 pg_wal/

不要只看文档说的“零停机”,得看你的表有没有长事务和未提交的 prepared statement

两个工具都会在启动前检查活跃事务,但 pg_repack 检查的是 pg_stat_activitystate = 'active' 的连接,pg_squeeze 还额外检查 pg_prepared_xacts。如果你有遗留的 prepared transaction(比如应用崩溃没清理),pg_squeeze 会直接拒绝启动,并提示:prepared transaction found, abort it first with ROLLBACK PREPARED 'xxx';pg_repack 则可能跳过检查、后续在切换阶段失败。

真正棘手的是那些持锁超过 30 秒的慢查询——它们不会被工具自动 kill,反而会让 pg_repack 的触发器堆积变更、让 pg_squeeze 的逻辑复制 lag 持续增长。这种情况下,与其硬扛,不如先 select pid, query, now() - backend_start FROM pg_stat_activity WHERE state = 'active' AND now() - backend_start > interval '30 seconds'; 手动干预。

  • pg_repack 的 --wait-timeout 参数控制等待锁释放的上限(默认 60 秒),超时后报错退出,不自动重试
  • pg_squeeze 没有等锁机制,它依赖逻辑复制“追平”,所以实际停机窗口 = 最长事务运行时间 + 复制追赶时间,这个值可能远超预期
  • 任何在线重整,只要表上有 BEFORE ROW 触发器(尤其是含 INSERT/UPDATE/delete 的),都建议先禁用——pg_repack 和 pg_squeeze 都无法保证与这类触发器的行为兼容
text=ZqhQzanResources