Mục lục
- Mục Tiêu Bài Học
- Bài Toán: Tại Sao Memory Lại Phức Tạp
- Đọc Memory Stats Từ INFO
- Encoding Internals: Compact vs Standard
- Config Ngưỡng Encoding
- Detect Encoding & Chiến Lược Giữ Compact
- maxmemory & Eviction Policies
- Chọn Policy Theo Use Case
- LFU Tuning: log-factor & decay-time
- maxmemory-samples: Độ Chính Xác vs CPU
- Memory Fragmentation
- Active Defragmentation
- Key Naming & String Overhead
- Application-side Compression
- Scan & MEMORY USAGE
- OS-level Tuning
- Anti-patterns & Best Practices
- Tổng Kết & Quiz
Mục Tiêu Bài Học
- Đọc và phân tích các trường
used_memory,used_memory_rss,mem_fragmentation_ratiotừINFO memory. - Hiểu khi nào Redis dùng encoding compact (listpack, intset) và khi nào upgrade lên standard (skiplist, hashtable).
- Cấu hình ngưỡng encoding để tiết kiệm memory cho hash, list, set, sorted set.
- Thiết lập
maxmemoryvà chọn eviction policy phù hợp với từng use case. - Phân biệt các policy:
allkeys-lru,allkeys-lfu,volatile-lru,noevictionvà khi nào dùng cái nào. - Nhận biết và xử lý memory fragmentation bằng active defragmentation.
- Áp dụng OS-level tuning cần thiết để Redis hoạt động ổn định.
Bài Toán: Tại Sao Memory Lại Phức Tạp
Redis lưu dữ liệu trong RAM — đó cũng là lý do nó nhanh. Nhưng RAM đắt và có giới hạn vật lý. Trong production, ba triệu chứng phổ biến khi memory không được quản lý tốt:
- OOM (Out of Memory): Redis bị hệ điều hành kill, hoặc từ chối write khi
noevictionvà đã đầy memory. - Fragmentation: allocator giữ memory nhưng không trả lại OS — RSS phình to dù dataset thực sự nhỏ hơn, có thể trigger OOM killer nhầm.
- Swap death: khi Redis dùng swap, latency tăng từ sub-millisecond lên hàng trăm ms hoặc giây — hệ thống thực tế coi đây là sự cố nghiêm trọng.
Ba nhóm giải pháp tương ứng:
- Encoding optimization: chọn data structure nội bộ compact, chia key để tránh big key.
- maxmemory + eviction: cap cứng memory, định nghĩa hành vi khi đầy.
- Defragmentation + allocator tuning: dọn memory holes, cấu hình OS.
Đọc Memory Stats Từ INFO
Điểm xuất phát cho bất kỳ việc tuning nào là đọc INFO memory:
redis-cli INFO memory
Các trường quan trọng:
used_memory:135266304 # dataset + overhead nội bộ Redis (bytes)
used_memory_human:129.02M
used_memory_rss:201326592 # resident set size — RAM thực sự được OS cấp
used_memory_rss_human:192.00M
used_memory_peak:148013056 # high-water mark của used_memory
mem_fragmentation_ratio:1.49 # RSS / used_memory
mem_fragmentation_bytes:66060288
allocator_frag_ratio:1.42
allocator_rss_ratio:1.05
mem_allocator:jemalloc-5.3.0
Cách đọc:
used_memory: bộ nhớ Redis cho rằng nó đang dùng — dataset thực tế cộng các cấu trúc nội bộ.used_memory_rss: bộ nhớ thực sự nằm trong RAM theo quan sát của OS. Con số này luôn ≥used_memory.mem_fragmentation_ratio: tỉ lệ RSS/used_memory. Gần 1.0 là lý tưởng; > 1.5 có vấn đề fragmentation; < 1.0 nghĩa là một phần dữ liệu đã bị swap ra đĩa — nguy hiểm.
Xem nhanh memory của một key cụ thể:
MEMORY USAGE user:1001
# trả về bytes, bao gồm overhead của key
MEMORY USAGE user:1001 SAMPLES 5
# SAMPLES N: tính average từ N phần tử mẫu (cho aggregate types)
Encoding Internals: Compact vs Standard
Redis dùng hai encoding nội bộ cho mỗi data type: một dạng compact cho dữ liệu nhỏ và một dạng standard khi dữ liệu vượt ngưỡng. Hiểu cơ chế này là nền tảng của memory optimization.
Bảng encoding theo data type
| Type | Compact encoding | Standard encoding | Điều kiện upgrade |
|---|---|---|---|
| Hash | listpack | hashtable | entries > 128 hoặc field/value > 64 bytes |
| List | listpack | quicklist | size > 128 hoặc element > 64 bytes |
| Set (integers) | intset | hashtable | entries > 512 hoặc member không phải integer |
| Set (strings) | listpack | hashtable | entries > 128 hoặc member > 64 bytes |
| Sorted Set | listpack | skiplist + hashtable | entries > 128 hoặc member > 64 bytes |
| String | int / embstr | raw | số nguyên vừa long; string ≤ 44 bytes → embstr |
Lưu ý: "upgrade" là một chiều. Khi hash đã upgrade lên hashtable do thêm phần tử thứ 129, việc xoá bớt về dưới 128 không tự động downgrade về listpack. Redis chỉ encode lại khi key bị xoá rồi tạo lại từ đầu.
Listpack là gì
Listpack (giới thiệu chính thức từ Redis 7.0, thay thế ziplist cũ) là một mảng liên tục (contiguous array) các entry được đóng gói chặt. Mỗi entry chứa: encoding byte, độ dài content, content và độ dài entry kề trước (để traverse ngược). Không có con trỏ rời rạc, không có header hash table.
Kết quả:
- Memory tiết kiệm 60–80% so với hashtable/skiplist với cùng dữ liệu nhỏ.
- Cache-friendly: toàn bộ dữ liệu nằm trong một vùng nhớ liên tục.
- Trade-off: các thao tác là O(N) — chấp nhận được khi N nhỏ (dưới 128).
Intset là gì
Intset là mảng sorted của integer thuần, được lưu liên tục trong bộ nhớ. Hỗ trợ encoding 16/32/64-bit để tối thiểu hoá dung lượng. Lookup bằng binary search O(log N). Khi thêm một string member, toàn bộ set chuyển sang hashtable.
String embedding
String ≤ 44 bytes được lưu dạng embstr: Redis object header và string data nằm trong một alloc duy nhất — giảm pointer overhead và allocation call. String > 44 bytes dùng raw: hai alloc riêng biệt (object header + SDS buffer).
Config Ngưỡng Encoding
Default của Redis thường hợp lý cho hầu hết trường hợp, nhưng có thể điều chỉnh theo đặc thù dataset:
# redis.conf
# Hash: dùng listpack khi entries <= 128 và mỗi field/value <= 64 bytes
hash-max-listpack-entries 128
hash-max-listpack-value 64
# List: listpack khi <= 128 entries; -2 = mỗi node quicklist tối đa 8KB
list-max-listpack-size -2
# Set (string members): listpack khi <= 128 members <= 64 bytes
set-max-listpack-entries 128
set-max-listpack-value 64
# Set (integer members): intset khi <= 512 integer members
set-max-intset-entries 512
# Sorted Set: listpack khi <= 128 entries và member <= 64 bytes
zset-max-listpack-entries 128
zset-max-listpack-value 64
Tăng ngưỡng entry (ví dụ lên 256) có thể tiết kiệm thêm memory nếu dataset thực sự có nhiều key nhỏ, nhưng các thao tác O(N) sẽ chậm hơn. Cần benchmark với workload thực tế trước khi tăng.
Áp dụng config mà không restart (nếu persistence không cần đồng bộ ngay):
CONFIG SET hash-max-listpack-entries 128
CONFIG SET hash-max-listpack-value 64
Detect Encoding & Chiến Lược Giữ Compact
Kiểm tra encoding hiện tại của một key:
OBJECT ENCODING mykey
# Trả về: listpack | hashtable | skiplist | intset | quicklist | ziplist | embstr | raw | int
Ví dụ thực tế:
# Hash nhỏ → listpack
HSET user:1 name "Alice" age "30" city "HN"
OBJECT ENCODING user:1
# => "listpack"
# Thêm field vượt 128 → hashtable
# (sau khi HSET đủ số field)
OBJECT ENCODING user:1
# => "hashtable"
# Set với toàn số nguyên → intset
SADD primes 2 3 5 7 11 13
OBJECT ENCODING primes
# => "intset"
# Thêm string → hashtable
SADD primes "seventeen"
OBJECT ENCODING primes
# => "hashtable"
Pattern: hash sharding để giữ listpack
Vấn đề: một hash với 100.000 field dùng hashtable, tốn memory và các thao tác scan toàn bộ hash sẽ chậm. Giải pháp là chia thành nhiều hash nhỏ, mỗi cái giữ < 128 field:
import redis
r = redis.Redis()
def hash_slot(user_id: int, buckets: int = 1000) -> str:
"""Tính bucket key cho user_id."""
return f"users:{user_id // buckets}"
def set_user_field(user_id: int, field: str, value: str):
key = hash_slot(user_id)
r.hset(key, f"{user_id}:{field}", value)
def get_user_field(user_id: int, field: str) -> str | None:
key = hash_slot(user_id)
return r.hget(key, f"{user_id}:{field}")
# Ví dụ: user 5432 → bucket "users:5"
# Mỗi bucket tối đa 1000 users × N fields
# Cần cân bằng: N fields × 1000 users vẫn phải < 128 để giữ listpack
# Thực tế: điều chỉnh bucket size theo số field mỗi user
Pattern này phổ biến khi lưu profile dữ liệu dạng thưa (sparse). Với user có ít field, một bucket 1000 users vẫn < 128 field tổng → listpack. Với user có nhiều field, chia bucket nhỏ hơn.
maxmemory & Eviction Policies
maxmemory đặt giới hạn cứng cho used_memory. Khi đạt ngưỡng, Redis áp dụng eviction policy để giải phóng chỗ trước khi xử lý lệnh write tiếp theo.
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
Nếu không set maxmemory, Redis không có giới hạn và sẽ tiếp tục phát triển cho đến khi OOM killer của OS can thiệp — thường là hậu quả tệ nhất.
Các eviction policy
| Policy | Hành vi | Áp dụng cho |
|---|---|---|
noeviction |
Từ chối write; trả lỗi OOM. Read và DEL vẫn hoạt động. | Default. Dữ liệu không được phép mất. |
allkeys-lru |
Evict key ít dùng gần đây nhất trong toàn bộ keyspace. | Cache thuần — không quan tâm key nào bị xoá. |
allkeys-lfu |
Evict key ít được truy cập nhất (theo tần suất). Redis 4+. | Cache có hot subset rõ ràng. |
allkeys-random |
Evict ngẫu nhiên trong toàn bộ keyspace. | Hiếm dùng trong production thực tế. |
volatile-lru |
Evict LRU chỉ trong số keys có TTL (expire). | Lưu cả dữ liệu persistent (không TTL) lẫn cache (có TTL). |
volatile-lfu |
LFU chỉ keys có TTL. | Tương tự volatile-lru nhưng dùng tần suất. |
volatile-random |
Random chỉ keys có TTL. | Hiếm dùng. |
volatile-ttl |
Evict key có TTL ngắn nhất trước. | Khi TTL phản ánh mức độ ưu tiên giữ lại. |
Lưu ý quan trọng: LRU và LFU trong Redis là xấp xỉ (approximation), không phải chính xác 100%. Redis không duyệt toàn bộ keyspace khi evict — xem phần tiếp theo về maxmemory-samples.
Chọn Policy Theo Use Case
Cache thuần (pure cache)
Tất cả key đều có thể bị xoá vì DB là source of truth. Dùng allkeys-lru hoặc allkeys-lfu.
allkeys-lru: tốt cho workload có temporal locality — dữ liệu mới được dùng lại nhiều hơn cũ.allkeys-lfu: tốt hơn khi có hot keys ổn định — một số key luôn được truy cập dù không phải gần nhất.
Mix: persistent data + cache
Một số key không bao giờ có TTL (config hệ thống, session counter), một số có TTL (cached query). Dùng volatile-lru: chỉ evict key có TTL, giữ nguyên key không TTL.
# Key persistent → không TTL
SET config:feature_flags "{...}"
# Key cache → có TTL
SET cache:query:abc123 "{...}" EX 300
# volatile-lru sẽ chỉ evict key cache:query:* khi đầy
Session store
Session không được mất trước khi TTL hết hạn. Dùng noeviction kết hợp với:
- Set TTL cho mọi session key.
- Monitor
used_memoryvà alert trước khi chạmmaxmemory. - Scale out (thêm node) trước khi đầy, không trông chờ eviction.
Analytics counter
Counter cần persistent (không có TTL), nhưng hot counter hiện tại quan trọng hơn cold counter cũ. Dùng volatile-lru: counter cũ nếu cần TTL có thể đặt, counter realtime thì không TTL.
LFU Tuning: log-factor & decay-time
LFU trong Redis không đếm access count chính xác mà dùng một counter 8-bit với hai cơ chế:
- Logarithmic increment: mỗi lần access, counter tăng với xác suất
1 / (counter × lfu-log-factor + 1). Counter nhỏ tăng nhanh; counter lớn tăng chậm — tránh bão hoà với hot keys. - Decay: counter giảm theo thời gian không dùng, tránh key hot trong quá khứ chiếm chỗ mãi.
# redis.conf
# Log factor: default 10
# - Nhỏ hơn = counter tăng nhanh hơn → phân biệt hot/cold rõ hơn ở low access count
# - Lớn hơn = cần nhiều access để tăng counter → phân biệt tốt hơn ở high access count
lfu-log-factor 10
# Decay time: minutes (default 1)
# - Nhỏ hơn = counter decay nhanh → phản ứng nhanh với workload thay đổi
# - Lớn hơn = giữ "ký ức" access lâu hơn
lfu-decay-time 1
Với workload có hot keys ổn định theo giờ (ví dụ trang chủ, trending list), tăng lfu-decay-time lên 5–10. Với workload hot keys thay đổi nhanh (flash sale, trending topic), giữ decay-time nhỏ.
Kiểm tra LFU counter của key:
OBJECT FREQ mykey
# trả về counter hiện tại (0-255)
maxmemory-samples: Độ Chính Xác vs CPU
Redis không scan toàn bộ keyspace khi evict — điều đó sẽ quá tốn kém. Thay vào đó, Redis lấy mẫu ngẫu nhiên maxmemory-samples keys rồi chọn key "tệ nhất" (LRU least recent, hoặc LFU least frequent) trong số đó để evict.
# redis.conf
maxmemory-samples 5 # default
Tăng samples → eviction chọn được key thực sự tệ hơn, gần với LRU/LFU chính xác hơn, nhưng tốn thêm CPU mỗi lần evict. Giảm samples → nhanh hơn nhưng ít chính xác hơn.
| samples | Gần LRU chính xác | CPU overhead |
|---|---|---|
| 3 | Thấp | Rất nhỏ |
| 5 (default) | Trung bình, đủ dùng | Nhỏ |
| 10 | Gần với LRU chính xác | Tăng đáng kể |
Trong hầu hết production, mặc định 5 là đủ. Tăng lên 10 chỉ khi hit ratio quan trọng và hệ thống có CPU headroom.
Memory Fragmentation
Memory allocator (jemalloc) chia bộ nhớ thành các bin có kích thước cố định (8B, 16B, 32B, 64B, ...). Khi Redis xoá key, vùng nhớ đó được đánh dấu free trong allocator nhưng thường không trả ngay lại OS. Kết quả là RSS > used_memory.
Fragmentation xảy ra nhiều khi:
- Nhiều thao tác xoá và tạo lại key liên tục với kích thước không đồng đều.
- Key expire hàng loạt (ví dụ TTL cùng hết lúc nửa đêm).
- Thay đổi encoding (ziplist → hashtable) tạo nhiều alloc/free.
Đọc các chỉ số fragmentation:
redis-cli INFO memory | grep -E "frag|rss|used_memory[^_]"
# Interpretation:
# mem_fragmentation_ratio < 1.0 → có data trong swap → nguy hiểm
# mem_fragmentation_ratio 1.0–1.5 → bình thường
# mem_fragmentation_ratio > 1.5 → fragmentation cao → cân nhắc defrag
# mem_fragmentation_ratio > 2.0 → nghiêm trọng
Cũng xem allocator_frag_ratio (fragmentation trong allocator) và allocator_rss_ratio (RSS overhead từ OS) để phân biệt nguồn gốc fragmentation.
Active Defragmentation
Redis 4+ hỗ trợ active defragmentation (defrag trực tuyến, không cần restart): Redis dần dần di chuyển object sang vùng nhớ mới compact hơn, để allocator giải phóng holes cũ cho OS.
# redis.conf
# Bật active defrag (mặc định off)
activedefrag yes
# Chỉ bắt đầu defrag khi fragmentation > ignore threshold
active-defrag-ignore-bytes 100mb
# Bắt đầu defrag khi frag ratio > lower threshold (10%)
active-defrag-threshold-lower 10
# Tốc độ defrag tối đa khi frag ratio đạt upper threshold (100%)
active-defrag-threshold-upper 100
# CPU effort: min và max % CPU dành cho defrag
active-defrag-cycle-min 1
active-defrag-cycle-max 25
Cách active defrag hoạt động:
- Redis scan một phần nhỏ keyspace trong mỗi event loop cycle.
- Nếu object nằm trong vùng nhớ bị fragmented, Redis alloc vùng mới, copy object, cập nhật pointer và free vùng cũ.
- jemalloc dần dần có thể trả pages trống lại cho OS.
Active defrag tiêu thụ CPU — lượng CPU tỉ lệ với fragmentation và cấu hình active-defrag-cycle-max. Không bật mặc định vì nhiều deployment không cần đến nó.
Bật/tắt runtime mà không restart:
CONFIG SET activedefrag yes
CONFIG SET active-defrag-ignore-bytes 100mb
Kiểm tra tiến trình defrag:
redis-cli INFO stats | grep defrag
# active_defrag_running:1
# active_defrag_hits:12453
# active_defrag_misses:103
# active_defrag_key_hits:8901
Key Naming & String Overhead
Tên key được lưu dưới dạng SDS string trong Redis — tốn memory như bất kỳ string nào khác. Với hàng triệu key, overhead từ key name cộng lại đáng kể.
# Key verbose: 52 bytes
user:profile:settings:notifications:email:enabled
# Key ngắn: 9 bytes
u:p:s:n:e:e
# Với 10 triệu keys: tiết kiệm ~430MB chỉ từ tên key
Cân nhắc thực tế:
- Prefix ngắn tiết kiệm memory nhưng giảm readability khi debug.
- Một quy ước phổ biến: dùng tên đầy đủ trong staging/dev, tên ngắn trong production nếu có documentation.
- Quan trọng hơn: tránh key verbose trong hot path có hàng triệu key. Với vài nghìn key, tên đầy đủ không quan trọng.
String value overhead
String ≤ 44 bytes: embstr encoding — Redis object header (16 bytes) và string nằm cùng allocation. String > 44 bytes: raw encoding — header và SDS buffer là hai allocation riêng biệt. Nếu value hay được update và thường < 44 bytes, giữ dưới 44 bytes để tận dụng embstr.
Application-side Compression
Redis không có built-in compression. Với value là JSON hoặc text lớn, ứng dụng có thể compress trước khi lưu:
import redis
import gzip
import json
r = redis.Redis()
def set_compressed(key: str, data: dict, ttl: int = 300):
raw = json.dumps(data).encode("utf-8")
compressed = gzip.compress(raw)
r.set(key, compressed, ex=ttl)
return len(raw), len(compressed)
def get_compressed(key: str) -> dict | None:
data = r.get(key)
if data is None:
return None
raw = gzip.decompress(data)
return json.loads(raw)
# Ví dụ: object 5KB JSON → 1-2KB sau gzip (tỉ lệ tuỳ thuộc vào content)
original, compressed = set_compressed("report:2026-06", large_report)
print(f"Original: {original}B, Compressed: {compressed}B")
Trade-off:
- CPU thêm ở mỗi read/write — thường nhỏ so với latency network.
- Giảm memory Redis 60–80% cho JSON text lớn.
- Giảm băng thông Redis ↔ application server.
- Không hiệu quả cho value nhỏ (< 100 bytes) — gzip header overhead lớn hơn saving.
Scan & MEMORY USAGE
Để tìm keys tốn nhiều memory nhất, kết hợp SCAN (cursor-based, không block) với MEMORY USAGE:
import redis
r = redis.Redis(decode_responses=False)
def find_heavy_keys(top_n: int = 20, scan_count: int = 100):
"""Scan toàn bộ keyspace, trả về top_n keys tốn memory nhất."""
cursor = 0
keys_memory: list[tuple[bytes, int]] = []
while True:
cursor, batch = r.scan(cursor, count=scan_count)
for key in batch:
mem = r.memory_usage(key, samples=5)
if mem:
keys_memory.append((key, mem))
if cursor == 0:
break
keys_memory.sort(key=lambda x: -x[1])
return keys_memory[:top_n]
results = find_heavy_keys()
for key, mem in results:
print(f"{key.decode()}: {mem / 1024:.1f} KB")
Lưu ý khi chạy trên production:
- SCAN không block nhưng với keyspace hàng chục triệu key, toàn bộ scan sẽ mất nhiều phút.
- Chạy vào giờ thấp điểm hoặc dùng replica để không ảnh hưởng primary.
- Giới hạn
scan_countthấp nếu lo ngại latency.
Ngoài ra, redis-cli --bigkeys là công cụ nhanh hơn để scan và báo cáo big key theo type:
redis-cli --bigkeys
# Scan toàn keyspace, báo top key lớn nhất theo type
# Dùng SCAN nội bộ, an toàn cho production nhưng chậm với keyspace lớn
OS-level Tuning
Redis phụ thuộc vào cách OS quản lý memory. Một số kernel parameter cần thiết lập:
# /etc/sysctl.conf hoặc /etc/sysctl.d/redis.conf
# 1. Cho phép overcommit memory
# Redis fork khi BGSAVE/BGREWRITEAOF — cần virtual memory gấp đôi tạm thời
# Không có overcommit=1, fork có thể thất bại với ENOMEM dù RAM vật lý còn
vm.overcommit_memory = 1
# 2. Tắt swap hoàn toàn hoặc giảm swappiness về mức tối thiểu
# swap=0 là lý tưởng cho Redis (latency sẽ tăng đột biến khi swap)
vm.swappiness = 1
# Áp dụng ngay
sysctl -p
# Tắt Transparent Huge Pages (THP)
# THP gây latency spike và tăng memory sau fork vì copy-on-write granularity lớn hơn
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
# Persistent qua reboot — thêm vào /etc/rc.local hoặc systemd service
# File descriptor limit
# Redis cần ~1 FD per client connection + một số FD nội bộ
ulimit -n 65535
# Persistent qua /etc/security/limits.conf:
# redis soft nofile 65535
# redis hard nofile 65535
Redis tự kiểm tra và cảnh báo về những setting này khi khởi động. Nếu thấy log cảnh báo về THP hay overcommit khi restart Redis, đó là dấu hiệu chưa áp dụng OS tuning.
Anti-patterns & Best Practices
Anti-patterns
- Không set
maxmemory: Redis tăng không giới hạn cho đến khi OOM killer can thiệp — instance crash đột ngột. noeviction+ dataset tăng trưởng liên tục: khi đầy, mọi write đều bị từ chối lỗi OOM. Application cần xử lý lỗi này hoặc scale trước.- Big hash/sorted set không chia: hash 1M field dùng hashtable encoding, O(N) HGETALL block toàn bộ server.
- Tên key quá dài với hàng triệu key: overhead cộng lại đáng kể.
- Bật swap: latency từ <1ms lên hàng trăm ms đến giây khi Redis bắt đầu page fault.
- Transparent Huge Pages bật: sau fork (BGSAVE), copy-on-write với 2MB pages thay vì 4KB pages → memory spike gấp đôi trong thời gian bgsave.
- LFU với
lfu-decay-timequá lớn: counter stale, key hot cũ "chiếm giữ" cache, key hot mới không được ưu tiên đúng mức. - Gzip toàn bộ value kể cả value nhỏ: overhead gzip header lớn hơn saving với value < 100 bytes.
Best practices tóm gọn
- Luôn set
maxmemoryvới policy phù hợp use case. - Giữ hash/sorted set < 128 entries để tận dụng listpack.
- Bật
activedefrag yesnếumem_fragmentation_ratiothường xuyên > 1.5. - Tắt swap (
vm.swappiness=1) và THP (transparent_hugepage=never). - Set
vm.overcommit_memory=1để BGSAVE không thất bại. - Compress JSON lớn ở application layer trước khi lưu Redis.
- Dùng key prefix ngắn khi keyspace hàng triệu entries.
- Monitor
mem_fragmentation_ratiovà alert khi > 1.5. - Chạy
redis-cli --bigkeysđịnh kỳ để phát hiện key tăng trưởng bất thường.
Tổng Kết & Quiz
Tổng kết
- Redis dùng encoding compact (listpack, intset) cho dữ liệu nhỏ và tự động upgrade lên encoding standard khi vượt ngưỡng — upgrade là một chiều.
used_memorylà dataset;used_memory_rsslà RAM thực OS cấp; chênh lệch = fragmentation + overhead.- Giữ hash/set/sorted set dưới ngưỡng
max-listpack-entries(128) để duy trì listpack encoding và tiết kiệm 60–80% memory so với hashtable/skiplist. - Luôn set
maxmemory; chọnallkeys-lrucho pure cache,volatile-lrukhi mix persistent + cache,noevictionkhi dữ liệu không được phép mất. mem_fragmentation_ratio > 1.5: cân nhắc bậtactivedefrag yes. Ratio < 1.0: dữ liệu trong swap — nguy hiểm.- OS: tắt swap, tắt THP, bật overcommit, tăng ulimit — Redis cảnh báo nếu thiếu những setting này lúc khởi động.
Quiz 5 câu
- Redis dùng listpack encoding cho hash khi nào? Điều gì xảy ra sau khi hash đã upgrade lên hashtable và bạn xoá bớt field về dưới ngưỡng?
used_memory_rsscao hơnused_memorynhiều (ratio ~2.0). Điều này có nghĩa gì và bạn sẽ làm gì tiếp theo?- Phân biệt
allkeys-lruvàvolatile-lru. Khi nào dùng cái nào? - Tại sao cần tắt Transparent Huge Pages khi chạy Redis? Cơ chế nào của Redis bị ảnh hưởng?
- Bạn có một Redis instance với maxmemory 8GB, policy
noeviction, dataset đang tăng 500MB/ngày. Alert ngưỡng nên đặt ở đâu và tại sao?
Đáp án gợi ý
- Redis dùng listpack khi entries ≤ 128 và mỗi field/value ≤ 64 bytes (cấu hình mặc định). Sau khi đã upgrade lên hashtable, Redis không tự downgrade — key phải bị xoá và tạo lại từ đầu mới có thể về listpack.
- Fragmentation ratio ~2.0 nghĩa là allocator đang giữ gần gấp đôi memory thực sự cần — nhiều holes trong heap. Bước tiếp: (1) bật
activedefrag yesnếu chưa bật; (2) kiểm traallocator_frag_ratiođể xác nhận nguồn gốc; (3) xem xét pattern xoá/ghi key có gây fragmentation. allkeys-lruevict bất kỳ key nào (kể cả không có TTL);volatile-lruchỉ evict keys có TTL. Dùngallkeys-lrukhi Redis là pure cache (mọi key đều có thể bị xoá); dùngvolatile-lrukhi Redis lưu cả dữ liệu persistent (không TTL) lẫn cached data (có TTL) — cần bảo vệ data persistent.- THP gom các 4KB page thành 2MB page. Khi Redis fork để BGSAVE, copy-on-write được kích hoạt. Với THP, một write nhỏ có thể trigger copy cả page 2MB thay vì 4KB → memory tăng đột biến gấp nhiều lần trong thời gian persistence, có thể trigger OOM.
- Alert nên đặt ở ~75–80% maxmemory (6–6.4GB). Lý do: với
noeviction, khi chạm maxmemory, mọi write đều bị từ chối ngay — không có buffer để xử lý. Alert sớm ở 75% cho đủ thời gian scale up/out hoặc tăng maxmemory trước khi hệ thống không nhận write.
Bài tiếp theo
Bài 106 đi sâu hơn vào eviction policies: so sánh LRU và LFU với đo lường thực tế, workload pattern nào phù hợp với policy nào, và cách tune để đạt hit ratio tối ưu.
