BRIN nhỏ hơn B-tree 4570 lần, nhưng chỉ cho đến khi 5% số dòng bị cập nhật
BRIN của PostgreSQL là một chỉ mục cực kỳ nhỏ gọn so với B-tree — chỉ bằng 1/4570 kích thước trong bài kiểm tra 10 triệu dòng. Tuy nhiên, khi chỉ 5% số dòng bị cập nhật, hiệu suất truy vấn giảm mạnh gấp 23 lần do các dữ liệu cũ nằm rải rác, một vấn đề mà pg_stats.correlation không kịp cảnh báo.

BRIN nhỏ hơn B-tree 4570 lần, nhưng chỉ cho đến khi 5% số dòng bị cập nhật
BRIN của PostgreSQL là một "vũ khí bí mật" cho các bảng lớn — chỉ 48 kB so với 214 MB của chỉ mục B-tree tương đương. Tuy nhiên, một bài phân tích chuyên sâu từ DeepSQL đã phát hiện ra một "lỗ hổng" nghiêm trọng: chỉ cần 5% số dòng bị cập nhật, hiệu suất truy vấn sụt giảm tới 23 lần, dù pg_stats.correlation vẫn báo giá trị gần 1. Đây là một bài học quan trọng cho các kỹ sư vận hành cơ sở dữ liệu đang dùng BRIN cho dữ liệu chuỗi thời gian.
BRIN là gì và vì sao nó quan trọng?
BRIN (Block Range Index) hoạt động dựa trên ý tưởng giống với Zonemaps mà Oracle đã xây dựng — và tác giả bài viết Venkat Sakamuri chính là người từng đồng sở hữu module Zonemaps tại Oracle. Thay vì lưu từng khóa riêng lẻ như B-tree, BRIN chỉ lưu giá trị min/max của mỗi dải trang (page range).
Với bảng 10 triệu dòng được chèn theo thứ tự thời gian, tác giả đã đo được:
- Chỉ mục BRIN: 48 kB — vừa vặn trong L2 cache
- Chỉ mục B-tree: 214 MB — lớn gấp 4.570 lần
- Truy vấn 1 ngày: quét chỉ 1.536 trang heap, mất 21,2 ms
Đây chính là ưu điểm vượt trội khiến BRIN trở thành lựa chọn hàng đầu cho bảng dữ liệu log, sự kiện hoặc IoT nơi dữ liệu thường được ghi thêm mà không sửa đổi.
Thử nghiệm "churn" — sự xuống cấp bất ngờ
Tác giả đã mô phỏng một kịch bản thực tế: cập nhật cột status trên một tỷ lệ ngày càng tăng số dòng, sau đó chạy VACUUM và đo lại cùng một truy vấn. Kết quả khiến nhiều người kinh ngạc:
| Trạng thái | Correlation | Số trang heap (lossy) | Dòng bị loại khi recheck | Thời gian thực thi |
|---|---|---|---|---|
| Dữ liệu mới | 1.000 | 1.536 | 11.781 | 21,2 ms |
| 1% dòng bị cập nhật | 0.979 | 1.806 | 32.243 | 24,2 ms |
| 5% dòng bị cập nhật | 0.921 | 51.268 | 3.827.572 | 558,7 ms |
| 20% dòng bị cập nhật | 0.782 | 63.923 | 4.216.606 | 690,6 ms |
Điểm mấu chốt nằm ở dòng thứ ba: giữa mức churn 1% và 5%, lượng I/O tăng 28 lần và thời gian thực thi tăng 23 lần — dù truy vấn trả về cùng một kết quả y hệt.
Vì sao correlation "nói dối"?
Đây là điểm thú vị nhất của bài viết. Hầu hết các kỹ sư DBA đều dựa vào pg_stats.correlation để đánh giá mức độ phù hợp của BRIN với dữ liệu. Khi correlation gần 1, họ mặc định rằng BRIN sẽ hoạt động tốt.
Nhưng tại mức churn 5%, correlation vẫn ở mức 0.921 — tưởng chừng rất tốt — trong khi BRIN đã "sụp đổ" hoàn toàn. Vì sao?
Bản chất của VACUUM là di chuyển các phiên bản dữ liệu cũ (dead tuples) đến các vùng trống mới trong heap, khiến dữ liệu "mới" bị phân tán ra ngoài phạm vi thời gian mà BRIN dự đoán. BRIN đọc các dải trang dựa trên min/max, nhưng khi các dải này bị "nhiễm bẩn" bởi các dòng ngoài phạm vi, toàn bộ dải đó phải được quét.
Giả sử bảng có 90 ngày dữ liệu, truy vấn lọc trong 1 ngày. Sau khi churn, mỗi dải trang có thể chứa vài dòng có created_at nằm ngoài phạm vi, khiến BRIN không thể loại bỏ dải đó — dẫn đến việc quét gần như toàn bộ bảng.
Kết quả tái lập và hạn chế
Một điểm đáng khen là tác giả đã công khai toàn bộ script đo lường để độc giả tự chạy lại. Khi thử trên PostgreSQL 16.13 với phần cứng khác:
- Dữ liệu mới: kết quả giống hệt đến từng con số — đây là bản chất cố hữu của BRIN
- Mức churn 5%: sai khác chỉ ~1,5% — chứng minh "vách đá" là một đặc tính ổn định
- Mức churn 20%: kết quả giữa hai phiên bản khác nhau tới gần 2 lần (63.923 trang so với 114.490 trang)
Lý do: vị trí đặt phiên bản mới của dòng dữ liệu phụ thuộc vào trạng thái free-space map và thời điểm VACUUM chạy, vốn khác nhau giữa các lần chạy.
Bài học cho nhà phát triển Việt Nam
Đối với các đội ngũ tại Việt Nam đang vận hành PostgreSQL cho hệ thống log, giao dịch tài chính, hoặc dữ liệu cảm biến IoT, bài viết này mang đến vài gợi ý thiết thực:
- Đừng tin tuyệt đối vào pg_stats.correlation — BRIN có thể suy giảm hiệu suất từ rất sớm mà correlation không phản ánh kịp
- Với bảng có dữ liệu bị UPDATE thường xuyên, hãy xem xét
CLUSTERđịnh kỳ để sắp xếp lại vật lý dữ liệu — bảng sau khiCLUSTERlại cho kết quả tương đương trạng thái "fresh" - Theo dõi "Rows Removed by Index Recheck" trong EXPLAIN ANALYZE — nếu con số này tăng đột biến, đó là dấu hiệu sớm nhất của sự xuống cấp
-- Đo số dòng bị recheck để phát hiện sớm sự thoái hóa của BRIN
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM events
WHERE created_at >= '2026-02-15' AND created_at < '2026-02-16';
Minh họa chỉ mục BRIN trong PostgreSQL
Kết luận
BRIN là một công cụ tuyệt vời cho các bảng "append-only" — và vẫn là lựa chọn đúng cho nhiều ứng dụng. Nhưng nếu dữ liệu của bạn có tỷ lệ cập nhật dù chỉ vài phần trăm, hãy đo lường thực tế thay vì dựa vào lý thuyết. Sự khác biệt giữa 1% và 5% churn có thể là 23 lần về tốc độ truy vấn — một con số đủ lớn để biến một hệ thống "okay" thành "không thể chấp nhận được" chỉ sau vài tháng sử dụng.
Tác giả cũng ngỏ lời mời cộng đồng: nếu bạn tái lập thí nghiệm và thấy một điểm "nghẽn" khác, hãy chia sẻ — đó có thể là một khám phá thú vị hơn cả phát hiện ban đầu.
So sánh kích thước BRIN và B-tree


