Phần trước đo TTL. Bài này đo chuyện xảy ra khi Redis chạm trần bộ nhớ — tám chính sách, cùng một phép thử.
Nạp 200.000 khoá vào 32 MB
| Chính sách | Lỗi ghi | Đã đuổi | Còn lại |
|---|---|---|---|
noeviction |
27.606 | 0 | 172.394 |
allkeys-lru |
0 | 27.676 | 172.324 |
allkeys-lfu |
0 | 27.678 | 172.322 |
allkeys-random |
0 | 27.682 | 172.318 |
volatile-lru (không khoá nào có TTL) |
27.610 | 0 | 172.390 |
Bốn dòng đầu đúng như tên gọi. Dòng cuối là cái bẫy.
volatile-* có thể chính là noeviction
volatile-lru với dữ liệu không khoá nào có TTL cho kết quả giống hệt noeviction: 27.610 lỗi ghi, 0 khoá bị đuổi.
Nó không có gì để đuổi, nên nó không đuổi gì cả — và trả OOM command not allowed cho mọi lệnh ghi, trong khi bộ nhớ đầy ắp khoá hoàn toàn có thể bỏ đi.
Trường hợp có khoá mang TTL còn rõ hơn. Tôi nạp 150.000 khoá có TTL rồi 200.000 khoá không TTL:
volatile-lru đuổi 150.000 | còn t:* (có TTL) 0 | còn k:* (không TTL) 166.395 | 33.605 lỗi
volatile-ttl đuổi 150.000 | còn t:* (có TTL) 0 | còn k:* (không TTL) 166.438 | 33.562 lỗi
volatile-random đuổi 150.000 | còn t:* (có TTL) 0 | còn k:* (không TTL) 166.115 | lỗi OOM
Cả ba đuổi sạch 150.000 khoá có TTL, rồi bắt đầu báo lỗi trong khi 166.000 khoá không TTL nằm nguyên.
Đây là chỗ dễ chọn sai nhất trong cấu hình Redis. volatile-lru nghe an toàn — "chỉ đuổi thứ vốn đã có hạn" — nhưng nó có nghĩa là: Redis sẽ ngừng nhận ghi trong khi phần lớn bộ nhớ vẫn giữ dữ liệu không ai bảo vệ.
Chọn volatile-* chỉ đúng khi bạn cố ý trộn hai loại dữ liệu trong một instance: loại phải giữ (không TTL) và loại bỏ được (có TTL). Nếu không có sự phân chia đó, dùng allkeys-*.
LFU giữ được khoá nóng, LRU thì gần như không
Tôi nạp 100.000 khoá nền, thêm 1.000 khoá và đọc mỗi khoá 200 lần, rồi đổ đầy bộ nhớ bằng khoá mới.
allkeys-lfu giữ lại 1.000 / 1.000
allkeys-lru giữ lại 545 / 1.000
allkeys-random giữ lại 476 / 1.000
LRU chỉ hơn random 14%. Với một mẫu truy cập lệch rõ rệt như thế này, đó là kết quả tệ.
Lý do: LRU của Redis là gần đúng. Nó không giữ danh sách thứ tự truy cập — làm vậy tốn con trỏ cho mọi khoá. Thay vào đó nó lấy mẫu maxmemory-samples khoá (mặc định 5) và đuổi cái cũ nhất trong năm cái đó. Với 300.000 khoá, xác suất năm cái ngẫu nhiên chứa đúng khoá cũ nhất là rất nhỏ.
Kiểm chứng bằng cách tăng số mẫu:
samples=5 563 / 1000
samples=10 572 / 1000
samples=20 594 / 1000
samples=50 896 / 1000
Đúng như dự đoán: nhiều mẫu hơn thì gần LRU thật hơn. Chi phí nạp tăng từ 0,2 lên 0,3 giây — không đáng kể ở đây, nhưng nó là công việc thêm cho luồng duy nhất mỗi lần cần đuổi.
Và điểm quan trọng nhất: LFU với số mẫu mặc định đã đạt 1.000/1.000 mà không cần chỉnh gì.
Vì sao LFU tốt hơn ở đây
LRU trả lời "khoá này được dùng gần đây không". LFU trả lời "khoá này được dùng nhiều không".
Với bộ đệm, câu hỏi thứ hai đúng hơn. Một khoá được đọc 200 lần rồi im lặng năm phút vẫn là khoá nóng; LRU coi nó ngang với khoá mới ghi một lần và chưa ai đọc.
Trường hợp LRU thắng là khi dữ liệu có tính cục bộ theo thời gian rõ: bộ đệm phiên đăng nhập, dữ liệu của "hôm nay". Ở đó thứ cũ thật sự vô dụng.
Với hầu hết bộ đệm chung, allkeys-lfu là lựa chọn mặc định tốt hơn — và nó không phải mặc định của Redis, nên bạn phải tự đặt.
redis-cli config set maxmemory-policy allkeys-lfu
LFU có hai tham số hiếm khi cần đụng tới: lfu-log-factor (mặc định 10, độ nhạy của bộ đếm) và lfu-decay-time (mặc định 1 phút, tốc độ nguội của khoá không được dùng). Chỉ chỉnh khi đã đo.
maxmemory chưa đặt là chưa có chính sách nào
Mặc định maxmemory 0 — không giới hạn. Khi đó không chính sách nào có tác dụng, và Redis lớn tới lúc hệ điều hành giết nó.
Cái chết đó tệ hơn OOM nhiều: OOM là lỗi ghi mà ứng dụng bắt được; bị hệ điều hành giết là mất toàn bộ dữ liệu trong RAM cùng lúc.
Đặt maxmemory khoảng 60–70% RAM của máy. Phần dư không phải lãng phí — nó dành cho bản sao lưu nền (fork lúc lưu RDB), bộ đệm đầu ra của khách, và phân mảnh của bộ cấp phát. Phần sau của sê-ri đo từng khoản đó.
Chọn nhanh
| Redis dùng để | Chính sách |
|---|---|
| Bộ đệm thuần | allkeys-lfu |
| Bộ đệm có tính thời gian rõ | allkeys-lru |
| Lưu dữ liệu không được mất | noeviction + cảnh báo bộ nhớ |
| Trộn dữ liệu bền và bộ đệm | volatile-lru, và mọi khoá bộ đệm phải có TTL |
Dòng cuối kèm điều kiện bắt buộc: nếu một khoá bộ đệm quên đặt TTL, nó trở thành bất tử và ăn dần chỗ của những khoá được dọn đúng cách.
Thử ba mươi giây
Kiểm ba thứ cùng lúc:
redis-cli config get maxmemory maxmemory-policy | paste - -
redis-cli info keyspace
redis-cli info stats | grep -E 'evicted_keys|keyspace_misses|keyspace_hits'
Ba dấu hiệu cần chú ý:
maxmemory 0— chưa có trần, mọi chính sách vô nghĩa.- Chính sách
volatile-*màexpiresnhỏ hơnkeysnhiều — bạn đang ở đúng cái bẫy trong bài này. evicted_keystăng đều vàkeyspace_missestăng theo — bộ đệm đang đuổi thứ nó sẽ cần lại ngay. Đó là lúc nên đổi sang LFU, hoặc thêm RAM.
Phần sau: phân mảnh bộ nhớ — đo con số mem_fragmentation_ratio thật sự nói gì.