Bài trước cho thấy UPDATE để lại bản chết, tích tụ thành bloat làm bảng phình to. Nếu bloat cứ tăng mãi, database sẽ ngốn hết đĩa và chậm dần đến mức không dùng được. PostgreSQL giải quyết điều này bằng VACUUM — tiến trình dọn bản chết — và quan trọng hơn, bằng autovacuum, cơ chế tự động chạy VACUUM mà không cần ai can thiệp. autovacuum là một trong những thành phần sống còn nhất của PostgreSQL; hiểu và cấu hình nó đúng là ranh giới giữa một database khỏe mạnh và một database sắp sập.
Bài này (phần 8 loạt PostgreSQL) đo thật autovacuum tự kích hoạt trên pg-lab, xem VACUUM báo cáo gì, và vì sao không gian được tái dùng giữ bảng ổn định.
VACUUM làm gì, autovacuum kích hoạt khi nào
VACUUM có ba việc chính: (1) đánh dấu không gian của bản chết là tái dùng được (reusable), (2) dọn các con trỏ trỏ tới bản chết trong index, (3) cập nhật thống kê cho planner và đóng băng (freeze) các txid cũ (chống wraparound — nói dưới). Lưu ý: VACUUM không trả đĩa cho OS (chỉ VACUUM FULL làm, bài 7); nó làm không gian chết sẵn sàng để UPDATE sau lấp vào.
autovacuum tự chạy VACUUM cho một bảng khi số bản chết vượt một ngưỡng tính theo công thức:

Hình 1: VACUUM dọn bản chết (tái dùng không gian, dọn index, chống wraparound) — không trả đĩa. autovacuum tự chạy khi n_dead_tup vượt ngưỡng = threshold + scale_factor × n_live_tup (mặc định 50 + 20%). Theo dõi qua pg_stat_user_tables.
nguong = autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * n_live_tup
# mac dinh: 50 + 0.2 (20%) * so hang song
Đo thật: autovacuum tự chạy, không cần lệnh
Mình tạo bảng 10.000 hàng với ngưỡng thấp (threshold=1000), UPDATE toàn bộ (tạo 10.000 bản chết), rồi chỉ chờ — không gõ lệnh VACUUM nào — và theo dõi pg_stat_user_tables. Kết quả thật từ pg-lab:

Hình 2: Kết quả thật. autovacuum tự chạy: n_dead_tup 10.000→0, autovacuum_count 0→1 ở 08:25:07, không ai gõ lệnh. VACUUM VERBOSE báo "5000 removed" + dọn index. VACUUM đều giữ kích thước ổn định ở 536 kB nhờ tái dùng không gian.
- autovacuum tự kích hoạt: trong ~27 giây đầu,
n_dead_tup=10000,autovacuum_count=0(chưa chạy). Đến ~30 giây:n_dead_tup=0,autovacuum_count=1,last_autovacuum=08:25:07. autovacuum tự phát hiện dead tuple vượt ngưỡng và dọn — không ai gõ một lệnh nào. Đây là điều quan trọng nhất của bài: bạn không phải nhớ chạy VACUUM, hệ thống tự lo. - VACUUM VERBOSE báo cáo cụ thể: chạy tay trên một bảng khác, nó in
tuples: 5000 removed(dọn 5000 bản chết),index "vv_pkey": 5000 dead item identifiers removed(dọn cả index),pages: 45 scanned (100%). Bạn thấy chính xác nó làm gì. - Không gian tái dùng → kích thước ổn định: sau VACUUM 360 kB; 2 UPDATE nữa (chưa vacuum) → 536 kB (10000 dead); nhưng sau khi VACUUM rồi UPDATE tiếp → vẫn 536 kB. Không gian chết được tái dùng, bảng không phình vô hạn. Đây chính là lý do autovacuum giữ database khỏe: VACUUM đều → bloat không tăng.
Đánh đổi cần cân nhắc
Mặc định scale_factor 20% quá cao cho bảng lớn — cần tune. Công thức ngưỡng dùng scale_factor=0.2 nghĩa là autovacuum chỉ chạy khi 20% số hàng là bản chết. Với bảng 1 triệu hàng, phải tích tụ ~200.000 bản chết mới dọn — bảng đã bloat đáng kể trước khi được dọn. Với bảng lớn hoặc UPDATE nặng, nên đặt autovacuum_vacuum_scale_factor thấp hơn (ví dụ 0.01-0.05) riêng cho bảng đó, để dọn thường xuyên hơn khi bảng còn ít bloat. Đây là một trong những tinh chỉnh phổ biến nhất cho PostgreSQL production.
autovacuum cũng chống transaction ID wraparound — một sự cố chết người. PostgreSQL dùng txid 32-bit; nếu không "đóng băng" (freeze) các hàng cũ, txid sẽ cạn kiệt sau ~2 tỉ giao dịch và database dừng nhận ghi để tránh hỏng dữ liệu — một sự cố production nổi tiếng. autovacuum tự động freeze các txid cũ để ngăn điều này (đo được qua age(datfrozenxid)). Đây là lý do tối quan trọng để không bao giờ tắt autovacuum: kể cả khi bạn không lo bloat, bạn vẫn cần nó chống wraparound.
Đừng tắt autovacuum; thay vào đó điều chỉnh nó. Một sai lầm kinh điển là tắt autovacuum vì "nó làm chậm lúc cao điểm". Tắt nó khiến bloat tích tụ và wraparound rình rập — thảm họa chờ sẵn. Nếu autovacuum gây tải lúc cao điểm, hãy điều chỉnh thay vì tắt: tăng autovacuum_max_workers, giảm autovacuum_vacuum_cost_delay (để chạy nhanh hơn, xong sớm hơn), hoặc đặt lịch VACUUM tay bổ sung lúc rảnh. autovacuum là bạn, không phải kẻ thù — chỉ cần dạy nó chạy đúng cách.
Ba ý mang về
- autovacuum tự dọn bản chết khi vượt ngưỡng — không cần can thiệp. Đo thật: UPDATE tạo 10.000 dead tuple, autovacuum tự chạy sau ~30s đưa n_dead_tup về 0, autovacuum_count tăng, không ai gõ lệnh. Ngưỡng = threshold + scale_factor × n_live_tup.
- VACUUM dọn bản chết và index, làm không gian tái dùng được. Đo thật: VACUUM VERBOSE báo "5000 removed" + dọn index; sau VACUUM, UPDATE tiếp vẫn giữ kích thước 536 kB (tái dùng không gian chết) thay vì phình vô hạn. Đây là lý do autovacuum giữ database ổn định.
- Tune autovacuum, đừng tắt. Mặc định scale_factor 20% quá cao cho bảng lớn (hạ xuống 1-5% cho bảng UPDATE nặng); autovacuum còn chống transaction ID wraparound (txid 32-bit cạn → DB dừng ghi) — một lý do sống còn để không bao giờ tắt nó. Nếu gây tải, điều chỉnh workers/cost_delay thay vì tắt.
Nguồn
- PostgreSQL docs — The Autovacuum Daemon: https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM
- PostgreSQL docs — Preventing Transaction ID Wraparound Failures: https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
- PostgreSQL docs — Automatic Vacuuming parameters: https://www.postgresql.org/docs/current/runtime-config-autovacuum.html
Phần sau ta nối MVCC với index: HOT update (heap-only tuple) giảm chi phí ghi index khi cập nhật, và index-only scan — đo thật tác động của chúng qua EXPLAIN.