Nhiều bài trong loạt này đã nhắc VACUUM FULL như công cụ thu hồi dung lượng khi bảng bị bloat nặng. Nhưng nó là một trong những lệnh nguy hiểm nhất bạn có thể chạy trên production — không phải vì nó làm hỏng dữ liệu (nó an toàn về mặt đó), mà vì nó khóa toàn bộ bảng theo cách khiến ứng dụng "treo". Bài này đo thật ba rủi ro của VACUUM FULL, và chỉ rõ khi nào thật sự cần nó so với khi nào một lựa chọn an toàn hơn là đúng.

VACUUM FULL làm gì: viết lại toàn bảng

Khác với VACUUM thường (chỉ đánh dấu chỗ trống để tái dùng, không trả về hệ điều hành), VACUUM FULL viết lại toàn bộ bảng thành một bản sao mới gọn gàng và xóa bản cũ — nhờ đó file thu nhỏ thật và dung lượng trả về đĩa:

VACUUM FULL vf;   -- 1065 MB → 355 MB (đo thật, thu nhỏ file thật)

Đây là khả năng của nó — thu hồi dung lượng mà VACUUM thường không làm được. Nhưng cách nó đạt được điều đó chính là nguồn gốc mọi rủi ro.

Ảnh chụp đoạn mã SQL nền tối minh hoạ VACUUM FULL khi nào cần và ba rủi ro, VACUUM FULL viết lại toàn bảng để thu hồi dung lượng VACUUM FULL vf 1065 MB xuống 355 MB thu nhỏ file thật trả về OS khác VACUUM thường VACUUM chỉ đánh dấu chỗ trống tái dùng không trả về hệ điều hành chỉ VACUUM FULL mới thu nhỏ file, rủi ro 1 khóa ACCESS EXCLUSIVE chặn cả đọc lẫn ghi trong lúc VACUUM FULL chạy ngay cả SELECT đơn giản cũng bị chặn SET lock_timeout 400ms SELECT count từ vf ERROR canceling statement due to lock timeout toàn bộ ứng dụng treo trên bảng đó suốt thời gian chạy, rủi ro 2 cần thêm khoảng 2 lần dung lượng đĩa tạm thời VACUUM FULL dựng một bản sao mới gọn rồi xóa bản cũ đỉnh điểm cần chỗ cho cả bản cũ cộng bản mới gần gấp đôi đĩa gần đầy cộng VACUUM FULL bảng lớn bằng hết đĩa giữa chừng, rủi ro 3 cộng lựa chọn thay thế rủi ro 3 thời gian tỉ lệ kích thước bảng bảng lớn bằng downtime dài thay thế an toàn pg_repack thu nhỏ online gần như không khóa production CLUSTER sắp lại cộng thu nhỏ nhưng cũng khóa ACCESS EXCLUSIVE VACUUM thường nếu chỉ cần ngăn phình thêm không thu nhỏ

Hình 1: VACUUM FULL thu nhỏ file thật (khác VACUUM thường chỉ đánh dấu chỗ trống). Nhưng nó khóa ACCESS EXCLUSIVE, cần ~2× đĩa tạm, và downtime tỉ lệ kích thước — có các lựa chọn thay thế.

Rủi ro 1: khóa ACCESS EXCLUSIVE chặn cả đọc

Đây là rủi ro nghiêm trọng nhất. VACUUM FULL giữ khóa ACCESS EXCLUSIVE trên bảng — mức khóa mạnh nhất, chặn mọi thao tác, kể cả SELECT đơn giản. Đo thật: trong khi VACUUM FULL chạy trên một bảng bloat, một truy vấn đọc bình thường bị chặn hoàn toàn.

Ảnh chụp bảng kết quả đo thật nền tối VACUUM FULL bảng bloat PostgreSQL 16 timing pg_locks pg_relation_size shared_buffers 128MB, thu hồi dung lượng thu nhỏ file thật VACUUM thường bảng bloat giữ nguyên chỉ đánh dấu free VACUUM FULL 1065 MB xuống 355 MB 1198 mili giây, rủi ro 1 khóa ACCESS EXCLUSIVE chặn cả đọc pg_locks trong lúc VACUUM FULL AccessExclusiveLock granted t trên bảng vf SELECT count với lock_timeout 400ms ERROR canceling statement due to lock timeout khác VACUUM thường không chặn VACUUM FULL treo mọi truy vấn tới bảng, rủi ro 2 và 3 cần khoảng 2 lần đĩa dựng bản sao mới trước khi xóa cũ đỉnh điểm gần gấp đôi dung lượng downtime tỉ lệ kích thước bảng càng lớn thời gian khóa càng dài bảng 100 GB bằng giờ downtime, khi nào dùng và lựa chọn thay thế bloat nặng có cửa sổ bảo trì VACUUM FULL đơn giản không cần cài gì bảng 24 trên 7 không được downtime pg_repack thu nhỏ online cần cài extension chỉ cần ngăn phình thêm VACUUM thường autovacuum đủ không thu nhỏ, cốt lõi VACUUM FULL thu nhỏ file thật 1065 xuống 355 MB nhưng giữ khóa ACCESS EXCLUSIVE chặn cả đọc lẫn ghi suốt thời gian chạy SELECT bị hủy vì lock timeout còn cần khoảng 2 lần đĩa tạm và downtime tỉ lệ kích thước chỉ dùng khi có cửa sổ bảo trì production 24 trên 7 dùng pg_repack thay thế

