VPS PostgreSQL 資料庫調校 REPACK pg_repack

PostgreSQL 19 REPACK CONCURRENTLY 把 pg_repack 收回核心:VACUUM FULL 卡整表這條舊路可以收起來

Table bloat 二十年來只有兩個差的答案——VACUUM FULL 拿 ACCESS EXCLUSIVE lock 卡死整張表,或者裝 pg_repack extension 跟 managed service 吵權限。PostgreSQL 19 把 REPACK 收進核心、CONCURRENTLY 版本靠 logical decoding 一邊重寫一邊接讀寫,這條維運痛點終於有乾淨的解法。

Table bloat 這個問題在 PostgreSQL 存在二十年。MVCC 設計保證了這一點——UPDATE 不是就地改寫、DELETE 不是立刻釋放、每一次寫入都會留下舊版本等待 autovacuum 回收。autovacuum 跟得上流量的時候整張表維持在健康體積,但一旦 OLTP 寫入壓力大、長 transaction 卡住 cleanup horizon、或 bulk DELETE 把幾千萬行一次打成 dead tuple,表就不會變回原本的大小。PostgreSQL 19 把 REPACK 收進核心、搭配 CONCURRENTLY 選項,這條帳一次清掉。

VACUUM FULL 鎖整表、pg_repack 要裝 extension:bloat 二十年來只有兩條差的路

VACUUM FULL 走的是 CLUSTER 的實作路徑:拿 ACCESS EXCLUSIVE lock、重寫整張表到新 file、swap、drop 舊 file。這段時間連 SELECT 都進不來。對 100 GB 以上的表來說,是幾個小時的完整 downtime——production 環境幾乎永遠排不出這段視窗。

另一條路是 pg_repack extension,從 2012 年由 NTT OSS Center 維護到今天。原理是建一張 shadow 表、靠 trigger 把原表的 DML 複製到 log 表、backfill 完成之後重放 log、最後拿一個短暫 ACCESS EXCLUSIVE lock 做 catalog swap。整個過程大部分時間只需要 ACCESS SHARE lock,正常的 INSERT/UPDATE/DELETE 不會被阻擋。

但 pg_repack 有一串自己的痛點:要在 shared_preload_libraries 掛上去、安裝需要 superuser 權限、表必須有 primary key 或 unique index on NOT NULL 欄位、trigger 疊上去會多 2 到 3 倍寫入成本、最後 swap 階段撞上長 transaction 會整個卡死。託管 PostgreSQL 服務對這個 extension 的支援也不平均:RDS 原生支援、Cloud SQL 要特定版本、部分 managed 方案根本不給裝。

pg_squeeze 是這條線上另一個分支。Antonin Houska 2017 年用 logical decoding 重新實作了一遍 pg_repack 的功能,不用 trigger、改走 WAL stream,寫入放大更小、不會污染 pg_stat_statements。問題是 pg_squeeze 一樣是 extension,而且社群知名度低到大部分 DBA 從沒聽過這個選項。

logical decoding 這條路線從 extension 變成核心命令

PostgreSQL 19 把 REPACK 收進核心,commit 落地在 2026 年 Beta 1、Beta 4 在 9 月 24 日釋出、GA 排在 10 月上旬。語法有兩種型態:

1
2
3
REPACK (VERBOSE, ANALYZE) orders;
REPACK (CONCURRENTLY) orders;
REPACK (VERBOSE, ANALYZE) orders USING INDEX idx_orders_created_at;

不加 CONCURRENTLY 的版本是強化版的 VACUUM FULL:拿 ACCESS EXCLUSIVE lock、重寫整張表、支援 USING INDEX 讓資料依照某個 B-tree index 的順序落盤(這部分是 CLUSTER 的語意),支援順便 ANALYZE 更新統計資訊。這條路線適用於小表或者排得出 maintenance window 的場合。

CONCURRENTLY 版本才是真正的重點。底層直接沿用 pg_squeeze 那條 logical decoding 路線:建立一個 temporary replication slot、snapshot 當下的 heap 到 shadow table、replay WAL 把 shadow 補到跟 primary 一致、最後拿一個短暫 ACCESS EXCLUSIVE lock 做 catalog swap。整個過程中原表持續接受讀寫。pgEdge 公開的測試在 200M 列的表上跑 REPACK CONCURRENTLY,baseline 1.5ms 的 insert latency 在重寫期間拉到平均 5.5ms、尖峰 63ms,結束後 settle 到 9.5ms——寫入會被拖慢,但不會停。

跟 pg_repack 的 trigger-based 作法相比,WAL stream 這條路有兩個結構性優勢:不需要在原表上掛 trigger(寫入成本不會翻倍),也不會進入 PL/pgSQL 的 execution path(統計資訊乾淨)。這也是為什麼 Houska 當年會用 logical decoding 重寫一次——這條路線走得通,只是以前被卡在 extension 的生態位。

