Bài về VACUUM FULL cho thấy nó thu hồi dung lượng bloat nhưng khóa toàn bảng — chặn cả đọc lẫn ghi suốt thời gian chạy, không dùng được trên hệ thống 24/7. Nhiều bài đã nhắc pg_repack là giải pháp thay thế. Bài này đo thật nó: pg_repack làm đúng công việc của VACUUM FULL (thu nhỏ bảng về kích thước thật) nhưng online — trong khi nó chạy, ứng dụng vẫn đọc và ghi bình thường. Đây là công cụ chuẩn để dọn bloat nặng trên production không được downtime.

Cài đặt và cách dùng

pg_repack là một extension cộng với một chương trình dòng lệnh (khác các extension chỉ-SQL) — bạn cài cả hai:

# cài gói (tên tùy phiên bản PostgreSQL)
apt-get install postgresql-16-repack
CREATE EXTENSION pg_repack;
# chạy như chương trình, KHÔNG phải lệnh SQL
pg_repack -d ten_db -t ten_bang

Đo thật trên container này: pg_repack 1.5.3, repack một bảng bloat rp từ 381 MB xuống 95 MB (500.000 dòng sống giữ nguyên), mất chưa tới nửa giây.

Ảnh chụp đoạn mã SQL nền tối minh hoạ pg_repack dọn bloat online gần như không khóa, vấn đề VACUUM FULL khóa toàn bảng bài trước VACUUM FULL thu nhỏ file nhưng chặn cả đọc lẫn ghi suốt thời gian chạy production 24/7 không chịu được downtime cần pg_repack, cài đặt extension cộng binary riêng cài gói apt-get install postgresql-16-repack CREATE EXTENSION pg_repack chạy như chương trình dòng lệnh không phải lệnh SQL pg_repack -d ten_db -t ten_bang, cơ chế bản sao cộng trigger đồng bộ cộng hoán đổi khóa ngắn 1 tạo bảng bản sao rỗng cộng trigger ghi lại mọi thay đổi 2 COPY dữ liệu sống sang bản sao phần lâu không khóa đọc ghi 3 replay các thay đổi đã ghi từ trigger 4 dựng lại index trên bản sao 5 khóa ACCESS EXCLUSIVE rất ngắn để hoán đổi rename hai bảng chỉ bước 5 khóa và chỉ vài mili giây, khác biệt then chốt so với VACUUM FULL VACUUM FULL khóa ACCESS EXCLUSIVE suốt thời gian chạy SELECT bị hủy pg_repack đọc ghi chạy bình thường trong lúc repack SELECT thành công đánh đổi pg_repack cần thêm khoảng 2x đĩa bản sao như VACUUM FULL

Hình 1: pg_repack là extension cộng chương trình dòng lệnh. Cơ chế năm bước: bản sao + trigger đồng bộ + hoán đổi khóa ngắn — khác VACUUM FULL vốn khóa suốt thời gian chạy.

Đo thật: đọc không bị chặn khi repack chạy

Đây là điểm khác biệt then chốt. Bài trước đã đo VACUUM FULL chặn một SELECT count(*) bình thường (nó bị canceling statement due to lock timeout). Giờ đo cùng kịch bản với pg_repack: repack một bảng bloat 1894 MB, trong lúc đó chạy một SELECT đồng thời:

Ảnh chụp bảng kết quả đo thật nền tối pg_repack 1.5.3 dọn bloat online PostgreSQL 16 bảng bloat chạy pg_repack cộng SELECT đồng thời shared_buffers 128MB, thu nhỏ bloat như VACUUM FULL bảng rp 500k dòng sống trước 381 MB sau pg_repack 95 MB bảng rp2 2,67 triệu dòng sống trước 1894 MB sau 631 MB, khác VACUUM FULL đọc không bị chặn khi repack chạy công cụ VACUUM FULL bài trước SELECT count đồng thời lock_timeout ngắn ERROR canceling statement due to lock timeout pg_repack trả về 2666666 dòng thành công không bị chặn trong lúc pg_repack copy dữ liệu phần lâu nhất đọc ghi chạy bình thường chỉ bước hoán đổi cuối cùng khóa ACCESS EXCLUSIVE vài mili giây, cơ chế 5 bước 1 bảng bản sao rỗng cộng trigger ghi thay đổi 2 COPY dữ liệu sống sang bản sao lâu không khóa 3 replay thay đổi từ trigger 4 dựng lại index 5 khóa ngắn hoán đổi rename hai bảng, cốt lõi pg_repack thu nhỏ bảng bloat như VACUUM FULL 1894 xuống 631 MB nhưng online đọc ghi không bị chặn suốt quá trình SELECT trả 2,67 triệu dòng trong lúc repack chỉ khóa vài mili giây ở bước hoán đổi cuối cần cài binary cộng extension và thêm khoảng 2x đĩa lựa chọn cho production 24/7

