Hai bài trước cho thấy phân vùng và partition pruning mạnh thế nào. Nhưng "mạnh" không đồng nghĩa "luôn nên dùng". Phân vùng là một quyết định thiết kế có đánh đổi thật, và dùng sai chỗ khiến truy vấn chậm hơn bảng thường. Bài này đo thật cả hai mặt của con dao — khi phân vùng thắng lớn và khi nó thua — để bạn quyết định có nên chia bảng của mình hay không.

Con dao hai lưỡi: đo cả hai mặt

Tôi dựng hai bảng cùng 3 triệu dòng: flat (bảng phẳng có index) và part (phân vùng theo cột k thành 50 vùng). Rồi chạy hai loại truy vấn khác nhau trên cả hai:

Ảnh chụp bảng kết quả đo thật nền tối phân vùng là con dao hai lưỡi bảng 3 triệu dòng 50 vùng flat bảng phẳng có index part phân vùng theo k thành 50 vùng cùng dữ liệu PostgreSQL 16, truy vấn không lọc theo khóa phân vùng point lookup theo id part thua SELECT payload FROM WHERE id bằng 1234567 id không phải khóa phân vùng flat Planning 0,21 ms Execution 0,025 ms 1 Index Scan part Planning 1,50 ms Execution 0,519 ms 50 Index Scan dò mọi vùng part chậm hơn khoảng 20 lần execution cộng 7 lần planning phải dò index của cả 50 vùng, truy vấn lọc đúng khóa phân vùng k bằng 7 part thắng SELECT count từ WHERE k bằng 7 k là khóa phân vùng flat k bằng 7 Execution 15,7 ms index scan nhưng 6000 dòng rải khắp bảng part k bằng 7 Execution 3,06 ms chỉ quét 1 partition pruning part nhanh hơn khoảng 5 lần khi truy vấn khớp khóa phân vùng, bảng truy vấn WHERE id bằng X không khóa flat 0,025 ms part 0,519 ms flat thắng part chậm 20x WHERE k bằng 7 đúng khóa flat 15,7 ms part 3,06 ms part nhanh 5x Planning Time flat 0,21 ms part 1,50 ms flat part đắt hơn, kết luận phân vùng không phải luôn nhanh hơn nó thắng lớn khi truy vấn khớp khóa phân vùng pruning thua khi không khớp dò mọi vùng cộng planning đắt chỉ phân vùng bảng rất lớn khi mẫu truy vấn khớp khóa hoặc cần archival rẻ

Hình 2: Truy vấn point lookup theo id (không phải khóa phân vùng): flat 0,025 ms với 1 Index Scan, part 0,519 ms với 50 Index Scan (dò mọi vùng) — chậm gấp ~20 lần, planning đắt gấp 7 lần. Truy vấn theo khóa k=7: part 3,06 ms thắng flat 15,7 ms nhờ pruning.

  • Truy vấn KHÔNG lọc theo khóa phân vùng (WHERE id=X, mà id không phải khóa): flat dùng một Index Scan, 0,025 ms; part phải dò index của cả 50 vùng (50 Index Scan trong Append), 0,519 ms — chậm gấp ~20 lần về thực thi, cộng planning 1,50 ms (đắt gấp 7 lần 0,21 ms của flat). Vì id rải khắp mọi partition, PostgreSQL không prune được và phải tìm ở từng vùng.
  • Truy vấn lọc ĐÚNG khóa phân vùng (WHERE k=7): part chỉ quét một partition (pruning), 3,06 ms; flat phải index scan rồi lấy 6000 dòng rải khắp bảng, 15,7 ms — part nhanh gấp ~5 lần.

Cùng một cặp bảng, cùng dữ liệu — phân vùng thắng đậm hay thua đậm hoàn toàn phụ thuộc truy vấn có khớp khóa phân vùng hay không.

Khi nào NÊN phân vùng

Phân vùng đáng làm khi hội đủ (không phải chỉ một) các dấu hiệu:

  • Bảng RẤT lớn — hàng trăm triệu dòng hoặc hàng trăm GB trở lên. Với bảng vài triệu dòng, một index tốt trên bảng phẳng thường nhanh hơn và đơn giản hơn.
  • Truy vấn chính lọc theo một khóa tự nhiên (thời gian, tenant, khu vực) → được partition pruning (như đo ở bài trước, nhanh 5-10 lần).
  • Cần archival rẻ — xóa dữ liệu cũ bằng DROP TABLE part_2023 (tức thì, không sinh dead tuple) thay vì DELETE hàng triệu dòng (chậm, cần VACUUM dọn sau).
  • Bảo trì theo vùng — VACUUM/REINDEX/tạo index chạy trên từng partition nhỏ thay vì cả bảng khổng lồ một lần.

