Skip to content

PostgreSQL — Storage ​

shared_buffers và page 8KB đã được trình bày 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;
StrategyKý hiệuÝ nghĩa
plainpKhông TOAST, không compress — dùng cho int, bool, timestamp
mainmCompress trước, TOAST nếu vẫn to — ưu tiên giữ trong main table
extendedeCompress + TOAST nếu cần — default cho text, jsonb
externalxTOAST nhưng không compress — dùng khi app tự compress

Khi nào TOAST ảnh hưởng đến 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
Disk

Data 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 thể 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ừ khi 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ật

Khi nào double buffering là vấn đề ​

Với workload OLTP thông thường — không phải vấn đề, vì kernel cache bù được cho shared_buffers miss.

Với workload streaming large sequential reads (bulk export, analytics) — khi 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.

Personal notes by thanhlt