Bài trước nói VACUUM "autovacuum tự lo" — nhưng "tự lo" không có nghĩa là "lo đúng". Autovacuum có một công thức quyết định khi nào chạy, và mặc định của công thức đó hoạt động tốt cho bảng nhỏ nhưng thất bại âm thầm trên bảng lớn, để bloat tích lũy tới hàng triệu dòng trước khi đụng tay vào. Bài này giải thích công thức, đo thật cạm bẫy trên bảng lớn, và chỉ cách chỉnh cùng theo dõi để autovacuum thực sự theo kịp tải ghi.

Công thức: khi nào autovacuum chạy

Autovacuum kích hoạt VACUUM cho một bảng khi số dead tuple vượt một ngưỡng động:

n_dead_tup > autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × n_live_tup

Mặc định: threshold = 50, scale_factor = 0.2. Nghĩa là autovacuum đợi cho tới khi dead tuple đạt 50 cộng 20% số dòng sống. Với bảng 100.000 dòng, ngưỡng là 50 + 0.2 × 100000 = 20.050 dead tuple.

-- xem tham số hiện tại
SELECT name, setting FROM pg_settings
WHERE name LIKE 'autovacuum%';   -- threshold=50, scale_factor=0.2, naptime=60, max_workers=3

Ảnh chụp đoạn mã SQL nền tối minh hoạ autovacuum cấu hình và theo dõi, công thức khi nào autovacuum chạy cho một bảng autovacuum chạy khi n_dead_tup lớn hơn threshold cộng scale_factor nhân n_live_tup mặc định threshold 50 scale_factor 0.2 bảng 100000 dòng ngưỡng bằng 50 cộng 0.2 nhân 100000 bằng 20050 dead, cạm bẫy scale_factor 0.2 quá cao cho bảng lớn bảng 10000 dòng chờ 2050 dead ổn bảng 1000000 dòng chờ 200050 dead bắt đầu nhiều bảng 100000000 dòng chờ 20000050 dead 20 triệu bloat khổng lồ tỉ lệ phần trăm cố định nghĩa là bảng càng lớn càng chờ lâu, sửa hạ scale_factor cho từng bảng ghi nhiều ALTER TABLE don_hang SET autovacuum_vacuum_scale_factor 0.01 1 phần trăm thay vì 20 phần trăm autovacuum_vacuum_threshold 1000 bảng 100M chờ 1 triệu thay vì 20 triệu 20 lần tốt hơn, theo dõi bảng nào cần chú ý SELECT relname n_live_tup n_dead_tup last_autovacuum autovacuum_count round n_dead_tup chia greatest n_live_tup 1 nhân 100 1 AS pct_dead FROM pg_stat_user_tables ORDER BY n_dead_tup DESC pct_dead cao cộng last_autovacuum cũ bằng autovacuum không theo kịp

Hình 1: Autovacuum chạy khi dead tuple vượt threshold + scale_factor × n_live_tup. Vì scale_factor là tỉ lệ cố định, bảng càng lớn ngưỡng càng cao. Chỉnh per-table và theo dõi qua pg_stat_user_tables.

Đo thật: cạm bẫy scale_factor trên bảng lớn

scale_factor là một tỉ lệ phần trăm cố định, và đây chính là vấn đề. Bảng càng lớn, ngưỡng tuyệt đối càng cao — autovacuum càng đợi lâu, càng nhiều bloat tích lũy. Tính ngưỡng cho các cỡ bảng khác nhau:

Ảnh chụp bảng kết quả đo thật nền tối autovacuum công thức ngưỡng và cấu hình PostgreSQL 16 pg_settings pg_stat_user_tables mặc định threshold 50 scale_factor 0.2 naptime 60s max_workers 3, ngưỡng kích hoạt bằng threshold cộng scale_factor nhân n_live_tup số dòng bảng 10000 ngưỡng mặc định 0.2 2050 dead ngưỡng nếu chỉnh 0.01 150 dead số dòng 1000000 ngưỡng mặc định 200050 dead chỉnh 10050 dead số dòng 100000000 ngưỡng mặc định 20000050 dead chỉnh 1000050 dead, scale_factor là phần trăm cố định bảng càng lớn autovacuum càng chờ lâu bloat khổng lồ trước khi chạy bảng 100 triệu dòng phải tích 20 triệu dead tuple mới kích hoạt ở mặc định 0.2, sửa chỉnh scale_factor per-table cho bảng lớn ghi nhiều ALTER TABLE av SET autovacuum_vacuum_scale_factor 0.01 autovacuum_vacuum_threshold 1000 reloptions bằng autovacuum_vacuum_scale_factor 0.01 autovacuum_vacuum_threshold 1000 mỗi bảng có cấu hình riêng không cần đổi toàn cục, theo dõi qua pg_stat_user_tables n_dead_tup số dead tuple hiện tại so với ngưỡng last_autovacuum lần autovacuum gần nhất cũ bằng có thể không theo kịp autovacuum_count tổng số lần autovacuum đã chạy trên bảng pct_dead n_dead_tup chia n_live_tup cao bất thường bằng cần chú ý, cốt lõi autovacuum chạy khi n_dead_tup vượt threshold cộng scale_factor nhân n_live_tup mặc định scale_factor 0.2 quá cao cho bảng lớn chờ tới 20 triệu dead trên bảng 100M dòng hạ scale_factor per-table cho bảng lớn ghi nhiều và theo dõi bằng pg_stat_user_tables

