Danh sách bài viết

Bài 105: Memory Tuning — Encoding, maxmemory, Fragmentation

Redis lưu toàn bộ dữ liệu trong RAM. Đó là nguồn gốc của tốc độ, đồng thời là giới hạn cứng nhất trong production. Bài này đi qua ba nhóm vấn đề cốt lõi: encoding internals (khi nào Redis dùng listpack compact vs skiplist/hashtable tiêu tốn hơn, làm sao giữ encoding nhỏ), maxmemory và eviction policies (cap memory, chọn policy phù hợp use case), và memory fragmentation (fragmentation ratio, active defragmentation, jemalloc). Kèm theo OS-level tuning, các anti-pattern phổ biến và checklist best practice.

01/06/2026
18 phút đọc
0 lượt xem
1

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_ratio từ 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 maxmemory và 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, noeviction và 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.
2

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 noeviction và đã đầ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:

  1. Encoding optimization: chọn data structure nội bộ compact, chia key để tránh big key.
  2. maxmemory + eviction: cap cứng memory, định nghĩa hành vi khi đầy.
  3. Defragmentation + allocator tuning: dọn memory holes, cấu hình OS.
3

Đọ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)
4

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

5

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
6

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.

7

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.

8

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_memory và alert trước khi chạm maxmemory.
  • 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.

9

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)
10

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.

11

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.

12

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:

  1. Redis scan một phần nhỏ keyspace trong mỗi event loop cycle.
  2. 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ũ.
  3. 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
13

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.

14

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

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_count thấ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
16

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.

17

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-time quá 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 maxmemory với policy phù hợp use case.
  • Giữ hash/sorted set < 128 entries để tận dụng listpack.
  • Bật activedefrag yes nếu mem_fragmentation_ratio thườ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_ratio và alert khi > 1.5.
  • Chạy redis-cli --bigkeys định kỳ để phát hiện key tăng trưởng bất thường.
18

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_memory là dataset; used_memory_rss là 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ọn allkeys-lru cho pure cache, volatile-lru khi mix persistent + cache, noeviction khi dữ liệu không được phép mất.
  • mem_fragmentation_ratio > 1.5: cân nhắc bật activedefrag 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

  1. 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?
  2. used_memory_rss cao hơn used_memory nhiều (ratio ~2.0). Điều này có nghĩa gì và bạn sẽ làm gì tiếp theo?
  3. Phân biệt allkeys-lruvolatile-lru. Khi nào dùng cái nào?
  4. 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?
  5. 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 ý

  1. Redis dùng listpack khi entries ≤ 128 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.
  2. 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 yes nếu chưa bật; (2) kiểm tra allocator_frag_ratio để xác nhận nguồn gốc; (3) xem xét pattern xoá/ghi key có gây fragmentation.
  3. allkeys-lru evict bất kỳ key nào (kể cả không có TTL); volatile-lru chỉ evict keys có TTL. Dùng allkeys-lru khi Redis là pure cache (mọi key đều có thể bị xoá); dùng volatile-lru khi Redis lưu cả dữ liệu persistent (không TTL) lẫn cached data (có TTL) — cần bảo vệ data persistent.
  4. 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.
  5. 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.

Tham khảo