Bài trước dựng ba kiểu phân vùng và hé lộ lợi ích cốt lõi. Bài này đo sâu chính lợi ích đó: partition pruning — khả năng của PostgreSQL bỏ qua hoàn toàn các vùng không thể chứa dữ liệu khớp truy vấn. Đây không phải một tính năng phụ; nó là lý do chính để phân vùng. Bài này đo thật pruning nhanh thế nào, và một điều kiện quan trọng: nó chỉ giúp khi bạn lọc theo khóa phân vùng.

Pruning làm gì

Tôi dựng bảng sk 5 triệu dòng, phân vùng RANGE theo cột thang thành 10 vùng (sk_0..sk_9, mỗi vùng một dải giá trị). Khi truy vấn lọc theo thang, PostgreSQL biết dữ liệu chỉ có thể nằm ở đúng một vùng, và bỏ qua 9 vùng còn lại:

SELECT count(*) FROM sk WHERE thang=25;   -- thang=25 chỉ có thể ở sk_2 (dải 20-30)

Pruning có hai dạng, tùy điều kiện lọc là hằng số hay tham số:

  • Plan-time pruning: khi khóa phân vùng so với hằng số biết lúc lập kế hoạch (WHERE thang=25), planner loại các vùng thừa ngay khi lập kế hoạch — EXPLAIN chỉ hiện đúng một vùng.
  • Run-time pruning: khi khóa so với tham số ($1) hoặc kết quả subquery chưa biết lúc lập kế hoạch. Generic plan giữ cả Append của mọi vùng, nhưng lúc chạy (biết giá trị thật) executor loại các vùng thừa — EXPLAIN hiện Subplans Removed: N.

Ảnh chụp đoạn mã SQL nền tối minh hoạ partition pruning bỏ qua các vùng không thể chứa dữ liệu khớp, ý tưởng chỉ quét partition có thể chứa dữ liệu khớp bảng sk phân vùng theo thang 10 vùng sk_0 đến sk_9 mỗi vùng một dải SELECT count từ sk WHERE thang bằng 25 thang 25 chỉ có thể nằm ở sk_2 dải 20-30 planner bỏ 9 vùng còn lại chỉ quét sk_2 nhanh gấp khoảng 10 lần chỉ đọc 1 trên 10 dữ liệu, plan-time pruning khi khóa phân vùng so với hằng số EXPLAIN SELECT WHERE thang bằng 25 hằng số biết lúc lập kế hoạch EXPLAIN chỉ hiện một vùng sk_2 9 vùng kia bị loại ngay khi lập kế hoạch, run-time pruning khi khóa so với tham số hoặc kết quả subquery PREPARE p int AS SELECT WHERE thang bằng 1 tham số chưa biết lúc lập kế hoạch generic plan giữ cả Append của mọi vùng nhưng lúc chạy biết 1 executor loại các vùng thừa EXPLAIN hiện Subplans Removed 9, điều kiện phải lọc theo khóa phân vùng SELECT WHERE payload LIKE abc phần trăm không lọc theo thang không prune được phải quét tất cả 10 vùng pruning chỉ giúp truy vấn lọc theo hoặc join theo khóa phân vùng đây là lý do chính để phân vùng

Hình 1: Partition pruning bỏ qua các vùng không thể chứa dữ liệu khớp. Plan-time pruning (khóa so với hằng số) loại vùng lúc lập kế hoạch; run-time pruning (khóa so với tham số) loại vùng lúc chạy (Subplans Removed). Chỉ hoạt động khi lọc theo khóa phân vùng.

Đo thật: nhanh gấp 10 lần

Ảnh chụp bảng kết quả đo thật nền tối pruning quét 1 trên 10 vùng nhanh gấp khoảng 10 lần bảng sk 5 triệu dòng phân vùng RANGE theo thang thành 10 vùng PostgreSQL 16, lọc theo khóa phân vùng thang bằng 25 chỉ quét sk_2 EXPLAIN ANALYZE SELECT count từ sk WHERE thang bằng 25 Seq Scan on sk_2 rows 50000 9 vùng kia bị loại lúc lập kế hoạch Buffers shared hit 1624 read 3049 khoảng 4700 trang Execution Time 19,8 ms, không lọc theo khóa payload LIKE quét cả 10 vùng EXPLAIN ANALYZE SELECT count từ sk WHERE payload LIKE abc phần trăm Append 10 Seq Scan on sk_0 đến sk_9 quét toàn bộ 5 triệu dòng Execution Time 207 ms chậm hơn khoảng 10 lần vì không prune được, run-time pruning generic plan tham số Subplans Removed SET plan_cache_mode force_generic_plan PREPARE p2 int AS SELECT count từ sk WHERE thang bằng 1 EXPLAIN ANALYZE EXECUTE p2 25 Append cost rows 500000 Subplans Removed 9 executor loại 9 vùng lúc chạy Seq Scan on sk_2, bảng truy vấn WHERE thang bằng 25 khóa phân vùng số vùng quét 1 trên 10 thời gian 19,8 ms WHERE payload LIKE không khóa 10 trên 10 207 ms pruning là lý do chính để phân vùng nhưng chỉ giúp truy vấn lọc theo khóa phân vùng

