Khi cơ sở dữ liệu vượt ngưỡng hàng chục triệu bản ghi, thao tác bảo trì như VACUUM, re-index, hoặc xóa dữ liệu cũ (soft delete/hard delete) sẽ trở thành cơn ác mộng. Một lệnh DELETE FROM logs WHERE created_at < '2025-01-01' trên bảng 100 triệu dòng có thể khóa bảng (table lock), làm phình file WAL và đánh sập cụm database production.
Đánh index lúc này không còn là chiếc đũa thần vì kích thước cây B-Tree đã vượt quá dung lượng RAM khả dụng, buộc ổ cứng phải đọc ghi liên tục.
Giải pháp nền tảng cho bài toán dữ liệu lớn là Table Partitioning (Phân vùng bảng): chia nhỏ một bảng khổng lồ về mặt vật lý thành nhiều bảng con độc lập, trong khi tầng ứng dụng vẫn thao tác như trên một bảng duy nhất.
1. Partition Pruning giúp Database đọc ít đi như thế nào?
Trong một bảng thông thường, câu lệnh truy vấn buộc phải quét qua các khối dữ liệu khổng lồ nằm rải rác trên đĩa cứng. Khi áp dụng kỹ thuật Declarative Partitioning:
- Dữ liệu được gom nhóm vật lý thành các phân vùng con (ví dụ: mỗi tháng hoặc mỗi quý một bảng riêng).
- Partition Pruning (Tỉa phân vùng): Khi nhận câu truy vấn kèm điều kiện lọc thời gian, bộ lập kế hoạch truy vấn (Query Planner) sẽ tự động loại bỏ 95% các bảng phân vùng không liên quan, chỉ đọc đúng phân vùng chứa dữ liệu đích.
Bảng cha ảo (orders)
[Nhận lệnh INSERT / SELECT]
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
[orders_2026_q1] [orders_2026_q2] [orders_2026_q3]
(Vùng dữ liệu 1) (Vùng dữ liệu 2) (Vùng dữ liệu 3)
Truy vấn tìm kiếm một đơn hàng trong tháng 10/2026 sẽ nhảy thẳng vào bảng orders_2026_10 mà không cần biết đến sự tồn tại của 90 triệu dòng của các năm trước.
2. Các chiến lược phân vùng phổ biến
- Phân vùng theo dải (Range Partitioning): Phổ biến nhất cho dữ liệu chuỗi thời gian (Time-series), log hệ thống, lịch sử giao dịch thanh toán hoặc đơn hàng (chia theo tuần, tháng, năm).
- Phân vùng theo danh sách (List Partitioning): Gom nhóm dữ liệu theo các giá trị rời rạc cụ thể (ví dụ: phân vùng theo mã quốc gia
country_code IN ('VN', 'TH', 'SG')). - Phân vùng theo hàm băm (Hash Partitioning): Dùng khi dữ liệu không có tính chu kỳ thời gian nhưng cần rải đều tải ghi dữ liệu lên nhiều đĩa cứng vật lý khác nhau theo khóa
user_id.
3. Triển khai Declarative Range Partitioning trên PostgreSQL
Dưới đây là kịch bản thiết lập bảng sự kiện audit_logs được phân vùng theo tháng:
Bước 1: Khởi tạo bảng cha định tuyến (Partitioned Table)
CREATE TABLE audit_logs (
id BIGSERIAL,
user_id UUID NOT NULL,
action VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
metadata JSONB,
PRIMARY KEY (id, created_at) -- Bắt buộc: Khóa chính phải chứa cột phân vùng
) PARTITION BY RANGE (created_at);
Bước 2: Tạo các bảng con đại diện cho từng tháng
-- Phân vùng cho tháng 10/2026
CREATE TABLE audit_logs_2026_10 PARTITION OF audit_logs
FOR VALUES FROM ('2026-10-01 00:00:00+07') TO ('2026-11-01 00:00:00+07');
-- Phân vùng cho tháng 11/2026
CREATE TABLE audit_logs_2026_11 PARTITION OF audit_logs
FOR VALUES FROM ('2026-11-01 00:00:00+07') TO ('2026-12-01 00:00:00+07');
-- Tạo index riêng trên từng bảng con (Index chỉ nằm gọn trong RAM)
CREATE INDEX idx_audit_logs_2026_10_user_id ON audit_logs_2026_10 (user_id);
Bước 3: Kiểm chứng cơ chế loại bỏ phân vùng bằng EXPLAIN
EXPLAIN SELECT * FROM audit_logs
WHERE created_at >= '2026-10-15' AND created_at < '2026-10-20';
Kết quả thực thi sẽ chỉ trỏ vào Seq Scan on audit_logs_2026_10 và hoàn toàn bỏ qua bảng audit_logs_2026_11 hay các phân vùng cũ.
4. Xóa hàng triệu bản ghi cũ trong 1 mili-giây: Sức mạnh của DROP TABLE
Bài toán đau đầu nhất của các hệ thống log hay bảng đơn hàng cũ là dọn dẹp dữ liệu quá hạn (Data Retention):
- Cách cũ (Rất chậm):
DELETE FROM audit_logs WHERE created_at < '2025-01-01';lệnh này quét từng dòng, sinh hàng triệu log giao dịch và làm phân mảnh bảng. - Cách dùng Partition (Tức thì):
-- Tách bảng phân vùng cũ ra khỏi bảng cha
ALTER TABLE audit_logs DETACH PARTITION audit_logs_2025_01;
-- Xóa bảng độc lập hoặc lưu trữ sang dạng file nén S3/Cold Storage
DROP TABLE audit_logs_2025_01;
Lệnh DROP TABLE giải phóng không gian ổ cứng ngay lập tức, không tốn tài nguyên CPU và không ảnh hưởng đến bất kỳ truy vấn đang chạy nào của bảng cha.
5. Ba cạm bẫy cần tránh khi cấu hình Partitioning
- Ràng buộc khóa chính (Primary Key Constraint): Trên PostgreSQL, mọi ràng buộc
UNIQUEhoặcPRIMARY KEYtrên bảng cha đều bắt buộc phải bao gồm cột phân vùng (created_at). Nếu ứng dụng yêu cầuidphải là duy nhất trên toàn hệ thống mà không kèm ngày tháng, bạn cần quản lý tính duy nhất này từ tầng ứng dụng (UUID v7/Snowflake ID) hoặc bảng tra cứu riêng. - Quên tạo phân vùng tương lai: Nếu một bản ghi có ngày tháng rơi vào khoảng thời gian chưa có bảng con nào tiếp nhận, lệnh
INSERTsẽ ném lỗi văng ứng dụng. Luôn có một bảng dự phòng mặc định (DEFAULT PARTITION) hoặc sử dụng extension tự động nhưpg_partman. - Phân vùng quá nhỏ (Over-partitioning): Đừng chia bảng theo từng ngày trừ khi mỗi ngày có hàng chục triệu dòng. Việc duy trì quá nhiều bảng con (hàng ngàn bảng) làm tăng thời gian lập kế hoạch truy vấn và tiêu tốn nhiều bộ nhớ chia sẻ của database.


