Loạt bài về bloat và VACUUM đã dùng pgstattuple nhiều lần để đo mức bloat — giờ ta mổ xẻ chính công cụ đó. Câu hỏi "bảng này bloat bao nhiêu?" nghe đơn giản nhưng khó trả lời chính xác từ thống kê ước lượng của pg_stat_user_tables. pgstattuple cho câu trả lời chính xác bằng cách quét thật từng trang. Bài này giải thích các con số nó trả về, đo thật sự khác biệt giữa bản chính xác và bản xấp xỉ nhanh, và cách đo bloat của index riêng biệt.
pgstattuple: quét cả bảng, cho con số chính xác
pgstattuple là hàm trong extension cùng tên, quét mọi trang của bảng để đếm chính xác không gian sống, chết, và trống:
CREATE EXTENSION pgstattuple;
SELECT * FROM pgstattuple('pt');
-- table_len 297 MB -- tổng dung lượng bảng
-- tuple_percent 30.66 -- % là dữ liệu SỐNG
-- dead_tuple_percent 0 -- % dòng chết
-- free_percent 64.8 -- % CHỖ TRỐNG = mức bloat
Con số quan trọng nhất để đánh giá bloat là free_percent — phần trăm không gian trống trong bảng. Trên bảng đo thật này (2 triệu dòng, xóa 2/3, đã VACUUM), free_percent là 64,8%: gần hai phần ba dung lượng file là chỗ trống. tuple_percent chỉ 30,66% — dữ liệu sống chiếm chưa tới một phần ba. Đây là dấu hiệu bloat rõ ràng.

Hình 1: pgstattuple quét cả bảng cho con số chính xác — free_percent là mức bloat. Bản _approx dùng visibility map để nhanh hơn. pgstatindex đo bloat của index riêng.
Chính xác vs xấp xỉ: nhanh hơn 25 lần
Nhược điểm của pgstattuple là nó quét toàn bộ bảng — tốn kém trên bảng lớn (một bảng 100 GB có thể mất phút). Giải pháp là pgstattuple_approx, dùng visibility map (đã bàn ở bài VACUUM) để bỏ qua các trang đã all-visible, chỉ quét phần cần thiết:

Hình 2: pgstattuple chính xác cho free_percent 64,80% nhưng quét 100% bảng (46 ms); pgstattuple_approx cho 64,58% với scanned_percent 0% (dùng visibility map) trong 1,85 ms — nhanh hơn ~25 lần, sai số nhỏ. pgstatindex đo avg_leaf_density của index.
pgstattuple(chính xác):free_percent64,80%, quét 100% bảng — 46 ms.pgstattuple_approx(xấp xỉ):free_percent64,58%,scanned_percent0% (mọi trang đã all-visible nên bỏ qua hết) — 1,85 ms, nhanh hơn ~25 lần.
Kết quả gần như bằng nhau (64,80% vs 64,58%) nhưng tốc độ khác 25 lần. Với bảng lớn, pgstattuple_approx là lựa chọn đúng — sai số nhỏ không đáng để trả giá cho một lần quét toàn bảng.
pgstatindex: đo bloat của index riêng
Bloat bảng và bloat index là hai thứ khác nhau — VACUUM dọn dead tuple bảng nhưng index vẫn có thể phình. pgstatindex đo bloat của một index cụ thể:
SELECT * FROM pgstatindex('pt_pkey');
-- avg_leaf_density 30.22 -- lá index chỉ đầy 30% = index bloat
-- leaf_fragmentation ... -- mức phân mảnh lá
Chỉ số chính là avg_leaf_density — phần trăm các trang lá của index được lấp đầy. Trên index đo thật này, density chỉ 30,22%: các trang lá phần lớn rỗng, index bị bloat nặng dù bảng đã VACUUM. Density thấp là dấu hiệu cần REINDEX (đã bàn ở bài index bloat). Một bảng có free_percent thấp nhưng index density thấp cần REINDEX chứ không phải VACUUM FULL.
Đánh đổi cần cân nhắc
Dùng ước lượng nhanh cho giám sát thường xuyên, pgstattuple cho chẩn đoán. Với dashboard theo dõi liên tục, công thức ước lượng bloat dựa trên pg_stat_user_tables (n_dead_tup, reltuples) là đủ và không tốn quét. Chỉ chạy pgstattuple/pgstattuple_approx khi cần xác nhận chính xác một bảng nghi ngờ bloat — không chạy nó định kỳ trên mọi bảng lớn.
pgstattuple_approx cần visibility map cập nhật để nhanh. Tốc độ của bản xấp xỉ đến từ việc bỏ qua trang all-visible — mà những trang đó chỉ được đánh dấu sau VACUUM. Trên bảng vừa ghi nhiều chưa VACUUM, visibility map lỗi thời và approx phải quét nhiều hơn, chậm lại. Kết quả nhanh nhất khi bảng đã được vacuum gần đây.
pgstattuple cần quyền và có thể nặng. Nó đọc mọi trang nên tốn I/O; trên bảng cực lớn đang tải cao, tránh chạy bản chính xác giờ cao điểm. Cân nhắc chạy vào lúc rảnh hoặc dùng approx.
Ba ý mang về
- pgstattuple đo bloat chính xác bằng cách quét cả bảng:
free_percentlà mức bloat — đo thật bảng 297 MB cófree_percent64,8% (gần 2/3 là chỗ trống) vàtuple_percentchỉ 30,66% dữ liệu sống. - pgstattuple_approx nhanh hơn ~25 lần nhờ visibility map: đo thật, bản chính xác quét 100% bảng trong 46 ms còn approx bỏ qua trang all-visible (scanned 0%) chỉ mất 1,85 ms, với kết quả gần như y hệt (64,58% vs 64,80%) — dùng cho bảng lớn.
- pgstatindex đo bloat index riêng biệt:
avg_leaf_densitythấp (đo thật 30,22%) báo index bloat cần REINDEX — bloat bảng và bloat index là hai thứ khác nhau, cần công cụ khác nhau để đo và cách khác nhau để dọn.
Phần sau ta đi tới công cụ dọn bloat không gây downtime đã nhắc nhiều lần: Phần sau mổ xẻ pg_repack — cách nó thu nhỏ bảng online gần như không khóa, khác VACUUM FULL thế nào, và các bước cùng lưu ý khi dùng trên production.