Hình 2: Lọc theo khóa thang=25 chỉ quét sk_2 (~4.700 trang, 19,8 ms). Không lọc theo khóa (payload LIKE) phải quét cả 10 vùng (207 ms) — chậm gấp ~10 lần. Run-time pruning với tham số hiện Subplans Removed: 9.

  • Lọc theo khóa phân vùng (WHERE thang=25): EXPLAIN chỉ hiện Seq Scan on sk_2 — 9 vùng kia bị loại. Đọc ~4.700 trang, 19,8 ms.
  • Không lọc theo khóa (WHERE payload LIKE 'abc%'): planner không biết dữ liệu ở vùng nào, phải quét cả 10 vùng (Append 10 Seq Scan), toàn bộ 5 triệu dòng — 207 ms, chậm gấp ~10 lần.
  • Run-time pruning (generic plan với tham số): EXPLAIN ANALYZE EXECUTE p2(25) hiện Subplans Removed: 9 — executor loại 9 vùng lúc chạy, chỉ quét sk_2.

Con số ~10 lần này chính là tỉ lệ số vùng: chia thành 10 vùng, truy vấn lọc đúng khóa chỉ đọc 1/10 dữ liệu. Chia càng nhiều vùng, pruning càng thắng lớn — với điều kiện truy vấn lọc theo khóa phân vùng.

Điều kiện: phải lọc theo khóa phân vùng

Đây là điểm quyết định thành bại của phân vùng. Pruning chỉ giúp truy vấn có điều kiện trên khóa phân vùng (hoặc join theo khóa đó). Truy vấn lọc theo cột khác (như payload ở trên) không prune được gì và phải quét toàn bộ — thậm chí chậm hơn bảng không phân vùng (vì phải gộp kết quả từ nhiều vùng qua Append).

Hệ quả thiết kế: chọn khóa phân vùng theo cách truy vấn của bạn lọc dữ liệu. Nếu ứng dụng phần lớn truy vấn theo khoảng thời gian → phân vùng RANGE theo ngày. Nếu truy vấn theo tenant/khu vực → LIST theo cột đó. Chọn sai khóa (khóa mà truy vấn hiếm khi lọc theo) thì phân vùng chỉ thêm overhead mà không được pruning.

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

Quá nhiều partition làm chậm lập kế hoạch. Mỗi partition là một bảng con planner phải xét. Vài chục đến vài trăm partition thường ổn; hàng nghìn partition khiến thời gian lập kế hoạch tăng đáng kể (dù pruning vẫn chạy). Cân bằng giữa số vùng (pruning tốt hơn) và chi phí quản lý/lập kế hoạch. PostgreSQL các phiên bản mới cải thiện nhiều, nhưng đừng chia nghìn vùng vô tội vạ.

Pruning cần thống kê và điều kiện "sạch". Điều kiện phải để planner suy ra được vùng: WHERE thang=25 prune tốt, nhưng WHERE thang::text='25' (bọc hàm) hay WHERE thang+0=25 có thể phá pruning. Giữ điều kiện trên khóa phân vùng ở dạng trực tiếp (giống nguyên tắc sargability của index).

Run-time pruning cần EXPLAIN ANALYZE để thấy. Với truy vấn tham số/prepared, EXPLAIN thường (không ANALYZE) hiện cả Append của mọi vùng — dễ tưởng nhầm không prune. Chỉ EXPLAIN ANALYZE (chạy thật) mới hiện Subplans Removed. Đừng kết luận pruning hỏng chỉ vì nhìn EXPLAIN tĩnh.

Ba ý mang về

  1. Partition pruning là lý do chính để phân vùng: đo thật, lọc theo khóa phân vùng chỉ quét 1/10 vùng (19,8 ms) so với quét cả 10 vùng khi không lọc theo khóa (207 ms) — nhanh gấp ~10 lần, đúng bằng tỉ lệ số vùng.
  2. Có hai dạng pruning: plan-time (khóa so với hằng số, loại vùng lúc lập kế hoạch, EXPLAIN chỉ hiện vùng liên quan) và run-time (khóa so với tham số/subquery, loại lúc chạy, hiện Subplans Removed — chỉ thấy qua EXPLAIN ANALYZE).
  3. Pruning chỉ giúp truy vấn lọc theo khóa phân vùng: chọn khóa theo cách ứng dụng lọc dữ liệu; giữ điều kiện "sạch" (không bọc hàm quanh khóa); và đừng chia quá nhiều vùng kẻo chậm lập kế hoạch.

Phần sau ta lùi lại một bước để quyết định thực dụng: Phần sau bàn khi nào thật sự nên phân vùng — ngưỡng kích thước bảng, dấu hiệu cần chia, và khi nào phân vùng chỉ thêm phức tạp mà không đáng.