Phân vùng theo thời gian (ngày/tháng) không phải dựng xong là quên. Nó cần một vòng đời vận hành: tạo partition mới trước khi dữ liệu tới, dọn partition cũ đi, và có lưới an toàn cho dữ liệu ngoài dải. Bài này đo thật các thao tác quản lý — đặc biệt lợi ích lớn nhất của phân vùng theo thời gian: dọn dữ liệu cũ bằng DROP thay vì DELETE.

Tạo partition tương lai và DEFAULT partition

Với bảng phân vùng theo tháng, bạn phải tạo partition cho các tháng sắp tới trước khi dữ liệu của tháng đó tới. Quên tạo thì INSERT dữ liệu tháng mới sẽ lỗi — không partition nào nhận. Giải pháp:

-- Tạo trước partition tương lai (thủ công, cron job, hoặc pg_partman tự động):
CREATE TABLE log_2025_12 PARTITION OF log_pt FOR VALUES FROM ('2025-12-01') TO ('2026-01-01');
-- Lưới an toàn: DEFAULT partition nhận mọi dòng không khớp vùng nào:
CREATE TABLE log_default PARTITION OF log_pt DEFAULT;

DEFAULT partition bắt các dòng ngoài dải đã khai. Đo thật: chèn một dòng ngày 2026-05-15 (ngoài mọi vùng tháng đã tạo) — nó rơi vào log_default thay vì báo lỗi. Nhưng dữ liệu kẹt ở default là dấu hiệu thiếu partition đúng; cần giám sát và dọn.

Ảnh chụp đoạn mã SQL nền tối minh hoạ quản lý partition theo thời gian vòng đời của bảng phân vùng ngày, tạo partition tương lai trước khi dữ liệu tới CREATE TABLE log_2025_12 PARTITION OF log_pt FOR VALUES FROM 2025-12-01 TO 2026-01-01 quên tạo INSERT dữ liệu tháng mới sẽ lỗi không vùng nào nhận tự động hóa pg_partman hoặc cron job tạo trước vài tháng, DEFAULT partition lưới an toàn cho dòng ngoài dải CREATE TABLE log_default PARTITION OF log_pt DEFAULT dòng không khớp vùng nào rơi vào đây thay vì báo lỗi nhớ dọn định kỳ dữ liệu kẹt ở default nghĩa là thiếu partition đúng, dọn dữ liệu cũ DROP partition tức thì thay vì DELETE chậm DROP TABLE log_2025_10 xóa cả tháng dữ liệu tức thì 0 dead tuple so với DELETE FROM WHERE ngay nhỏ hơn 2025-11-01 chậm hơn sinh hàng trăm nghìn dead tuple cần VACUUM dọn sau, DETACH ATTACH tách gắn partition ALTER TABLE log_pt DETACH PARTITION log_2025_11 tách thành bảng độc lập dữ liệu còn nguyên để archive sang lưu trữ lạnh rồi DROP hoặc pg_dump riêng DETACH CONCURRENTLY PG14 không khóa dài ALTER TABLE log_pt ATTACH PARTITION bang_co_san FOR VALUES FROM 2026-01-01 TO 2026-02-01 gắn bảng dựng sẵn

Hình 1: Vòng đời partition theo thời gian — tạo partition tương lai trước, DEFAULT partition làm lưới an toàn, dọn dữ liệu cũ bằng DROP (thay DELETE), DETACH/ATTACH để tách/gắn partition.

Đo thật: DROP partition thay vì DELETE

Đây là lợi ích lớn nhất của phân vùng theo thời gian. Để dọn dữ liệu cũ (ví dụ xóa toàn bộ log tháng 10), so hai cách trên ~303 nghìn dòng:

Ảnh chụp bảng kết quả đo thật nền tối DROP partition dọn dữ liệu cũ sạch DELETE để lại 303k dead tuple bảng log phân vùng theo tháng khoảng 303k dòng mỗi tháng PostgreSQL 16, DEFAULT partition bắt dòng ngoài dải đã khai INSERT VALUES 2026-05-15 ngoài mọi vùng tháng đã tạo log_default có 1 dòng ngoài dải không báo lỗi rơi vào DEFAULT, dọn 1 tháng dữ liệu DROP partition vs DELETE A DROP TABLE log_2025_10 303k dòng 0,069 giây n_dead_tup 0 xóa file không quét dòng B DELETE FROM log_flat WHERE ngay nhỏ hơn 2025-11-01 303k dòng 0,118 giây n_dead_tup 303.272 cần VACUUM dọn sau, DETACH tách partition thành bảng độc lập ALTER TABLE log_pt DETACH PARTITION log_2025_11 0,043 giây log_2025_11 giờ là bảng độc lập còn nguyên 293.486 dòng để archive sang lưu trữ lạnh pg_dump riêng trước khi DROP, bảng cách dọn 303k dòng cũ DROP partition 0,069 s dead tuple để lại 0 DELETE bảng phẳng 0,118 s 303.272 cần VACUUM, ở quy mô này chênh thời gian còn nhỏ nhưng DELETE sinh dead tuple cộng WAL khổng lồ và cần VACUUM DROP thì tức thì không bloat khác biệt càng lớn khi bảng càng to