Hình 2: Đo thật, pg_locks cho thấy AccessExclusiveLock (granted=t) khi VACUUM FULL chạy; một SELECT count(*) với lock_timeout 400ms bị ERROR: canceling statement due to lock timeout. VACUUM FULL thu 1065 MB → 355 MB trong 1.198 ms. Khác VACUUM thường (không chặn đọc/ghi).

  • pg_locks trong lúc VACUUM FULL: AccessExclusiveLock (granted=t) trên bảng.
  • Một SELECT count(*) bình thường (với lock_timeout 400ms) bị ERROR: canceling statement due to lock timeout — nó chờ khóa mà không được.

Điều này rất khác VACUUM thường (chạy online, không chặn đọc/ghi). Với VACUUM FULL, toàn bộ ứng dụng "treo" trên bảng đó suốt thời gian chạy — trên bảng nóng, đây là downtime thật.

Rủi ro 2: cần thêm gấp đôi đĩa

Vì VACUUM FULL dựng một bản sao mới của bảng trước khi xóa bản cũ, ở đỉnh điểm nó cần chỗ cho cả hai — gần gấp đôi dung lượng bảng. Nếu đĩa đã gần đầy và bạn chạy VACUUM FULL trên một bảng lớn, nó có thể hết đĩa giữa chừng và thất bại — mà đĩa đầy thường chính là lý do bạn muốn thu hồi dung lượng ngay từ đầu. Nghịch lý này khiến VACUUM FULL đặc biệt nguy hiểm đúng lúc bạn cần nó nhất.

Rủi ro 3: downtime tỉ lệ kích thước bảng

Thời gian VACUUM FULL chạy tỉ lệ với kích thước bảng — đo thật 1.198 ms cho bảng ~1 GB, nhưng một bảng 100 GB có thể mất hàng giờ. Suốt thời gian đó, khóa ACCESS EXCLUSIVE giữ nguyên. Với bảng thật lớn trên hệ thống 24/7, thời gian khóa này là không chấp nhận được.

Khi nào dùng, và lựa chọn thay thế

Tình huống Nên dùng
Bloat nặng, có cửa sổ bảo trì VACUUM FULL — đơn giản, không cần cài gì
Bảng 24/7, không được downtime pg_repack — thu nhỏ online, gần như không khóa
Chỉ cần ngăn phình thêm VACUUM thường / autovacuum — đủ, không thu nhỏ

pg_repack là extension dựng lại bảng gọn online — nó tạo bản sao và đồng bộ thay đổi trong lúc chạy, chỉ cần khóa rất ngắn ở cuối để hoán đổi. Đây là công cụ đúng cho production. CLUSTER (sắp lại bảng theo index) cũng thu nhỏ nhưng cũng khóa ACCESS EXCLUSIVE như VACUUM FULL — không phải lựa chọn online.

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

Đừng chạy VACUUM FULL theo lịch định kỳ. Nếu bạn thấy mình cần VACUUM FULL thường xuyên, vấn đề thật là autovacuum không theo kịp (bài trước) — sửa cấu hình autovacuum để bloat không tích lũy, thay vì dọn bằng VACUUM FULL lặp đi lặp lại. VACUUM FULL nên là biện pháp một lần sau một sự cố bloat, không phải bảo trì thường quy.

Một chút bloat không đáng VACUUM FULL. Chi phí (khóa, đĩa, downtime) chỉ đáng khi bloat nghiêm trọng (ví dụ free_percent > 50% trên bảng lớn). Với bloat nhẹ, để VACUUM thường tái dùng chỗ trống là đủ — thu nhỏ file vài phần trăm không đáng downtime.

Kiểm đĩa trước khi chạy. Luôn xác nhận còn ít nhất bằng kích thước bảng dung lượng trống trước khi VACUUM FULL, để tránh hết đĩa giữa chừng. Với bảng rất lớn, pg_repack an toàn hơn vì có thể theo dõi và dừng.

Ba ý mang về

  1. VACUUM FULL thu nhỏ file thật nhưng khóa ACCESS EXCLUSIVE chặn cả đọc: đo thật, pg_locks cho thấy khóa này khi chạy, và một SELECT count(*) bị canceling statement due to lock timeout — khác hẳn VACUUM thường vốn không chặn đọc/ghi.
  2. Còn hai rủi ro nữa: cần gần gấp đôi đĩa tạm (dựng bản sao trước khi xóa cũ — có thể hết đĩa giữa chừng), và downtime tỉ lệ kích thước bảng (1 GB mất ~1,2 giây, 100 GB có thể mất hàng giờ).
  3. Chỉ dùng khi có cửa sổ bảo trì; production 24/7 dùng pg_repack: pg_repack thu nhỏ online gần như không khóa; và nếu phải VACUUM FULL thường xuyên thì gốc rễ là autovacuum không theo kịp — sửa cấu hình chứ đừng dọn lặp lại.

Phần sau ta quay lại một tối ưu chống bloat đã nhắc nhiều lần: Phần sau mổ xẻ HOT update — cách PostgreSQL cập nhật một dòng mà không đụng index, điều kiện để nó xảy ra, và vì sao nó giảm mạnh bloat.