Hình 2: Với scale_factor mặc định 0.2: bảng 10.000 dòng chờ 2.050 dead (ổn), bảng 1 triệu chờ 200.050, bảng 100 triệu chờ 20.000.050 dead (20 triệu!). Hạ per-table xuống 0.01 giảm còn 1 triệu — tốt hơn 20 lần.

  • 10.000 dòng: chờ 2.050 dead — ổn.
  • 1.000.000 dòng: chờ 200.050 dead — bắt đầu nhiều.
  • 100.000.000 dòng: chờ 20.000.050 dead — 20 triệu dòng chết tích lũy trước khi autovacuum động tay! Bảng phình khổng lồ, truy vấn chậm dần, mà không lỗi nào báo.

Đây là lý do bảng lớn ghi nhiều thường bị bloat dù autovacuum "đang bật": ngưỡng mặc định quá cao cho quy mô đó.

Sửa: chỉnh scale_factor per-table

Không cần đổi cấu hình toàn cục (ảnh hưởng mọi bảng). PostgreSQL cho phép chỉnh từng bảng:

ALTER TABLE don_hang SET (
  autovacuum_vacuum_scale_factor = 0.01,   -- 1% thay vì 20%
  autovacuum_vacuum_threshold = 1000);

Với scale_factor = 0.01, bảng 100 triệu dòng chỉ chờ ~1 triệu dead thay vì 20 triệu — tốt hơn 20 lần. Đo thật xác nhận cơ chế hoạt động: sau khi hạ scale_factor xuống 0.01 trên một bảng 100.000 dòng đang có 25.000 dead tuple (vượt ngưỡng mới 2.000), autovacuum chạy ngay trong chu kỳ tiếp theo — autovacuum_count tăng lên 1 và n_dead_tup về 0. Quy tắc: bảng càng lớn và ghi càng nhiều, scale_factor càng nên thấp.

Theo dõi: bảng nào cần chú ý

pg_stat_user_tables cho biết autovacuum có theo kịp không:

SELECT relname, n_live_tup, n_dead_tup, last_autovacuum, autovacuum_count,
  round(n_dead_tup::numeric / greatest(n_live_tup,1) * 100, 1) AS pct_dead
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;

Các cột quan trọng: n_dead_tup (so với ngưỡng), last_autovacuum (cũ = có thể không theo kịp), autovacuum_count (số lần đã chạy), và pct_dead (tỉ lệ chết). Một bảng có pct_dead cao và last_autovacuum lâu là dấu hiệu autovacuum tụt lại — cần hạ scale_factor hoặc tăng số worker.

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

Đừng hạ scale_factor toàn cục quá thấp. Nếu đặt scale_factor = 0.01 cho cả cụm, các bảng nhỏ sẽ bị vacuum quá thường xuyên (mỗi vài chục dead tuple), tốn CPU/IO vô ích. Chỉnh per-table cho những bảng lớn ghi nhiều là cách đúng — giữ mặc định cho phần còn lại.

Autovacuum có thể bị giới hạn tốc độ, không chỉ ngưỡng. Ngay cả khi ngưỡng đúng, autovacuum có thể chạy chậm vì autovacuum_vacuum_cost_delay/cost_limit bóp ga để không đè I/O. Trên máy chủ mạnh, tăng cost_limit (hoặc giảm cost_delay) giúp autovacuum dọn nhanh hơn khi tải ghi cao. Số worker (autovacuum_max_workers, mặc định 3) cũng giới hạn bao nhiêu bảng được vacuum song song.

Autovacuum để INSERT-only cũng cần từ PG13. Bảng chỉ INSERT (không UPDATE/DELETE) không sinh dead tuple, nhưng vẫn cần vacuum để freeze (chống wraparound) và cập nhật visibility map. PG13+ có autovacuum_vacuum_insert_scale_factor xử lý việc này — đừng cho rằng bảng chỉ chèn thì không cần autovacuum.

Ba ý mang về

  1. Autovacuum chạy theo công thức threshold + scale_factor × n_live_tup: mặc định 50 + 20% số dòng sống — với bảng 100.000 dòng là 20.050 dead tuple, hợp lý cho bảng nhỏ.
  2. scale_factor mặc định 0.2 là cạm bẫy cho bảng lớn: đo thật, bảng 100 triệu dòng phải tích 20 triệu dead tuple mới kích hoạt autovacuum — bloat khổng lồ tích lũy âm thầm; hạ scale_factor per-table xuống 0.01 giảm còn 1 triệu (tốt hơn 20 lần).
  3. Theo dõi qua pg_stat_user_tables: n_dead_tup, last_autovacuum, autovacuum_count, và tỉ lệ pct_dead cho biết autovacuum có theo kịp không — bảng pct_dead cao với last_autovacuum cũ cần chỉnh scale_factor hoặc tăng worker.

Phần sau ta xét công cụ mạnh nhưng nguy hiểm mà nhiều bài đã nhắc: Phần sau mổ xẻ VACUUM FULL — khi nào thật sự cần nó để thu hồi dung lượng, vì sao nó khóa toàn bảng, và các rủi ro cùng lựa chọn thay thế như pg_repack.