Appearance
PostgreSQL — WAL & Checkpoint
1. Bức tranh toàn cảnh — RAM nằm ở đâu
PostgreSQL có hai vùng RAM riêng biệt trước khi xuống disk:
RAM Disk
┌─────────────────────┐
│ shared_buffers │◄──── data pages (8KB/page)
│ (buffer pool) │────► pg_data/base/...
└─────────────────────┘
↑ ↓ read/write data pages
┌─────────────────────┐
│ WAL buffers │
│ (wal_buffers) │────► pg_wal/
└─────────────────────┘
↑ append-onlyshared_buffers là page cache của PostgreSQL — cả đọc lẫn ghi đều đi qua đây, không có gì bypass nó.
WAL buffers là buffer riêng chứa log entries trước khi flush xuống WAL file trên disk.
2. Flow đọc
SELECT * FROM users WHERE id = 1
↓
tìm page trong shared_buffers?
├── có → trả về ngay (buffer hit)
└── không → đọc từ disk → nạp vào shared_buffers → trả về (buffer miss)Không có result cache. PostgreSQL cache data pages, không cache kết quả query. Mỗi lần chạy SELECT count(*) WHERE ... PostgreSQL vẫn phải tính lại — dù data không đổi. Oracle có result cache để skip bước này, PostgreSQL không có.
Nếu muốn cache result ở tầng application, dùng Redis/Memcached ở ngoài.
3. Flow ghi và vai trò WAL
UPDATE users SET tier = 'gold' WHERE id = 1
↓
[1] tìm/nạp page vào shared_buffers (nếu chưa có)
↓
[2] sửa page trong shared_buffers ← page trở thành "dirty"
[2] đồng thời ghi log entry vào WAL buffers
↓
[3] COMMIT → flush WAL buffers → WAL file trên disk ← phải xong trước khi COMMIT trả về
↓
[4] background writer dần dần flush dirty pages xuống disk ← diễn ra sau, asyncTại sao COMMIT chỉ cần flush WAL, không cần flush data page?
WAL chứa đủ thông tin để reconstruct mọi thay đổi. Nếu crash xảy ra giữa bước 3 và 4, data page trên disk còn cũ — nhưng PostgreSQL có thể replay WAL để đưa nó về đúng trạng thái.
Nếu crash xảy ra:
server restart
↓
PostgreSQL đọc WAL từ checkpoint cuối cùng
↓
replay lại từng log entry
↓
dirty pages được reconstruct đúng
↓
database sẵn sàng2. Checkpoint
Checkpoint là gì
WAL sẽ tích lũy mãi nếu không có cơ chế dọn. Checkpoint là lúc PostgreSQL nói: "tất cả data pages đã được flush xuống disk đến điểm này rồi — WAL trước điểm này không cần nữa."
WAL timeline:
─────────────────────────────────────────────────────────────►
[Checkpoint A] [Checkpoint B]
│ │
│ WAL segment 001-005 │ WAL segment 006-010
│ (có thể xóa sau B) │ (cần giữ cho đến C)Tại sao checkpoint gây chậm
Khi checkpoint chạy, PostgreSQL phải flush một lượng lớn dirty pages xuống disk cùng lúc → I/O spike → query bị chậm đột ngột.
checkpoint_completion_target = 0.9 giải quyết điều này bằng cách trải đều việc flush ra 90% thời gian giữa 2 checkpoint thay vì flush dồn vào cuối.
checkpoint_completion_target = 0.9:
[Checkpoint A]────────────────────────────────────[Checkpoint B]
flush ████████████████████████████████░░░░░░░░░
←─────────── 90% thời gian ──────────►
checkpoint_completion_target = 0.1 (xấu):
[Checkpoint A]────────────────────────────────────[Checkpoint B]
flush ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████
← spikeKhi nào checkpoint được trigger
- Định kỳ: mỗi
checkpoint_timeoutgiây (default 5 phút) - Khi WAL quá lớn: khi WAL vượt
max_wal_size(default 1GB) - Manual:
CHECKPOINTcommand
Nếu thấy checkpoint quá thường xuyên trong log, nghĩa là WAL đang tích lũy nhanh hơn mức bình thường — thường do bulk write hoặc long-running transaction.
sql
-- Xem thống kê checkpoint
SELECT checkpoints_timed,
checkpoints_req, -- checkpoint bị trigger sớm do WAL đầy
checkpoint_write_time,
checkpoint_sync_time,
buffers_checkpoint
FROM pg_stat_bgwriter;
-- checkpoints_req cao → max_wal_size đang quá nhỏ3. WAL và Replication
WAL streaming replication hoạt động đơn giản: primary liên tục stream WAL sang replica, replica replay lại.
Primary Replica
────── ───────
INSERT/UPDATE/DELETE
↓
ghi WAL ──── stream WAL ────► nhận WAL
↓ ↓
data page replay → data pagewal_level kiểm soát lượng thông tin trong WAL:
| Level | Dùng khi |
|---|---|
minimal | Không replication, không backup |
replica | Streaming replication (default nên dùng) |
logical | Logical replication, CDC, pglogical |
Thực tế: luôn set replica kể cả không dùng replication ngay — cost không đáng kể và giúp mọi công cụ backup hoạt động được.
Monitor replication lag
sql
-- Trên primary: xem replica lag
SELECT client_addr,
state,
sent_lsn - write_lsn AS write_lag_bytes,
write_lsn - flush_lsn AS flush_lag_bytes,
flush_lsn - replay_lsn AS replay_lag_bytes
FROM pg_stat_replication;
-- Trên replica: tự xem lag của mình
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;4. PITR — Point-in-Time Recovery
PITR cho phép restore database về bất kỳ thời điểm nào trong quá khứ, không chỉ về backup gần nhất.
Cần:
- Base backup — snapshot của data directory tại một thời điểm
- WAL archive — tất cả WAL segments từ sau backup đó đến hiện tại
Recovery flow:
base backup (T=0) + WAL T=0..T=5 → restore về T=3
(replay WAL đến T=3 rồi dừng)Khi nào BE dev thực sự cần biết điều này:
- Khi thiết kế backup strategy cho production
- Khi cần giải thích tại sao cần lưu WAL archive chứ không chỉ daily snapshot
Còn setup cụ thể (pg_basebackup, archive_command, restore_command) thường là việc của DBA/DevOps.
5. Các tình huống hay gặp
App chậm từng đợt không rõ nguyên nhân
Có thể là checkpoint I/O spike. Kiểm tra:
sql
-- Bật log checkpoint
-- postgresql.conf:
-- log_checkpoints = on
-- Sau đó xem trong log:
-- LOG: checkpoint complete: wrote 12453 buffers (5.3%); ...
-- Nếu buffers cao và duration dài → I/O đang bị ảnh hưởngFix: tăng checkpoint_completion_target lên 0.9, tăng max_wal_size để checkpoint xảy ra ít thường hơn.
Disk đầy đột ngột ở thư mục pg_wal
WAL có thể tích lũy khi:
- Long-running transaction — PostgreSQL giữ WAL từ trước khi transaction bắt đầu
- Replica bị lag — primary phải giữ WAL cho đến khi replica catch up
- WAL archiver bị stuck —
archive_commandfail liên tục
sql
-- Tìm long-running transaction
SELECT pid, now() - xact_start AS duration, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY duration DESC
LIMIT 5;
-- Xem replica có bị lag không
SELECT client_addr, state, sent_lsn - replay_lsn AS lag_bytes
FROM pg_stat_replication;
-- Xem WAL archiver status
SELECT last_failed_wal, last_failed_time, last_archived_time
FROM pg_stat_archiver;full_page_writes — bao giờ cần tắt
Mặc định on. Khi checkpoint xảy ra, lần đầu tiên một page được modify sau checkpoint → PostgreSQL ghi cả page 8KB vào WAL (thay vì chỉ ghi delta). Lý do: bảo vệ trường hợp crash giữa chừng khi đang ghi một 8KB page (partial write).
Tắt được nếu storage của bạn đảm bảo atomic write (ZFS, EBS, một số SAN). Thường không cần tắt — WAL compression bù lại phần lớn overhead.
6. Config thực tế
ini
# WAL
wal_level = replica # luôn set, không để minimal
max_wal_size = 4GB # tăng nếu checkpoint quá thường xuyên
min_wal_size = 1GB # giữ sẵn để tránh allocate liên tục
wal_compression = on # nên bật, giảm I/O và disk
# Checkpoint
checkpoint_completion_target = 0.9 # trải đều I/O, tránh spike
checkpoint_timeout = 5min # giữ default
# Logging để debug
log_checkpoints = onMột điểm về work_mem và WAL: work_mem lớn giúp sort/hash trong bộ nhớ thay vì spill ra disk — gián tiếp giảm lượng WAL vì ít temp file operations hơn. Nhưng tổng RAM dùng = work_mem × connections × operations_per_query → đừng set quá cao.