mem_fragmentation_ratio là chỉ số hay bị hiểu sai nhất trong INFO memory. Bài này đo nó ở ba trạng thái khác nhau để thấy nó nói gì và không nói gì.

Ba trạng thái với ba tỷ lệ phân mảnh, và kết quả bật activedefrag

Ba trạng thái, ba con số

Trạng thái used_memory used_memory_rss Tỷ lệ
Gần như rỗng 2,6 MB 42,6 MB 16,59
Nạp 300.000 khoá 200 byte 90,7 MB 107,4 MB 1,18
Xoá 90% số khoá 11,5 MB 107,6 MB 9,39

Dòng cuối là chỗ đáng dừng lại: xoá 270.000 khoá và RSS không giảm một byte nào. Redis trả 79 MB về cho bộ cấp phát, và bộ cấp phát giữ lại toàn bộ.

Tỷ lệ một mình không nói được gì

Dòng đầu giải thích vì sao không nên cảnh báo trên mem_fragmentation_ratio.

Một instance gần rỗng có tỷ lệ 16,59 và hoàn toàn khoẻ mạnh. 42,6 MB RSS đó là mã Redis, ngăn xếp, bộ đệm — những thứ không tỷ lệ với dữ liệu. Chia một số cố định cho một số nhỏ thì ra một số lớn, và nó chẳng nói lên điều gì.

Con số đáng đọc là allocator_frag_bytes:

allocator_frag_bytes: 82.439.560

82 MB đã cấp phát mà không chứa gì. Đó là byte thật — so được với maxmemory, quyết định được có đáng làm gì không.

Quy tắc thực dụng: cảnh báo trên allocator_frag_bytes, không trên tỷ lệ. Và chỉ xem tỷ lệ khi used_memory đã đủ lớn để nó có nghĩa.

Cũng nên phân biệt hai cặp chỉ số:

  • mem_fragmentation_ratio = rss / used_memory — gồm cả phần không do bộ cấp phát gây ra.
  • allocator_frag_ratio = phần thật sự thuộc về jemalloc.

Ở phép đo trên, hai con số là 9,39 và 8,10 — gần nhau, nên phân mảnh đúng là do bộ cấp phát. Nếu chúng lệch nhiều thì phần chênh nằm ở chỗ khác: bộ đệm sao chép, fork để lưu RDB, hoặc bộ đệm đầu ra của khách.

Vì sao bộ cấp phát không trả lại

jemalloc chia bộ nhớ thành các trang lớn và cắt nhỏ theo lớp kích thước. Một trang chỉ trả về hệ điều hành khi mọi đối tượng trong đó đã được giải phóng.

Sau khi xoá 90% khoá ngẫu nhiên, gần như mọi trang còn ít nhất một đối tượng sống. Nghĩa là gần như không trang nào trả về được, dù 90% chỗ trong đó đã trống.

Đây là lý do phân mảnh nặng nhất xảy ra sau xoá rải rác, chứ không phải sau xoá theo cụm. Xoá theo tiền tố khoá — nếu chúng được ghi liên tiếp — thường trả lại nhiều hơn.

Bật activedefrag xong vẫn không có gì xảy ra

config set activedefrag yes
   t=10s  rss 97,4 MB  frag 8,91
   t=20s  rss 97,4 MB  frag 8,91
   t=30s  rss 97,4 MB  frag 8,91

Ba mươi giây, không thay đổi gì.

Tôi tưởng cấu hình không được nhận. Nó được nhận — config get activedefrag trả về yes. Vấn đề ở tham số khác:

active-defrag-ignore-bytes = 104857600   (100 MB)
active-defrag-threshold-lower = 10       (%)

Redis chỉ bắt đầu chống phân mảnh khi cả hai điều kiện đúng: chỗ phí vượt 100 MB tỷ lệ phí vượt 10%. Chỗ phí của tôi là 82 MB — chưa đủ.

Hạ ngưỡng xuống:

config set active-defrag-ignore-bytes 10mb

   t=10s  rss 97,4 MB  frag 8,91
   t=20s  rss 96,2 MB  frag 8,80
   t=40s  rss 48,0 MB  frag 4,39     đã trả về 49 MB
   t=60s  rss 48,0 MB  frag 4,39

RSS giảm một nửa trong khoảng 40 giây, sau 67.497 lần di chuyển đối tượng.

Bài học: activedefrag yes một mình thường là lệnh không có tác dụng. Với instance nhỏ hơn vài GB, ngưỡng mặc định 100 MB có nghĩa là nó gần như không bao giờ chạy. Đây là kiểu cấu hình tệ nhất — bật rồi mà tưởng đã xong.

Chống phân mảnh không miễn phí

Chống phân mảnh là di chuyển từng đối tượng sang vùng nhớ mới rồi cập nhật con trỏ, và nó chạy trên chính luồng xử lý lệnh. 67.497 lần di chuyển ở đây là 67.497 khoảng thời gian nhỏ mà không lệnh nào của khách được phục vụ.

Hai tham số kiểm soát mức độ:

active-defrag-cycle-min 1     % CPU tối thiểu dành cho việc này
active-defrag-cycle-max 25    % CPU tối đa

Mặc định tối đa 25% — tức một phần tư luồng duy nhất. Với hệ thống nhạy độ trễ, hạ xuống 5–10% và chấp nhận dọn lâu hơn.

Khi nào đáng làm gì

Không làm gì, nếu allocator_frag_bytes nhỏ so với maxmemory. Vài chục MB phí trên một instance 16 GB là bình thường.

Bật activedefrag kèm hạ ignore-bytes, nếu chỗ phí đáng kể và bạn chịu được thêm một chút độ trễ. Đây là cách duy nhất trả bộ nhớ về mà không gián đoạn.

Khởi động lại, nếu bạn có bản sao và chuyển đổi được. Đó là cách sạch nhất và nhanh nhất, nhưng nó là gián đoạn thật.

Và cách phòng hiệu quả hơn cả ba: giữ kích thước giá trị đồng đều. Phân mảnh tệ nhất khi trộn lẫn nhiều lớp kích thước rất khác nhau, vì mỗi lớp có vùng riêng và không dùng lại chỗ của nhau.

Thử ba mươi giây

Đọc đúng ba con số thay vì nhìn tỷ lệ:

redis-cli info memory | grep -E \
  'used_memory:|used_memory_rss:|used_memory_peak:|allocator_frag_bytes|allocator_frag_ratio|maxmemory:'

Cách đọc:

  • allocator_frag_bytes so với maxmemory — dưới 5% thì bỏ qua.
  • used_memory_peak cao hơn used_memory nhiều — bạn từng có một đợt phình, và RSS đang giữ dấu vết của nó. Đây là chỉ số hay bị bỏ qua nhất, và nó giải thích phần lớn các ca "RSS cao mà dữ liệu ít".
  • Tỷ lệ dưới 1 nghĩa là hệ điều hành đã đẩy một phần Redis ra bộ nhớ tráo đổi. Đó mới là cảnh báo thật sự khẩn cấp — nó làm mọi lệnh chậm đi hàng nghìn lần.

Phần sau: RDB và AOF — đo chính xác bao nhiêu dữ liệu mất khi Redis tắt đột ngột.