Phân vùng không chỉ giúp pruning. Khi bạn join hoặc gom trên bảng phân vùng, PostgreSQL có thêm hai tối ưu nâng cao: partition-wise join và partition-wise aggregate — xử lý theo từng cặp vùng tương ứng thay vì gộp cả bảng. Điều bất ngờ: cả hai tắt mặc định. Bài này đo thật chúng làm gì và vì sao mặc định tắt.

Ý tưởng: join theo từng cặp vùng

Nếu hai bảng phân vùng giống hệt nhau (cùng khóa phân vùng, cùng ranh giới vùng — gọi là co-partitioned), thì join theo khóa đó có một cách tối ưu: thay vì gộp toàn bộ hai bảng rồi join một lần, PostgreSQL join từng cặp vùng tương ứng (o_0 ⋈ c_0, o_1 ⋈ c_1, ...) rồi ghép kết quả.

SET enable_partitionwise_join = on;       -- mặc định OFF
SET enable_partitionwise_aggregate = on;  -- mặc định OFF
SELECT count(*) FROM o JOIN c ON o.k = c.k;   -- k là khóa phân vùng của cả hai

Ảnh chụp đoạn mã SQL nền tối minh hoạ partition-wise join và aggregate xử lý theo từng cặp vùng, ý tưởng nếu hai bảng phân vùng giống nhau join theo từng cặp vùng bình thường off Hash Join Append o_0 đến o_7 Hash Append c_0 đến c_7 gộp cả bảng rồi join partition-wise on Append Join o_0 c_0 mỗi cặp vùng một join nhỏ Join o_1 c_1 Join o_7 c_7 điều kiện hai bảng cùng khóa phân vùng cùng ranh giới vùng co-partitioned, cả hai tắt mặc định vì tăng thời gian lập kế hoạch SHOW enable_partitionwise_join off mặc định SHOW enable_partitionwise_aggregate off mặc định SET enable_partitionwise_join on bật khi có lợi SET enable_partitionwise_aggregate on, vì sao có lợi 1 hash table nhỏ hơn mỗi cặp vùng có bảng băm riêng nhỏ ít tràn đĩa hơn khi work_mem hạn chế đo batches 32 xuống 4 2 song song hóa mỗi join aggregate của một vùng chạy trên worker riêng 3 aggregate GROUP BY khóa phân vùng gom từng vùng rồi ghép không cần gom lại toàn cục nhanh hơn đo 268ms xuống 214ms, partition-wise aggregate SELECT k count từ o GROUP BY k k là khóa phân vùng on Append HashAggregate mỗi vùng off HashAggregate toàn bộ

Hình 1: Partition-wise join thay một Hash Join lớn (gộp cả hai bảng) bằng một Append của nhiều join nhỏ theo từng cặp vùng. Yêu cầu hai bảng co-partitioned. Cả hai tối ưu tắt mặc định vì tăng thời gian lập kế hoạch.

Đo thật: cấu trúc plan và lợi ích

Tôi dựng hai bảng o và c cùng phân vùng theo k thành 8 vùng, rồi join theo k:

Ảnh chụp bảng kết quả đo thật nền tối partition-wise đổi cấu trúc plan giảm tràn đĩa 2 bảng co-partitioned theo k 8 vùng PostgreSQL 16 đơn luồng đã cache, cấu trúc plan JOIN thay đổi OFF Hash Join Append o_0 đến o_7 gộp cả bảng rồi join một lần Hash Append c_0 đến c_7 ON Append Hash Join o_0 c_0 rows 250000 8 join nhỏ theo từng cặp vùng Hash Join o_1 c_1 rows 250000 8 cặp, lợi ích bộ nhớ work_mem nhỏ hash table per-partition ít tràn đĩa hơn work_mem 1MB join có tràn đĩa partitionwise off Batches 32 hash cả bảng tràn nhiều partitionwise on Batches 4 hash từng vùng nhỏ tràn ít, partition-wise aggregate GROUP BY khóa phân vùng SELECT k count từ o GROUP BY k agg off HashAggregate toàn bộ Execution 268 ms agg on Append HashAggregate mỗi vùng Execution 214 ms khoảng 20 phần trăm nhanh hơn, bảng thao tác join batches work_mem 1MB OFF 32 batches ON 4 batches aggregate GROUP BY k 268 ms 214 ms, lợi ích đơn luồng ở đây khiêm tốn win lớn nhất là song song hóa mỗi vùng một worker cần đo trên tải thật

