Xử lý bảng dữ liệu lớn trong 5 phút với pg_partman
Khi bảng dữ liệu chạm mốc 500GB hoặc hàng tỷ record, những câu lệnh SELECT đơn giản bắt đầu chậm dần đều. Việc xóa dữ liệu cũ bằng DELETE cũng trở thành cực hình vì gây khóa bảng và phình file log (WAL). pg_partman là công cụ giúp bạn chia nhỏ các bảng khổng lồ này thành các phân vùng (partitions) nhỏ hơn dựa trên thời gian hoặc ID một cách hoàn toàn tự động.
Giả sử bạn đã cài extension trên Ubuntu, hãy triển khai nhanh theo các bước sau:
-- Gom nhóm partman vào một schema riêng cho sạch sẽ
CREATE SCHEMA partman;
CREATE EXTENSION pg_partman SCHEMA partman;
-- Khai báo bảng chính (parent table) phân vùng theo thời gian
CREATE TABLE public.user_logs (
id bigserial,
log_date timestamptz not null,
data text,
PRIMARY KEY (id, log_date)
) PARTITION BY RANGE (log_date);
-- Cấu hình partman tự động tạo phân vùng theo ngày
SELECT partman.create_parent(
p_parent_table := 'public.user_logs',
p_control := 'log_date',
p_type := 'native',
p_interval := 'daily',
p_premake := 4
);
Ngay lập tức, hệ thống sẽ tạo sẵn 4 bảng con cho 4 ngày kế tiếp. Bạn không còn phải thấp thỏm lo sợ ứng dụng crash vì quên tạo bảng con khi bước sang ngày mới.
Tại sao bạn nên bỏ cách làm thủ công?
Trước đây, mình thường phải dùng script Python hoặc Cronjob để ALTER TABLE tạo partition mới trên MySQL. PostgreSQL từ bản 10 đã có Native Partitioning rất mạnh, nhưng nó mới chỉ dừng lại ở mức cung cấp khung sườn. Việc tính toán thời điểm tạo bảng con, đặt tên sao cho chuẩn, hay dọn dẹp dữ liệu cũ (Retention) vẫn buộc bạn phải tự tay xử lý.
Đã có trường hợp hệ thống mình quản lý bị treo vào đúng 0h00 sáng mùng 1. Nguyên nhân chỉ vì script tạo partition gặp lỗi logic khiến dữ liệu mới không có chỗ chứa. pg_partman giúp bạn gạt bỏ những rủi ro ngớ ngẩn đó bằng cơ chế tự động hóa thông minh.
Những tính năng thực dụng nhất:
- Chuẩn bị sẵn phân vùng: Thông số
p_premakeđảm bảo các bảng con luôn có sẵn trước khi dữ liệu thực tế đổ về. - Tự động dọn dẹp: Bạn có thể thiết lập để hệ thống tự hủy (drop) các bảng log cũ hơn 3 tháng chỉ bằng một dòng cấu hình.
- Vận hành ngầm: Chế độ Background Worker chạy trực tiếp trong nhân Postgres, không phụ thuộc vào công cụ bên ngoài.
Cài đặt thực tế
1. Cài đặt từ Repository
Dưới đây là lệnh cài đặt cho PostgreSQL 15 trên môi trường Ubuntu:
sudo apt-get update
sudo apt-get install postgresql-15-partman
2. Kích hoạt Background Worker
Để pg_partman tự chạy mà không cần tác động thủ công, bạn cần sửa file postgresql.conf. Hãy tìm và thêm các dòng sau:
shared_preload_libraries = 'pg_partman_bgw'
pg_partman_bgw.interval = 3600 -- Kiểm tra mỗi giờ một lần
pg_partman_bgw.role = 'postgres'
pg_partman_bgw.dbname = 'ten_database_cua_ban'
Sau khi lưu, hãy restart lại dịch vụ PostgreSQL để kích hoạt bộ máy tự động này.
Quản lý vòng đời dữ liệu (Data Retention)
Xóa 10 triệu dòng bằng DELETE có thể mất vài phút và làm nghẽn DB. Nhưng DROP một partition chứa 10 triệu dòng chỉ mất chưa đầy 1 giây. Đây chính là điểm ăn tiền nhất của pg_partman.
UPDATE partman.part_config
SET retention = '3 months',
retention_keep_table = false
WHERE parent_table = 'public.user_logs';
Với lệnh trên, cứ mỗi giờ, hệ thống sẽ quét các bảng con. Bảng nào chứa dữ liệu cũ hơn 90 ngày sẽ bị xóa sổ ngay lập tức, giải phóng dung lượng ổ cứng mà không gây overhead cho hệ thống.
Cách chuyển đổi bảng thường sang phân vùng
Đa số chúng ta chỉ tìm đến Partitioning khi bảng hiện tại đã quá lớn, có khi lên tới 50-100 triệu record. Đừng hoảng loạn, pg_partman có sẵn quy trình migration an toàn.
- Tạo một bảng mới với cấu trúc
PARTITION BYy hệt bảng cũ. - Sử dụng hàm
partition_data_procđể chuyển dữ liệu theo từng đợt (batch).
-- Di chuyển dữ liệu theo từng đợt 10.000 dòng để tránh treo DB
CALL partman.partition_data_proc('public.user_logs', p_batch := 10000);
Kinh nghiệm “xương máu” khi triển khai
Sau nhiều năm vận hành các hệ thống tài chính và log tập trung, mình rút ra vài lưu ý quan trọng:
Đừng chia phân vùng quá vụn
Nhiều người thích chia theo giờ (hourly) cho chi tiết. Tuy nhiên, nếu dữ liệu không đạt mức vài tỷ dòng/ngày, việc chia theo ngày (daily) là tối ưu nhất. Quá nhiều partition sẽ khiến Query Planner của Postgres tốn thêm thời gian tính toán, làm chậm các câu lệnh truy vấn tổng hợp.
Bắt buộc có Index trên cột phân vùng
PostgreSQL có tính năng Partition Pruning để chỉ quét các bảng con cần thiết. Nhưng nếu bạn quên đánh Index cho cột log_date, hệ thống vẫn phải quét toàn bộ (Full Scan) trên các bảng con đó. Lúc này, tốc độ truy vấn có thể vọt từ 50ms lên 5s là chuyện bình thường.
Lưu ý về ràng buộc Unique
Đây là điểm yếu của Partitioning. Một Unique Index bắt buộc phải chứa cột phân vùng. Nếu bạn muốn cột id là duy nhất trên toàn bộ các partition, bạn phải khai báo nó dưới dạng UNIQUE(id, log_date).
Thay vì chuyển sang các giải pháp NoSQL phức tạp, việc kết hợp PostgreSQL với pg_partman là lựa chọn cực kỳ kinh tế. Nó vừa đảm bảo tính toàn vẹn dữ liệu (ACID), vừa giúp hệ thống của bạn mở rộng (scale-up) một cách chuyên nghiệp.