pg_stat_progress_repack 跟 owner 權限:收進核心換到的實作品

一個直接影響 production 維運的改變是 pg_stat_progress_repack 這個進度 view。pg_repack 外部工具只能看 process state 跟 lock 狀態,沒有辦法知道 shadow table 複製到哪一行、WAL replay 落後多少。核心版本把這些 counter 以 system catalog 的形式暴露出來,Prometheus 可以 scrape、Grafana 可以畫、SLO 可以設底線。

另一個差別是權限模型。pg_repack 需要 superuser 權限才能安裝 extension,託管服務要嘛不提供、要嘛靠 vendor patch。REPACK 內建之後只需要表 owner 的權限就能跑,RDS、Cloud SQL、Aurora、Alibaba RDS 這些託管環境一旦更新到 PG19,DBA 就不用再跟 vendor 爭論為什麼不給裝 pg_repack。

限制還是要說清楚。REPACK CONCURRENTLY 要求表有 primary key 或 REPLICA IDENTITY FULL、不能用在 unlogged table、不能用在 partitioned table 本身(個別 partition 可以)、不能在 transaction block 裡執行。預設最多 5 個 concurrent repack 操作,由 max_repack_replication_slots 控制——這條預算跟 logical replication slot 共用資源池,大表併行跑之前要確認 wal_sender slot 夠。

MVCC 這邊還有一個容易踩的 edge case。REPACK CONCURRENTLY 做 catalog swap 的瞬間,如果某個 transaction 已經開了 snapshot 但還沒碰過這張表,之後再 SELECT 這張表會看到空表——這個語意跟 TRUNCATE 一樣。長跑的 analytics query 或 pg_dump 進行中特別容易中招,排 REPACK 之前先確認沒有這類交易在跑。

pg_repack 不會明天就死,但生命週期開始倒數

PG19 GA 之後 pg_repack extension 不會立刻過時,但生命週期進入倒數。幾個情境短期內還是會留著舊工具:PG13 到 PG18 的 instance 升不上去——企業資料庫版本遷移週期常常 2 到 3 年、託管服務的新版本 adoption 更慢,這段期間 pg_repack 還是唯一的無停機 bloat 處理方式。另一個是需要用任意 ORDER BY 表達式重排 index 順序的工作負載,pg_repack 的 --order-by 可以吃任意表達式,REPACK USING INDEX 只支援既有的 B-tree index,複雜 clustering 的需求舊工具還有空間。

遷移路徑直接:cron job 從 pg_repack -t orders -d prod 換成 psql -d prod -c "REPACK CONCURRENTLY orders",兩邊的 semantic 幾乎對齊。要注意的是 CONCURRENTLY 的 replication slot 預算——在 wal_level=logical 已經打開、max_replication_slots 夠大的 instance 上基本不用動設定,但如果這台 PostgreSQL 本來就有 logical replication 在跑,slot 預算要重新算一次。

VPS 單節點 PostgreSQL 的 bloat SOP 重畫

single-node PostgreSQL 跑在 VPS 上的場景,REPACK CONCURRENTLY 把「VACUUM FULL 卡 2 小時沒人敢排」這個痛點直接解掉。對臺灣中小企業自架 PostgreSQL 的常見配置,升級到 PG19 之後 bloat 處理的作業流程大致可以重畫成:

  • 日常:autovacuum 設定正確(autovacuum_vacuum_scale_factor 從預設 0.2 降到 0.05 到 0.1、autovacuum_naptime 從 60 秒拉短到 15 到 30 秒)就不會累積到需要 REPACK。
  • 定期:對 UPDATE 密集的表每月排一次 REPACK CONCURRENTLY,時間放在流量低谷。
  • 緊急:bulk DELETE 之後確認 pg_stat_user_tables.n_dead_tup 比例,直接 REPACK CONCURRENTLY,不用停服也不用排 maintenance window。

REPACK CONCURRENTLY 期間 shadow table 要寫一次完整副本、WAL replay 要讀 log、index rebuild 是 random I/O,這幾項對 I/O 吞吐跟記憶體 cache 都敏感——跑在 HDD 或老舊 SATA SSD 的機器上,重寫期間 insert latency 會被拉得比測試數據更難看。NCSE Network 的 VPS 全線配置 Intel Gold CPU 加 NVMe SSD、放在臺灣是方電訊機房,對自架 PostgreSQL 做 bloat 處理的團隊來說,REPACK 進行中不會因為 I/O 爭用拖垮正常業務;準備把 PG19 的這條新路線納入維運標準流程、或在評估資料庫自架方案的團隊,可以參考 NCSE Network 的 VPS 服務。

需要穩定的雲端主機?

NCSE Network 提供企業級 VPS,7 天免費試用,臺灣是方電訊機房,99% SLA 保證。

查看 VPS 方案 →