Hình 2: OFF: một Hash Join gộp cả hai bảng. ON: Append của 8 Hash Join theo từng cặp vùng (250k dòng mỗi cặp). Với work_mem=1MB, partition-wise giảm số batch tràn đĩa từ 32 xuống 4 (hash table mỗi vùng nhỏ hơn). Partition-wise aggregate: 268 ms → 214 ms (~20% nhanh hơn).

  • Cấu trúc plan thay đổi: OFF cho một Hash Join lớn (gộp Append(o_0..o_7) rồi join với hash của cả c); ON cho một Append của 8 Hash Join nhỏ, mỗi cái xử lý một cặp vùng (250k dòng).
  • Lợi ích bộ nhớ: với work_mem=1MB (buộc hash tràn đĩa), partition-wise=OFF cần 32 batch (hash cả bảng lớn, tràn nhiều), ON chỉ 4 batch (hash table mỗi vùng nhỏ, tràn ít hơn). Đây là lợi ích thật khi bộ nhớ hạn chế.
  • Partition-wise aggregate: GROUP BY k (khóa phân vùng) với OFF là một HashAggregate toàn bộ (268 ms); ON là Append của HashAggregate từng vùng (214 ms) — nhanh hơn ~20%.

Trung thực: trên lab đơn luồng đã cache này, lợi ích khiêm tốn (aggregate ~20%, join gần như hòa về thời gian dù giảm batch). Win lớn nhất của partition-wise là song song hóa — mỗi join/aggregate của một vùng chạy trên một worker riêng, cho tốc độ tăng gần tuyến tính theo số core khi tải đủ lớn. Điều đó cần đo trên tải thật với parallelism bật.

Vì sao tắt mặc định

Cả enable_partitionwise_join và enable_partitionwise_aggregate tắt mặc định vì chúng làm tăng thời gian lập kế hoạch: planner phải xét thêm phương án per-partition cho mỗi cặp vùng, và với bảng nhiều vùng, chi phí lập kế hoạch tăng đáng kể. PostgreSQL chọn mặc định an toàn (tắt) và để bạn bật khi đã xác định có lợi.

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

Chỉ hoạt động khi hai bảng co-partitioned. Partition-wise join yêu cầu hai bảng có cùng khóa phân vùng và cùng ranh giới vùng. Nếu chúng phân vùng khác nhau (khác số vùng, khác dải), PostgreSQL không dùng được và rơi về join thường. Thiết kế co-partitioning là quyết định phải làm từ đầu (ví dụ cùng phân vùng theo tenant_id).

Bật làm tăng thời gian lập kế hoạch — cân nhắc phạm vi. Vì mặc định tắt vì lý do này, đừng bật toàn cục vô tội vạ. Bật ở mức session/truy vấn cho các truy vấn analytics nặng trên bảng phân vùng lớn, nơi lợi ích thực thi (đặc biệt song song) vượt xa chi phí lập kế hoạch. Với OLTP truy vấn ngắn, chi phí lập kế hoạch thêm có thể không đáng.

Lợi ích thật đến từ parallelism và bộ nhớ, không phải luôn từ thời gian đơn luồng. Như đo ở trên, đơn luồng có thể gần như hòa. Đừng bật rồi kỳ vọng nhanh gấp bội ngay; hãy đo với parallelism bật, work_mem thực tế, và tải giống production. Lợi ích rõ nhất ở join/aggregate lớn trên nhiều vùng với nhiều core.

Ba ý mang về

  1. Partition-wise join/aggregate xử lý theo từng cặp vùng tương ứng thay vì gộp cả bảng: đo thật, plan đổi từ một Hash Join lớn thành Append của 8 join nhỏ (mỗi cặp vùng), yêu cầu hai bảng co-partitioned (cùng khóa và ranh giới vùng).
  2. Lợi ích là bộ nhớ nhỏ hơn và song song hóa: đo thật, partition-wise join giảm số batch tràn đĩa từ 32 xuống 4 (hash table mỗi vùng nhỏ hơn), partition-wise aggregate nhanh hơn ~20% — nhưng win lớn nhất là mỗi vùng một worker (cần parallelism và tải thật).
  3. Cả hai tắt mặc định vì tăng thời gian lập kế hoạch: bật enable_partitionwise_join/enable_partitionwise_aggregate ở mức session cho analytics nặng trên bảng phân vùng lớn, không bật toàn cục vô tội vạ — và đo với parallelism/work_mem thực tế.

Phần sau ta xét khía cạnh vận hành thực dụng của phân vùng theo thời gian: Phần sau mổ xẻ quản lý partition theo thời gian — tạo partition tương lai tự động, ATTACH/DETACH, và dọn dữ liệu cũ bằng DROP partition thay vì DELETE.