Hình 2: pg_repack thu nhỏ rp 381→95 MB và rp2 1894→631 MB. Trong lúc repack rp2, một SELECT count(*) đồng thời trả về 2.666.666 dòng — thành công, khác hẳn VACUUM FULL vốn khiến SELECT bị canceling statement due to lock timeout.

  • VACUUM FULL (bài trước): SELECT đồng thời bị hủy — ERROR: canceling statement due to lock timeout.
  • pg_repack: SELECT đồng thời trả về 2.666.666 dòng — thành công, không bị chặn.

Trong lúc pg_repack copy dữ liệu (phần lâu nhất của quá trình), đọc và ghi chạy bình thường. Chỉ bước hoán đổi cuối cùng cần khóa ACCESS EXCLUSIVE, và chỉ vài mili giây.

Cơ chế năm bước

pg_repack đạt được tính online bằng cách không sửa bảng gốc mà dựng một bản sao gọn rồi hoán đổi:

  1. Tạo một bảng bản sao rỗng và một trigger trên bảng gốc để ghi lại mọi thay đổi (INSERT/UPDATE/DELETE) xảy ra trong lúc repack.
  2. COPY dữ liệu sống từ bảng gốc sang bản sao — đây là phần lâu nhất, nhưng không khóa đọc/ghi.
  3. Replay các thay đổi đã được trigger ghi lại (để bản sao bắt kịp bảng gốc).
  4. Dựng lại index trên bản sao.
  5. Khóa ACCESS EXCLUSIVE rất ngắn để hoán đổi (rename) hai bảng — chỉ bước này khóa, và chỉ vài mili giây.

Kết quả: bảng gốc được thay bằng bản sao gọn, không còn bloat, mà ứng dụng gần như không cảm nhận được gián đoạn.

Đánh đổi cần cân nhắc

pg_repack cần thêm ~2x đĩa như VACUUM FULL. Nó dựng một bản sao đầy đủ của bảng trước khi hoán đổi, nên ở đỉnh điểm cần chỗ cho cả bảng gốc và bản sao. Đây là ràng buộc chung với mọi cách "viết lại bảng" — kiểm dung lượng trống trước khi chạy trên bảng lớn.

Cần khóa ngắn ở đầu và cuối — không hoàn toàn "không khóa". pg_repack lấy một khóa ngắn lúc tạo trigger (đầu) và lúc hoán đổi (cuối). Nếu có giao dịch dài đang chạy chiếm khóa bảng, pg_repack có thể phải chờ hoặc bị chặn ở hai điểm này. "Gần như không khóa" chứ không phải "không khóa tuyệt đối".

Là extension bên thứ ba, cần cài và cấp quyền. Khác VACUUM FULL (lệnh SQL sẵn có), pg_repack phải cài binary + extension, và thường cần quyền cao. Trên dịch vụ quản lý (RDS, Cloud SQL), kiểm xem nó có được hỗ trợ không — nhiều dịch vụ có sẵn nhưng một số không. Với hệ thống tự vận hành, đây là công cụ đáng cài sẵn cho bảo trì bloat.

Ba ý mang về

  1. pg_repack thu nhỏ bảng bloat như VACUUM FULL nhưng online: đo thật, repack bảng 1894 MB xuống 631 MB trong khi một SELECT đồng thời vẫn trả về 2,67 triệu dòng — khác hẳn VACUUM FULL vốn khiến SELECT bị hủy vì lock timeout.
  2. Cơ chế là bản sao + trigger đồng bộ + hoán đổi khóa ngắn: pg_repack copy dữ liệu sang bảng bản sao mới (không khóa), replay thay đổi qua trigger, rồi chỉ khóa ACCESS EXCLUSIVE vài mili giây ở bước hoán đổi cuối cùng.
  3. Là lựa chọn cho production 24/7 nhưng có ràng buộc: cần cài binary + extension, thêm ~2x đĩa tạm, và có hai điểm khóa ngắn ở đầu/cuối — "gần như không khóa" chứ không tuyệt đối; kiểm hỗ trợ trên dịch vụ quản lý.

Phần sau ta chuyển sang chủ đề cấu hình bộ nhớ nền tảng: Phần sau mổ xẻ shared_buffers — bộ nhớ đệm chính của PostgreSQL, nên đặt bao nhiêu, và vì sao "càng nhiều càng tốt" không đúng.