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.

Ảnh chụp đoạn mã SQL nền tối minh hoạ pgstattuple đo bloat chính xác và bản xấp xỉ nhanh, pgstattuple quét cả bảng cho con số chính xác CREATE EXTENSION pgstattuple SELECT sao FROM pgstattuple pt table_len 297 MB tổng dung lượng bảng tuple_percent 30.66 phần là dữ liệu sống dead_tuple_percent 0 phần dòng chết đã VACUUM free_percent 64.8 phần chỗ trống bằng mức bloat, đọc con số bloat bằng free_percent cao bảng khỏe tuple_percent cao free_percent thấp bảng bloat free_percent cao nhiều chỗ trống từ dòng đã xóa cập nhật 64.8 phần trăm chỗ trống nghĩa là khoảng 2 phần 3 file là không gian phí, pgstattuple_approx nhanh nhờ visibility map SELECT sao FROM pgstattuple_approx pt approx_free_percent 64.58 gần bằng bản chính xác scanned_percent 0 bỏ qua trang all-visible map dùng cho bảng lớn nhanh hơn nhiều sai số nhỏ, pgstatindex đo bloat của index riêng SELECT sao FROM pgstatindex pt_pkey avg_leaf_density 30.22 lá index chỉ đầy 30 phần trăm bằng index bloat leaf_fragmentation mức phân mảnh lá density thấp REINDEX bảng bloat và index bloat là hai thứ khác nhau

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:

Ảnh chụp bảng kết quả đo thật nền tối pgstattuple bảng 2 triệu dòng xóa 2 phần 3 PostgreSQL 16 pgstattuple pgstattuple_approx pgstatindex timing shared_buffers 128MB, pgstattuple pt chính xác quét cả bảng table_len 297 MB tổng dung lượng tuple_percent 30,66 phần trăm phần là dữ liệu sống dead_tuple_percent 0 phần trăm dòng chết đã VACUUM free_percent 64,8 phần trăm chỗ trống bằng mức bloat, chính xác vs xấp xỉ kết quả gần nhau tốc độ khác 25 lần pgstattuple chính xác free_percent 64,80 phần trăm scanned_percent 100 phần trăm quét cả bảng 46 mili giây pgstattuple_approx xấp xỉ free_percent 64,58 phần trăm scanned_percent 0 phần trăm dùng visibility map 1,85 mili giây approx bỏ qua trang all-visible nhờ visibility map nhanh hơn khoảng 25 lần với sai số nhỏ bảng lớn dùng approx để tránh quét toàn bộ tốn kém, pgstatindex pt_pkey bloat của index khác bloat bảng index_size 45 MB kích thước index avg_leaf_density 30,22 phần trăm lá chỉ đầy 30 phần trăm bằng index bloat leaf_fragmentation 0 mức phân mảnh, cốt lõi pgstattuple đo bloat chính xác bằng cách quét bảng free_percent bằng mức bloat bản approx dùng visibility map bỏ qua trang all-visible nhanh hơn khoảng 25 lần với sai số nhỏ hợp bảng lớn pgstatindex đo bloat index riêng avg_leaf_density thấp bằng cần REINDEX bloat bảng và index là hai thứ

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_percent 64,80%, quét 100% bảng — 46 ms.
  • pgstattuple_approx (xấp xỉ): free_percent 64,58%, scanned_percent 0% (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ề

  1. pgstattuple đo bloat chính xác bằng cách quét cả bảng: free_percent là mức bloat — đo thật bảng 297 MB có free_percent 64,8% (gần 2/3 là chỗ trống) và tuple_percent chỉ 30,66% dữ liệu sống.
  2. 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.
  3. pgstatindex đo bloat index riêng biệt: avg_leaf_density thấ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.