Bảng append-only lớn cần được thiết kế quanh vòng đời dữ liệu, không chỉ quanh truy vấn.
1. Phân vùng theo thời gian. Chia bảng theo ngày/tuần/tháng bằng range partitioning:
create table events (
id bigserial,
occurred_at timestamptz not null,
event_type text not null,
payload jsonb
) partition by range (occurred_at);
create table events_2026_08 partition of events
for values from ('2026-08-01') to ('2026-09-01');Hai lợi ích lớn: truy vấn có điều kiện thời gian chỉ quét đúng vài partition (partition pruning), và xoá dữ liệu cũ là DROP TABLE một partition — thao tác gần như tức thì, thay cho DELETE hàng triệu dòng vốn sinh khối lượng WAL khổng lồ, làm bảng đầy dead tuple và ép autovacuum chạy nặng.
2. Index tối thiểu. Mỗi index là chi phí trên từng lần chèn. Chỉ giữ index thật sự được dùng, ưu tiên composite bắt đầu bằng cột thời gian; cân nhắc BRIN cho cột occurred_at — dữ liệu chèn theo thứ tự thời gian nên BRIN nhỏ hơn B-tree rất nhiều.
3. Tách đường đọc. Dashboard không nên quét bảng thô: tổng hợp sẵn theo giờ/ngày vào bảng rollup, còn bảng chi tiết chỉ dùng để tra cứu theo id hoặc theo khoảng thời gian hẹp.
4. Đặt sẵn chính sách lưu trữ. Job tự tạo partition tháng tới và tự drop/archive partition quá hạn. Thiếu bước này, việc dọn dẹp lại thành sự cố vào đúng lúc đĩa gần đầy.
Câu hỏi nối tiếp hay gặp: nếu khối lượng vẫn tăng vượt một node, dữ liệu sự kiện nên chuyển sang kho chuyên dụng (time-series hoặc cột) — nhưng phân vùng đúng cách thường đã đủ cho phần lớn hệ thống ở quy mô Việt Nam.