Hình 2: DEFAULT partition bắt dòng ngoài dải (2026-05 → log_default). Dọn 1 tháng: DROP TABLE xóa 303k dòng trong 0,069 s với 0 dead tuple; DELETE tương đương mất 0,118 s và để lại 303.272 dead tuple (cần VACUUM). DETACH tách partition thành bảng độc lập trong 0,043 s, dữ liệu còn nguyên.

  • DROP partition: DROP TABLE log_2025_10 xóa cả 303k dòng trong 0,069 giây với 0 dead tuple. Nó không quét dòng — chỉ xóa file của partition.
  • DELETE trên bảng phẳng: DELETE ... WHERE ngay < '2025-11-01' mất 0,118 giây và để lại 303.272 dead tuple, cần VACUUM để đòi lại không gian.

Ở quy mô 303k dòng này, chênh lệch thời gian còn nhỏ. Nhưng khác biệt quan trọng là dead tuple: DROP để lại 0, DELETE để lại hàng trăm nghìn — mà dead tuple nghĩa là bảng bloat (không co lại nếu không VACUUM FULL), cộng với WAL khổng lồ mà DELETE sinh ra (mỗi dòng một bản ghi). Trên bảng thật hàng chục triệu dòng, DELETE có thể mất hàng phút và cần VACUUM nặng, còn DROP vẫn gần như tức thì. Đây chính là lý do vận hành số một để phân vùng dữ liệu theo thời gian.

DETACH và ATTACH

Đôi khi bạn muốn tách partition ra (để archive sang lưu trữ lạnh, pg_dump riêng) thay vì xóa thẳng:

ALTER TABLE log_pt DETACH PARTITION log_2025_11;   -- tách thành bảng độc lập
ALTER TABLE log_pt ATTACH PARTITION bang_co_san    -- gắn bảng dựng sẵn làm partition
  FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

Đo thật: DETACH mất 0,043 giây, biến log_2025_11 thành một bảng độc lập với 293.486 dòng còn nguyên — không xóa dữ liệu. Từ đó bạn có thể archive rồi DROP, hoặc giữ lại. ATTACH làm ngược lại: gắn một bảng đã dựng sẵn (và nạp dữ liệu) làm partition mới, hữu ích để chuẩn bị dữ liệu ngoài giờ cao điểm rồi gắn vào.

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

Tự động hóa tạo partition là bắt buộc trên production. Đừng dựa vào việc nhớ tạo partition tháng sau bằng tay — quên một lần là INSERT lỗi hàng loạt. Dùng pg_partman (extension chuyên quản lý partition theo thời gian, tự tạo tương lai và dọn cũ), hoặc một cron job chạy CREATE TABLE ... PARTITION OF định kỳ. DEFAULT partition là lưới an toàn, không phải giải pháp thay thế.

DETACH thường (không CONCURRENTLY) lấy khóa mạnh. ALTER TABLE ... DETACH PARTITION mặc định lấy ACCESS EXCLUSIVE trên bảng cha một thoáng — có thể chặn truy vấn. PostgreSQL 14+ có DETACH PARTITION ... CONCURRENTLY để tách mà không khóa dài; dùng nó trên hệ thống bận.

ATTACH kiểm tra ràng buộc có thể tốn thời gian. Khi ATTACH một bảng, PostgreSQL phải kiểm mọi dòng có nằm đúng dải partition không (trừ khi có sẵn CHECK constraint khớp). Trên bảng lớn, việc quét này chậm — thêm CHECK constraint trước để PostgreSQL bỏ qua bước quét.

Ba ý mang về

  1. Dọn dữ liệu cũ bằng DROP partition, không phải DELETE: đo thật, DROP xóa 303k dòng trong 0,069 giây với 0 dead tuple (chỉ xóa file), còn DELETE tương đương mất 0,118 giây và để lại 303.272 dead tuple cần VACUUM — khác biệt càng lớn khi bảng càng to (DELETE sinh WAL khổng lồ + bloat).
  2. Tạo partition tương lai trước, DEFAULT partition làm lưới an toàn: quên tạo partition tháng mới thì INSERT lỗi; DEFAULT bắt dòng ngoài dải (đo thật: dòng 2026-05 rơi vào log_default) — nhưng tự động hóa (pg_partman/cron) là bắt buộc, DEFAULT không thay thế.
  3. DETACH tách partition thành bảng độc lập (0,043 giây, dữ liệu còn nguyên) để archive trước khi DROP; dùng DETACH CONCURRENTLY (PG14+) để tránh khóa dài, và thêm CHECK constraint trước khi ATTACH bảng lớn.

Phần sau ta xét chi tiết kỹ thuật quan trọng khi phân vùng: Phần sau mổ xẻ index trên bảng phân vùng — index tạo trên bảng cha lan xuống mọi partition thế nào, unique index và ràng buộc khóa phân vùng, và CREATE INDEX không khóa toàn bảng.