Dùng declarative range partitioning theo cột thời gian, mỗi tháng (hoặc mỗi tuần nếu lượng ghi lớn) một partition.
create table request_logs (
id bigserial,
created_at timestamptz not null,
payload jsonb
) partition by range (created_at);
create table request_logs_2026_08
partition of request_logs
for values from ('2026-08-01') to ('2026-09-01');Lợi ích chính không phải là truy vấn nhanh hơn, mà là vòng đời dữ liệu: xoá dữ liệu cũ bằng drop table request_logs_2026_01 chạy tức thì, không tạo dead tuple như DELETE hàng trăm triệu dòng.
Partition pruning — planner loại bỏ partition không liên quan — chỉ xảy ra khi:
- WHERE lọc trực tiếp trên partition key. Lọc theo cột khác thì phải quét mọi partition.
- Điều kiện so sánh được với biên partition. where created_at >= now() - interval '30 days' vẫn prune được ở thời điểm chạy (runtime pruning), nhưng EXPLAIN không kèm ANALYZE sẽ vẫn hiện tất cả partition.
- Bọc partition key trong hàm (where date(created_at) = ...) làm mất pruning.
Lưu ý khác: primary key phải chứa partition key, và unique constraint chỉ có hiệu lực trong phạm vi từng partition. Cần tạo sẵn partition tương lai bằng job định kỳ, nếu không INSERT sẽ lỗi vì không tìm được partition phù hợp.