Ảnh chụp đoạn mã SQL nền tối minh hoạ khi nào nên phân vùng và khi nào chỉ thêm phức tạp, nên phân vùng khi có đủ các dấu hiệu bảng rất lớn hàng trăm triệu dòng hàng trăm GB trở lên truy vấn chính lọc theo một khóa tự nhiên thời gian tenant khu vực được partition pruning nhanh gấp 5-10 lần cần archival rẻ xóa dữ liệu cũ bằng DROP TABLE part cũ tức thì thay vì DELETE hàng triệu dòng chậm sinh dead tuple cần VACUUM bảo trì theo vùng VACUUM REINDEX CREATE INDEX từng partition nhỏ, không nên phân vùng khi bảng nhỏ vừa vài triệu dòng index thường tốt hơn phân vùng chỉ thêm overhead truy vấn lọc theo nhiều cột khác nhau point lookup theo id email không prune được phải dò mọi partition chậm hơn bảng phẳng không có khóa phân vùng tự nhiên khớp với cách truy vấn của bạn, đo để quyết định so bảng phẳng flat vs phân vùng part cùng dữ liệu trên đúng truy vấn của bạn EXPLAIN ANALYZE WHERE khoa_phan_vung bằng X part thắng nếu prune tốt EXPLAIN ANALYZE WHERE cot_khac bằng Y part thua nếu không prune xem cả Planning Time tăng theo số partition lẫn Execution Time

Hình 1: Danh sách quyết định — NÊN phân vùng khi bảng rất lớn + truy vấn khớp khóa + cần archival rẻ; KHÔNG nên khi bảng nhỏ, truy vấn lọc theo nhiều cột khác, hoặc không có khóa phân vùng tự nhiên. Luôn đo cả Planning lẫn Execution Time trên truy vấn thật.

Khi nào KHÔNG nên

  • Bảng nhỏ/vừa (vài triệu dòng): index trên bảng phẳng thường tốt hơn; phân vùng chỉ thêm overhead quản lý và planning.
  • Truy vấn lọc theo nhiều cột khác nhau (point lookup theo id, email, v.v.): như đo ở trên, không prune được thì phải dò mọi partition — chậm hơn bảng phẳng.
  • Không có khóa phân vùng tự nhiên khớp với cách truy vấn của bạn: chọn khóa mà truy vấn hiếm khi lọc theo thì mất hết lợi ích pruning.

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

Cách quyết định đúng là đo, không phải nghe theo quy tắc chung. Dựng một bảng phẳng và một bảng phân vùng cùng dữ liệu, rồi chạy EXPLAIN (ANALYZE) trên chính những truy vấn thật của ứng dụng bạn — cả truy vấn khớp khóa lẫn không khớp. Xem cả Planning Time (tăng theo số partition) lẫn Execution Time. Nếu phần lớn truy vấn không khớp khóa phân vùng, đừng phân vùng.

Có thể index nhiều cột trên bảng phân vùng, nhưng không cứu được pruning. Bạn có thể tạo index trên id cho mọi partition (như tôi làm ở trên), nhưng truy vấn theo id vẫn phải dò từng index của từng vùng — index không thay thế pruning. Chỉ khóa phân vùng mới cho pruning.

Phân vùng thêm phức tạp vận hành thật. Cần chiến lược tạo partition tương lai, DEFAULT partition làm lưới an toàn, ràng buộc unique phải chứa khóa phân vùng, và một số thao tác (ALTER, khóa ngoại tới bảng phân vùng) có giới hạn. Đừng trả cái giá phức tạp này cho một bảng chưa đủ lớn để cần.

Ba ý mang về

  1. Phân vùng là con dao hai lưỡi: đo thật cùng 3 triệu dòng, truy vấn khớp khóa phân vùng nhanh gấp ~5 lần (pruning), nhưng truy vấn point lookup theo cột khác chậm gấp ~20 lần (dò index của cả 50 vùng) cộng planning đắt gấp 7 lần.
  2. Chỉ phân vùng khi hội đủ dấu hiệu: bảng rất lớn (hàng trăm triệu dòng), truy vấn chính khớp một khóa tự nhiên, cần archival rẻ (DROP partition) — với bảng vài triệu dòng, index trên bảng phẳng thường tốt hơn.
  3. Đo trước khi chia: dựng bảng phẳng vs phân vùng cùng dữ liệu, chạy EXPLAIN ANALYZE trên chính truy vấn thật của bạn (cả khớp và không khớp khóa), xem cả Planning lẫn Execution Time — index không thay được pruning, và phức tạp vận hành là có thật.

Phần sau ta xét một lợi ích nâng cao của phân vùng khi kết hợp bảng: Phần sau mổ xẻ partition-wise join và aggregate — cách PostgreSQL join/gom theo từng cặp partition tương ứng để tăng tốc và song song hóa.