Appearance
PostgreSQL — Storage
shared_buffers và page 8KB đã được cover trong WAL & Checkpoint. File này tập trung vào 2 thứ còn lại: TOAST và OS page cache.
1. TOAST — Lưu trữ giá trị lớn
PostgreSQL có giới hạn: một row không được vượt quá 1 page (8KB). Nhưng thực tế các column text, jsonb, bytea dễ dàng vượt 8KB.
Giải pháp: TOAST (The Oversized-Attribute Storage Technique)
Khi một giá trị vượt ~2KB, PostgreSQL tự động:
row trong main table:
id=1, name='foo', payload=<TOAST pointer> ← pointer 18 bytes
TOAST table (riêng biệt):
chunk_id=42, chunk_seq=0, chunk_data=<2KB đầu>
chunk_id=42, chunk_seq=1, chunk_data=<2KB tiếp>
chunk_id=42, chunk_seq=2, chunk_data=<phần còn lại>Hoàn toàn transparent — app không cần biết gì, query bình thường. PostgreSQL tự assemble lại khi cần.
Storage strategy của từng column
sql
-- Xem strategy hiện tại
SELECT attname,
attstorage,
atttypid::regtype
FROM pg_attribute
WHERE attrelid = 'your_table'::regclass
AND attnum > 0;| Strategy | Ký hiệu | Ý nghĩa |
|---|---|---|
plain | p | Không TOAST, không compress — dùng cho int, bool, timestamp |
main | m | Compress trước, TOAST nếu vẫn to — ưu tiên giữ trong main table |
extended | e | Compress + TOAST nếu cần — default cho text, jsonb |
external | x | TOAST nhưng không compress — dùng khi app tự compress |
Khi nào TOAST ảnh hưởng performance
Query SELECT id, name FROM large_table — nếu không select column TOAST thì PostgreSQL không đọc TOAST table → nhanh.
Query SELECT * FROM large_table hoặc WHERE payload @> '...' → phải đọc TOAST → thêm I/O.
Tip thực tế: Tránh SELECT * trên bảng có column jsonb/text lớn. Chỉ select đúng column cần dùng.
Xem TOAST table của từng bảng
sql
SELECT relname, reltoastrelid::regclass AS toast_table
FROM pg_class
WHERE reltoastrelid > 0
AND relkind = 'r'
AND relnamespace = 'public'::regnamespace;2. OS Page Cache — Double Buffering
PostgreSQL có shared_buffers làm page cache. Nhưng bên dưới, OS cũng có page cache riêng của mình.
PostgreSQL process
↓ đọc/ghi
shared_buffers (PostgreSQL cache, RAM)
↓ cache miss / flush
OS page cache (kernel cache, RAM)
↓ cache miss / flush
DiskData thực ra được cache hai lần — một lần trong shared_buffers, một lần trong OS page cache. Đây gọi là double buffering.
Tại sao không tắt OS cache
Không có cách nào bypass hoàn toàn OS cache trên Linux với PostgreSQL (trừ dùng Direct I/O, không phải default). Nên thực tế RAM được dùng như sau:
shared_buffers = 25% RAM → PostgreSQL tự quản lý
OS page cache = phần còn lại RAM → kernel tự quản lýeffective_cache_size trong postgresql.conf không phải config cấp phát RAM — nó chỉ là hint cho planner biết tổng cache (shared_buffers + OS cache) khoảng bao nhiêu để ước lượng cost của index scan. Set bằng 75% RAM là hợp lý.
ini
shared_buffers = 4GB # cấp phát thật cho PostgreSQL
effective_cache_size = 12GB # hint cho planner, không cấp phát thậtKhi nào double buffering là vấn đề
Với workload OLTP thông thường — không phải vấn đề, kernel cache bù được cho shared_buffers miss.
Với workload streaming large sequential reads (bulk export, analytics) — data lớn hơn RAM, OS cache bị thrash liên tục → lúc này có thể xem xét pg_fadvise hoặc tăng shared_buffers để giữ hot data.