Skip to content

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;
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 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 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ật

Khi 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.

Personal notes by thanhlt