Mục lục
- Mục Tiêu Bài Học
- Linux fork() & Copy-on-Write
- Vì Sao Redis Dùng Fork
- Chi Phí Của fork()
- COW Page Duplication Trong Parent
- Đo Latency Fork — LATENCY HISTORY
- Transparent Huge Pages — Thảm Họa
- Disable THP Bắt Buộc
- vm.overcommit_memory
- Memory Headroom Cần Thiết
- AOF Fsync Policies
- AOF Rewrite & Save Point Tuning
- Diskless Replication
- OOM Scenarios Trong Fork
- Monitor Fork Events
- Anti-patterns & Best Practices
- Tổng Kết & Quiz
Mục Tiêu Bài Học
- Giải thích được cơ chế
fork()+ COW trên Linux và tại sao Redis phụ thuộc vào nó cho persistence. - Tính được chi phí fork theo dataset size và hiểu tại sao fork latency có thể đạt hàng trăm mili-giây.
- Phân tích memory pressure trong COW: parent write → kernel duplicate → peak memory = dataset × (1 + write rate).
- Biết cách đo fork latency bằng
INFO statsvàLATENCY HISTORY fork. - Hiểu vì sao Transparent Huge Pages (THP) khuếch đại COW overhead 500 lần và cách disable bắt buộc.
- Cấu hình
vm.overcommit_memory=1và tính memory headroom tối thiểu cho production Redis. - So sánh ba AOF fsync policy:
always,everysec,no— tradeoff durability/latency. - Dùng diskless replication để giảm số lần fork khi có nhiều replica.
- Xây được script Python check fork health và biết ngưỡng alert thực tế.
Linux fork() & Copy-on-Write
fork() là system call tạo child process là bản sao của parent. Trước khi Copy-on-Write tồn tại, kernel phải copy toàn bộ bộ nhớ parent sang child ngay tại thời điểm fork — rất tốn kém với process nhiều GB. COW giải quyết bằng cách để parent và child chia sẻ cùng trang vật lý ban đầu; kernel đánh dấu tất cả trang là read-only.
Khi một trong hai process ghi vào trang được chia sẻ, CPU phát sinh page fault, kernel duplicate trang đó và cấp bản riêng cho process đang ghi. Process còn lại vẫn thấy bản cũ. Đây là cơ chế "copy khi cần" (Copy-on-Write).
Parent process (Redis server):
Page A: [data] ─────────────────── shared (read-only mark)
Page B: [data] ─────────────────── shared
Page C: [data] ─────────────────── shared
fork()
│
Child process (BGSAVE):
Page A: [data] ← same physical frame
Page B: [data] ← same physical frame
Page C: [data] ← same physical frame
Parent sau fork nhận write vào Page A:
kernel detects write to shared page
→ duplicate Page A → parent gets new frame, child keeps original
Page A (parent): [new data] ← new physical frame
Page A (child): [old data] ← original frame (snapshot consistent!)
Kết quả: child thấy trạng thái bộ nhớ tại thời điểm fork — đây chính là nền tảng cho snapshot point-in-time của Redis.
Vì Sao Redis Dùng Fork
Redis là single-threaded event loop. Để ghi snapshot toàn bộ dataset xuống disk mà không block client request, Redis fork một child process và giao toàn bộ công việc I/O cho child trong khi parent tiếp tục xử lý command.
- BGSAVE: tạo child → child serialize toàn bộ dataset thành file
dump.rdb→ child kết thúc, parent nhận báo hiệu → atomic rename file mới. - BGREWRITEAOF: tạo child → child viết lại AOF từ dataset hiện tại (compacted) → parent ghi tiếp AOF incremental buffer → merge → rename.
Cả hai lệnh đều trả lời ngay lập tức ("Background saving started") và không làm Redis block client trong giai đoạn I/O chính. Nhưng bản thân lời gọi fork() vẫn block Redis trong một khoảng thời gian ngắn — và "ngắn" này có thể không nhỏ.
# Kích hoạt thủ công
redis-cli BGSAVE
# Output: Background saving started
redis-cli BGREWRITEAOF
# Output: Background append only file rewriting started
# Kiểm tra trạng thái
redis-cli INFO persistence | grep -E "rdb_bgsave_in_progress|aof_rewrite_in_progress|latest_fork_usec"
Chi Phí Của fork()
Dù COW tránh copy toàn bộ dữ liệu, kernel vẫn phải copy page table — cấu trúc ánh xạ virtual address → physical frame. Page table tỉ lệ thuận với lượng memory được dùng:
- Hệ thống 4 KB page size, mỗi entry page table ~8 bytes → 1 GB dataset ≈ 2 MB page table.
- 10 GB dataset ≈ 20 MB page table cần copy.
- 100 GB dataset ≈ ~200 MB page table — và copy 200 MB trong kernel space không phải tức thì.
Trong khoảng thời gian fork() đang chạy, Redis event loop bị block hoàn toàn — không xử lý được command nào. Điều này biểu hiện ra ngoài như latency spike đột ngột.
Thời gian fork thực tế phụ thuộc nhiều yếu tố: memory size, CPU speed, kernel version, NUMA topology, memory fragmentation, và (quan trọng nhất) Transparent Huge Pages. Dưới đây là con số tham khảo trên hardware thông thường:
| Dataset size | Fork latency thường gặp | Worst case (THP bật) |
|---|---|---|
| 1 GB | 10–50 ms | 100+ ms |
| 10 GB | 100–300 ms | 1–3 s |
| 50 GB | 500 ms–1 s | 5–10 s |
| 100 GB | 1–3 s | 10 s+ |
Con số trên chỉ là fork, chưa tính thời gian child thực sự ghi RDB xuống disk. Với SSD NVMe thì I/O nhanh; với HDD hoặc network-attached storage thì lâu hơn nhiều.
COW Page Duplication Trong Parent
Sau khi fork xong, parent tiếp tục nhận write từ client. Mỗi lần parent ghi vào trang đang được chia sẻ với child, kernel phải duplicate trang đó. Memory sử dụng thực tế tăng theo số trang bị modified.
Worst case: nếu mọi trang đều bị write trong khoảng thời gian child đang chạy, tổng RAM dùng có thể tăng gần gấp đôi:
RSS sau fork = parent_modified_pages × 2 + parent_unmodified_pages
= dataset × (1 + write_rate_factor)
Ví dụ:
Dataset: 8 GB
50% pages được write trong lúc child chạy
→ Peak memory ≈ 8 GB + 4 GB = 12 GB
100% pages được write (high write throughput)
→ Peak memory ≈ 8 GB + 8 GB = 16 GB
Thời gian child chạy càng lâu (dataset lớn, disk chậm) và write throughput parent càng cao thì COW duplicate càng nhiều. Đây là lý do tại sao Redis khuyến nghị để memory headroom đáng kể — không phải cho dataset tăng trưởng mà cho COW overhead.
Redis 7.x xuất thêm thông số rdb_cow_size và aof_rewrite_buffer_length trong INFO persistence để theo dõi mức độ COW:
redis-cli INFO persistence | grep -E "rdb_cow_size|aof_rewrite_buffer_length|current_cow_peak|current_fork_perc"
Đo Latency Fork — LATENCY HISTORY
Redis có hệ thống latency monitoring tích hợp, ghi lại các event vượt ngưỡng latency-monitor-threshold. Fork là một trong các event được theo dõi:
# Bật latency monitoring (ngưỡng 100ms)
redis-cli CONFIG SET latency-monitor-threshold 100
# Xem lịch sử event fork
redis-cli LATENCY HISTORY fork
# Xem event gần nhất
redis-cli LATENCY LATEST
# LATENCY DOCTOR — phân tích toàn diện
redis-cli LATENCY DOCTOR
# Output LATENCY HISTORY fork (timestamp, event_duration_ms):
# 1717200000 145
# 1717203600 312
# 1717207200 289
# Output LATENCY LATEST:
# fork 1717207200 289 312 ← event, timestamp, last_ms, max_ms
Trường latest_fork_usec trong INFO stats cho thấy thời gian fork lần cuối tính bằng micro-giây:
redis-cli INFO stats | grep latest_fork_usec
# latest_fork_usec:145823 → ~146 ms
Ngoài fork, LATENCY HISTORY còn theo dõi các event persistence liên quan:
rdb-unlink-event: xóa file RDB cũ sau khi tạo xong file mới — với large file trên HDD có thể mất giây.aof-fstat,aof-stat: thống kê file AOF.aof-fsync-always: fsync mỗi write (khi dùng policyalways).
Transparent Huge Pages — Thảm Họa
Linux Transparent Huge Pages (THP) là tính năng kernel tự động gộp các trang 4 KB thành trang 2 MB để giảm TLB miss. Với database thông thường đọc nhiều trang cùng lúc, THP có thể hữu ích. Với Redis và COW, THP là thảm họa.
Nguyên nhân: khi COW xảy ra, kernel phải duplicate toàn bộ trang 2 MB — dù parent chỉ write 1 byte trong trang đó. Thay vì duplicate 4 KB, kernel duplicate 2 MB:
Standard 4 KB pages:
Parent write 1 byte → kernel duplicate 4 KB
Amplification: 1
Transparent Huge Pages (2 MB):
Parent write 1 byte → kernel duplicate 2 MB
Amplification: 2 MB / 4 KB = 512×
Kết hợp với write workload thực tế:
10 GB dataset, 30% pages touched → COW overhead với 4KB: ~3 GB
10 GB dataset, 30% pages touched → COW overhead với THP: ~3 GB × 512 ÷ coverage_factor
Nhưng vì THP split lazily, thực tế amplification từ 5×–500× tuỳ kernel version và workload
Triệu chứng THP trên production Redis:
- Fork latency tăng đột biến sau khi OS upgrade hoặc migrate sang server mới.
- Memory usage sau BGSAVE tăng vọt, đôi khi vượt maxmemory → OOM.
- Redis log cảnh báo:
WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. INFO memoryfieldmem_allocatorcho thấy jemalloc nhưng RSS vẫn rất cao sau fork.
Redis Redis 4+ tự detect THP và log cảnh báo khi khởi động. Không ignore cảnh báo này.
Disable THP Bắt Buộc
Disable THP là yêu cầu bắt buộc cho bất kỳ Redis server production nào. Không có trường hợp ngoại lệ khi dùng Redis với persistence enabled.
# Disable ngay lập tức (không cần restart, hiệu lực ngay)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# Verify
cat /sys/kernel/mm/transparent_hugepage/enabled
# always madvise [never] ← dấu [] chỉ giá trị active
# Persistent qua reboot — Ubuntu/Debian (GRUB)
# Thêm vào /etc/default/grub:
GRUB_CMDLINE_LINUX="transparent_hugepage=never"
update-grub
# Hoặc dùng rc.local / systemd service:
# /etc/rc.local
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# Persistent qua systemd (cách khuyến nghị cho modern Linux)
cat <<'EOF' > /etc/systemd/system/disable-thp.service
[Unit]
Description=Disable Transparent Huge Pages
[Service]
Type=simple
ExecStart=/bin/sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag"
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now disable-thp
Với container (Docker/Kubernetes): THP được control ở host kernel, không phải trong container. Cần disable ở host node hoặc dùng privileged: true + init container để set. Một số managed Kubernetes provider (EKS, GKE) có node tuning option riêng.
vm.overcommit_memory
Khi Redis fork child, kernel phải đảm bảo child có thể dùng memory ngang bằng parent. Với vm.overcommit_memory=0 (default), kernel kiểm tra xem có đủ memory trống không trước khi cho fork. Nếu Redis đang dùng 8 GB và total free memory < 8 GB, fork có thể fail với lỗi ENOMEM.
Thực tế thì COW đảm bảo child không thực sự cần thêm 8 GB ngay lập tức — chỉ cần thêm cho các trang bị modify. Nhưng kernel không biết trước bao nhiêu trang sẽ bị modify. Kết quả: fork fail khi Redis đang dùng nhiều memory, ngay cả khi thực tế có đủ RAM.
# Giá trị vm.overcommit_memory:
# 0 = heuristic check (default) — có thể từ chối fork
# 1 = always allow overcommit — không check, fork luôn thành công
# 2 = strict — không bao giờ overcommit, quá conservative
# Redis recommend: set to 1
sysctl vm.overcommit_memory=1
# Persistent:
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl -p
# Redis log khi overcommit_memory=0:
# WARNING overcommit_memory is set to 0!
# Background save may fail under low memory condition.
# To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf
Với overcommit_memory=1, Linux sẽ cấp memory ảo ngay cả khi không có RAM vật lý tương ứng, dựa trên giả định không phải mọi memory được cấp đều dùng hết cùng lúc. Đây là hành vi bình thường cho server process chạy nhiều child process.
Memory Headroom Cần Thiết
Rule of thumb cho production Redis với persistence:
- Tối thiểu: RAM = dataset × 1.5 (50% headroom cho COW overhead + overhead khác).
- Khuyến nghị: RAM = dataset × 2 (100% headroom). Nếu BGSAVE chạy lúc peak write, toàn bộ dataset có thể bị duplicate qua COW.
- maxmemory nên đặt ≤ 70% tổng RAM để để lại room cho COW.
Ví dụ tính toán:
Server: 32 GB RAM
maxmemory nên set ≤ 70% × 32 GB = ~22 GB
Headroom cho COW: 32 GB - 22 GB = 10 GB
COW worst case (100% pages written): 22 GB extra needed
→ Server 32 GB không đủ nếu write throughput rất cao khi BGSAVE
An toàn hơn: maxmemory = 50% × 32 GB = 16 GB
COW worst case: 16 GB extra, tổng = 32 GB = đúng RAM
→ tight nhưng chạy được nếu không có process khác
Nếu không thể tăng RAM, giảm write throughput trong lúc BGSAVE bằng cách:
- Giảm tần suất BGSAVE (ít save point hơn).
- Chỉ dùng một trong hai: RDB hoặc AOF rewrite, không cả hai đồng thời.
- Dùng replica để BGSAVE thay vì master.
AOF Fsync Policies
AOF (Append Only File) ghi mỗi write command vào file log. Khi nào file được flush xuống disk vật lý phụ thuộc vào appendfsync:
| Policy | Khi nào fsync | Latency impact | Dữ liệu mất khi crash |
|---|---|---|---|
always |
Sau mỗi write command | Cao nhất — đợi disk mỗi lần | 0 (hoặc 1 command) |
everysec |
1 lần/giây (background thread) | Thấp — không block event loop | Tối đa ~1 giây |
no |
OS quyết định (thường 30s) | Thấp nhất | Có thể vài chục giây–phút |
Với always: mỗi command phải đợi disk fsync → latency p99 bị kéo lên vài ms trên mỗi write. Không phù hợp với throughput cao. Chỉ dùng khi yêu cầu durability tuyệt đối (ví dụ Redis là primary storage).
Với everysec: background thread fsync mỗi giây. Event loop không bị block. Đây là default và cũng là lựa chọn phổ biến nhất. Nếu disk đang bận (BGSAVE đang ghi RDB), fsync có thể bị delay thêm vài trăm ms — nhưng Redis 2.4+ có cơ chế detect và skip fsync khi disk quá bận.
Với no: hoàn toàn để OS xử lý, Redis chỉ gọi write(). Nhanh nhất nhưng dữ liệu trong kernel buffer có thể mất nếu server mất điện đột ngột. Phù hợp khi Redis chỉ là cache (không phải source of truth).
# redis.conf
appendonly yes
appendfsync everysec
# Khi BGSAVE đang chạy, có thể tạm tắt fsync để tránh I/O contention:
no-appendfsync-on-rewrite yes # default: no
no-appendfsync-on-rewrite yes tắt fsync AOF trong lúc BGREWRITEAOF đang chạy. Disk không phải cạnh tranh giữa write RDB/AOF rewrite và AOF fsync. Trade-off: trong lúc rewrite, nếu crash thì mất dữ liệu tương đương policy no.
AOF Rewrite & Save Point Tuning
AOF file tăng dần theo từng command. Để tránh file AOF phình to vô tận, Redis tự động trigger rewrite khi file đủ lớn:
# redis.conf
# Trigger AOF rewrite khi file tăng gấp đôi so với lần rewrite cuối
auto-aof-rewrite-percentage 100
# Chỉ trigger nếu file đạt tối thiểu 64 MB
auto-aof-rewrite-min-size 64mb
AOF rewrite = một lần fork, một lần COW. Nếu điều chỉnh auto-aof-rewrite-percentage nhỏ hơn, rewrite xảy ra thường xuyên hơn → nhiều fork hơn. Nếu lớn hơn, file AOF phình to hơn trước khi compact.
Tương tự, RDB save points kiểm soát tần suất BGSAVE:
# Cấu hình save point mặc định Redis:
save 3600 1 # save nếu ít nhất 1 key thay đổi trong 3600s
save 300 100 # save nếu ít nhất 100 key thay đổi trong 300s
save 60 10000 # save nếu ít nhất 10000 key thay đổi trong 60s
# Disable tất cả RDB saves:
save ""
# Chỉ save 1 lần/giờ dù có bao nhiêu thay đổi:
save 3600 1
Nếu chỉ dùng AOF, disable RDB saves bằng save "" để tránh fork thêm từ BGSAVE. Ngược lại nếu chỉ dùng RDB, disable AOF bằng appendonly no. Chạy cả hai đồng thời với frequency cao = fork nhiều = nhiều latency spike hơn.
Diskless Replication
Khi replica kết nối lần đầu (full resync), master phải gửi toàn bộ dataset. Cách truyền thống: master BGSAVE → ghi file RDB → gửi file qua socket → replica load. Mỗi replica join = một lần fork + ghi disk.
Diskless replication (Redis 2.8.18+) bỏ bước ghi file trung gian:
# redis.conf
repl-diskless-sync yes
# Delay giữa các replica (để nhiều replica có thể share cùng 1 fork):
repl-diskless-sync-delay 5 # giây, default 5
Với diskless sync: master fork child → child serialize RDB và stream trực tiếp qua socket sang replica — không ghi file nào lên disk. Ích lợi rõ ràng khi:
- Disk slow (network faster than disk): streaming qua network nhanh hơn ghi + đọc lại file.
- Nhiều replica cần full resync cùng lúc:
repl-diskless-sync-delaycho phép nhiều replica chờ trong window nhỏ, master chỉ cần 1 fork phục vụ tất cả.
Trade-off: nếu replica disconnect giữa chừng, master phải fork lại ngay vì không có file RDB để retry. Với disk-based sync, master có thể gửi lại file đã ghi. Với fast network và ít replica, diskless thường tốt hơn.
Ngoài ra: để replica không gây fork trên master, có thể cấu hình replica tự làm BGSAVE riêng:
# replica redis.conf
# Chỉ save trên replica, không save trên master:
# (trên master: save "")
save 900 1
save 300 10
OOM Scenarios Trong Fork
Linux OOM killer (Out-of-Memory Killer) sẽ kill process khi hệ thống hết RAM. Trong bối cảnh Redis fork, có hai scenario OOM thường gặp:
Scenario 1 — maxmemory quá cao:
RAM server: 16 GB
maxmemory Redis: 14 GB (87%)
BGSAVE trigger, COW: parent write heavy → +4 GB COW pages
Tổng: 14 GB (parent) + 4 GB (COW) + ~1 GB OS = 19 GB > 16 GB
→ OOM killer kill Redis hoặc child process
Scenario 2 — Cluster với nhiều master đồng loạt fork:
6 master nodes trên cùng server (Redis Cluster với 3 master + 3 replica trên 2 server)
Mỗi node 4 GB, tổng = 24 GB
Cùng lúc tất cả 3 master trigger BGSAVE (do cùng save point 60s)
COW: mỗi node + 2 GB × 3 = +6 GB
Tổng đỉnh: 24 GB + 6 GB = 30 GB → OOM
Mitigation:
- Reserve > 30% RAM headroom: maxmemory ≤ 70% total RAM.
- Stagger save points giữa các nodes: node A
save 60 10000, node Bsave 65 10000(offset 5s). Không hoàn hảo nhưng giảm xác suất đồng thời. - Disable RDB trên master, chỉ BGSAVE trên replica.
- Tắt swap hoặc để swap nhỏ: swap thrashing khi fork còn tệ hơn OOM.
# Kiểm tra OOM killer log
dmesg | grep -i "out of memory\|oom-killer" | tail -20
# Hoặc
journalctl -k | grep -i oom | tail -20
Monitor Fork Events
Monitoring fork không thể là afterthought. Cần track ít nhất hai metric sau:
# 1. latest_fork_usec — từ INFO stats
redis-cli INFO stats | grep latest_fork_usec
# latest_fork_usec:145823 → 145 ms
# 2. LATENCY HISTORY fork — lịch sử event
redis-cli LATENCY HISTORY fork
# 3. LATENCY RESET — clear history sau khi đã xử lý
redis-cli LATENCY RESET fork
Script Python kiểm tra fork health và alert:
import redis
import time
def check_fork_health(r: redis.Redis, threshold_ms: int = 200) -> dict:
"""
Kiểm tra fork latency từ INFO stats.
threshold_ms: ngưỡng alert (ms)
"""
info = r.info("stats")
fork_usec = info.get("latest_fork_usec", 0)
fork_ms = fork_usec / 1000
result = {
"fork_ms": fork_ms,
"threshold_ms": threshold_ms,
"status": "ok" if fork_ms <= threshold_ms else "warning",
}
if fork_ms > threshold_ms:
result["message"] = (
f"Fork slow: {fork_ms:.1f} ms (threshold {threshold_ms} ms). "
f"Check THP, maxmemory, dataset size."
)
return result
def check_fork_events(r: redis.Redis) -> list[dict]:
"""
Lấy lịch sử fork event từ LATENCY HISTORY.
Trả về danh sách (timestamp, duration_ms).
"""
events = r.latency_history("fork")
return [
{"timestamp": ts, "duration_ms": duration}
for ts, duration in events
]
if __name__ == "__main__":
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
health = check_fork_health(r, threshold_ms=200)
print(f"Fork health: {health['status']} — {health['fork_ms']:.1f} ms")
if health["status"] == "warning":
print(f" ALERT: {health['message']}")
events = check_fork_events(r)
if events:
avg_ms = sum(e["duration_ms"] for e in events) / len(events)
max_ms = max(e["duration_ms"] for e in events)
print(f"Fork history: {len(events)} events, avg={avg_ms:.1f}ms, max={max_ms}ms")
Ngoài fork latency, nên track thêm:
rdb_changes_since_last_save: số key thay đổi kể từ BGSAVE cuối — nếu cao bất thường có thể cần trigger BGSAVE sớm hơn.rdb_last_bgsave_status:okhoặcerr— alert ngay khierr.aof_last_bgrewrite_status: tương tự cho AOF rewrite.- Memory:
used_memory_rssvsused_memory— ratio cao sau fork gợi ý COW overhead lớn.
Anti-patterns & Best Practices
Anti-patterns
- THP enabled trong production: là lỗi phổ biến nhất. Thường xảy ra sau OS upgrade hoặc migrate sang VM mới mà không kiểm tra lại kernel settings. Redis log sẽ warn nhưng dễ bị bỏ qua.
- maxmemory > 80% RAM: để lại quá ít room cho COW, dễ dẫn đến OOM kill khi BGSAVE + heavy write load.
- Cả RDB lẫn AOF rewrite với frequency cao: hai nguồn fork cộng lại, latency spike liên tục.
- Không set vm.overcommit_memory=1: BGSAVE fail với
Can't save in background: fork: Cannot allocate memorykhi memory pressure. - Swap enabled lớn: khi fork COW tràn RAM, kernel swap page ra disk — thay vì OOM nhanh sẽ có swap thrashing, latency tăng vọt hàng giây.
- Không monitor fork latency: vấn đề âm thầm tích tụ theo thời gian, không phát hiện cho đến khi incident.
- Cùng save point cho tất cả node trong Cluster: đồng loạt fork cùng lúc → RAM peak cộng dồn.
Best practices tổng hợp
- Disable THP bắt buộc trên mọi Redis production server.
- Set
vm.overcommit_memory=1persistent qua sysctl.conf. - maxmemory ≤ 70% total RAM (để 30% headroom cho COW).
- AOF fsync
everyseclà điểm cân bằng tốt nhất cho hầu hết workload. - Dùng
no-appendfsync-on-rewrite yesđể tránh I/O contention trong lúc rewrite. - Chọn một: chỉ RDB hoặc chỉ AOF nếu write rate cao và RAM hạn chế.
- Diskless replication với fast network (≥ 1 Gbps).
- Monitor
latest_fork_usec+ alert khi > 200 ms. - Stagger save points giữa các master node trong Cluster.
- Disable save trên master, để replica đảm nhận nếu có thể.
Checklist nhanh khi điều tra fork latency spike
# 1. THP?
cat /sys/kernel/mm/transparent_hugepage/enabled
# 2. Fork duration gần nhất?
redis-cli INFO stats | grep latest_fork_usec
# 3. overcommit?
cat /proc/sys/vm/overcommit_memory
# 4. Memory ratio?
redis-cli INFO memory | grep -E "used_memory_human|used_memory_rss_human|maxmemory_human"
# 5. LATENCY DOCTOR
redis-cli LATENCY DOCTOR
# 6. Persistence status
redis-cli INFO persistence | grep -E "rdb_last_bgsave_status|aof_last_bgrewrite_status|rdb_bgsave_in_progress"
Tổng Kết & Quiz
Fork/COW là cơ chế nền tảng cho persistence non-blocking trong Redis. Hiểu cơ chế giúp giải thích các hiện tượng quan sát được trong production: latency spike định kỳ, memory tăng đột biến trong lúc BGSAVE, OOM khi server đang "dư" RAM trên giấy tờ.
Ba điều quan trọng nhất cần nhớ:
- Disable THP ngay lập tức và persistent — không có trường hợp ngoại lệ.
- maxmemory ≤ 70% RAM để có room cho COW overhead.
- Monitor
latest_fork_usecvà alert khi vượt ngưỡng.
Quiz
- Tại sao fork() block Redis event loop ngay cả khi dùng COW? Phần nào của kernel state phải được copy ngay tại lúc fork?
- Một server có 32 GB RAM, Redis maxmemory 28 GB. BGSAVE bắt đầu và 40% trang bị write trong lúc child đang chạy. Tính peak memory sử dụng (xấp xỉ). Server có OOM không?
- Giải thích vì sao THP có thể gây amplification lên tới 512×. Điều gì xảy ra cụ thể khi parent write 1 byte vào trang đang được chia sẻ với child (THP enabled vs disabled)?
- Sự khác biệt giữa
appendfsync alwaysvàappendfsync everysecvề latency? Khi nào nên dùngalways? - Diskless replication giải quyết vấn đề gì so với disk-based replication? Trong trường hợp nào disk-based lại tốt hơn?
Đáp án gợi ý
- COW tránh copy dữ liệu thực, nhưng kernel vẫn phải copy page table (cấu trúc ánh xạ virtual → physical address) từ parent sang child. Page table tỉ lệ với lượng memory được dùng — với dataset lớn (10–100 GB), page table có thể vài chục đến vài trăm MB, copy page table mất thời gian đáng kể và block event loop trong suốt thời gian đó.
- Tại lúc fork: parent 28 GB. COW 40% = 11.2 GB extra. Peak = 28 + 11.2 + ~1 GB OS = ~40 GB > 32 GB → OOM killer sẽ kill Redis hoặc child. Server sẽ OOM.
- THP gộp 512 trang 4 KB thành 1 trang 2 MB. Khi parent write 1 byte: THP disabled → kernel chỉ duplicate 1 trang 4 KB cho parent. THP enabled → kernel phải duplicate toàn bộ trang 2 MB (vì COW hoạt động ở granularity của trang, mà trang giờ là 2 MB) → 2 MB / 4 KB = 512× overhead cho cùng 1 byte write.
always: mỗi write phải đợi fsync hoàn thành → latency p99 tăng vài ms mỗi write, throughput giảm mạnh với write-heavy workload.everysec: background thread fsync 1 lần/giây, event loop không bị block → latency thấp hơn nhiều. Dùngalwayskhi Redis là primary storage và không thể mất bất kỳ command nào (trường hợp hiếm).- Diskless replication bỏ bước ghi file RDB tạm lên disk → không tốn disk I/O, fork child stream trực tiếp qua network, nhanh hơn khi network > disk speed. Disk-based tốt hơn khi: nhiều replica connect lần lượt (có thể reuse file RDB đã ghi), hoặc khi replica có thể disconnect và cần retry mà không fork lại master.
Bài tiếp theo
Bài 110 tổng hợp các anti-patterns khi scaling Redis và incident story cho Module 9.
