+2

[Backend Masterclass] OOM (Out Of Memory): Ác Mộng Màn Hình Đen & Bí Kíp Chọn Eviction Policy Cứu Sinh

Hôm nay, mình sẽ dắt anh em lặn thẳng xuống tầng Kernel của Linux (Hệ điều hành) và mổ xẻ Mã nguồn (Source Code) của Redis để xem con ác mộng "màn hình đen" này thực sự diễn ra như thế nào, và tại sao những dòng cấu hình ngây ngô lại giết chết server của bạn lúc 2 giờ sáng.

Chuẩn bị sẵn cà phê đi, bài này sẽ rất nặng đô đấy!

1. Sát thủ giấu mặt dưới tầng OS: Linux OOM Killer

Nhiều anh em lầm tưởng OOM (Out Of Memory) là lỗi do Redis ném ra. Không hề! OOM thực chất là bản án tử hình do chính hệ điều hành Linux ban lệnh.

Câu chuyện bắt đầu từ thói "Hứa lèo" của Linux:

Linux có một cơ chế gọi là Overcommit Memory (vm.overcommit_memory = 1). Khi Redis gọi hàm malloc() để xin RAM, Linux thường tỏ ra hào phóng: "Cứ lấy đi, tao còn nhiều!" kể cả khi RAM vật lý sắp cạn. Nó làm vậy vì nghĩ rằng các ứng dụng hiếm khi xài hết số RAM đã xin.

Nhưng Redis là một con quái vật ăn RAM thực thụ. Khi RAM thực tế (Physical RAM + Swap) bị vắt kiệt đến giọt cuối cùng, Linux rơi vào trạng thái hoảng loạn (Kernel Panic). Để tự cứu lấy mạng sống của mình, Linux triệu hồi một sát thủ mang tên OOM Killer.

OOM Killer sẽ đi lùng sục tất cả các tiến trình, chấm điểm (gọi là oom_score). Thằng nào to xác nhất, ngốn nhiều RAM nhất sẽ bị trảm đầu tiên. Và 99% trường hợp, Redis là nạn nhân bị kill -9 (Giết không kịp ngáp, không kịp lưu data).

👉 Kết quả: Server Database mất Cache đột ngột. Hàng vạn request từ App ập thẳng vào MySQL gây ra thảm họa Cache Stampede. MySQL sập, App mất kết nối. Xin chúc mừng, bạn đã quay vào ô Màn Hình Đen (Downtime toàn hệ thống)!

2. Cú lừa BGSAVE và Thảm họa Copy-on-Write (CoW)

Một case study kinh điển: "Server của em 16GB RAM. Em cấu hình Redis maxmemory 12GB. Rõ ràng còn dư tận 4GB, sao vẫn bị OOM Killer trảm?"

Thủ phạm chính là tiến trình sao lưu nền BGSAVE (Background Save). Khi đến chu kỳ ghi data từ RAM xuống ổ cứng (RDB file), Redis không tự làm mà nó gọi hàm fork() để nhân bản ra một Tiến trình con (Child Process). Nhờ cơ chế Copy-on-Write (CoW) của Linux, tiến trình con không sao chép ngay lập tức 12GB RAM, mà nó "dùng chung" RAM với tiến trình cha. Mọi thứ có vẻ rất hoàn hảo và tiết kiệm.

NHƯNG... Lỗ hổng chết người nằm ở đây: Trong quá trình tiến trình con đang cặm cụi ghi data xuống đĩa (mất khoảng 1-2 phút), nếu App của bạn ghi (Write/Update) dữ liệu mới vào Redis. Lúc này, Linux bắt buộc phải tách vùng nhớ đó ra (Copy) để tiến trình cha ghi data mới, không làm hỏng data cũ tiến trình con đang save.

Nếu hệ thống đang có Flash Sale, lượng lệnh Write xả vào như thác lũ, 12GB RAM ban đầu có thể bị nhân bản lên thành 15GB, 18GB, thậm chí 24GB. Vượt ngưỡng 16GB vật lý -> OOM Killer thức giấc -> ĐOÀNG! Redis bay màu.

Nếu hệ thống đang có Flash Sale, lượng lệnh Write xả vào như thác lũ, 12GB RAM ban đầu có thể bị nhân bản lên thành 15GB, 18GB, thậm chí 24GB. Vượt ngưỡng 16GB vật lý -> OOM Killer thức giấc -> ĐOÀNG! Redis bay màu.

Bí kíp sinh tồn 1: TUYỆT ĐỐI không bao giờ set maxmemory vượt quá 60-70% tổng RAM của server nếu bạn có bật tính năng Persistence (RDB/AOF). Hãy chừa khoảng trống cho Copy-on-Write thở!

3. Mổ xẻ thuật toán LRU: Nó không như Sách giáo khoa!

Để không bị OOM Killer gọi tên, bạn set maxmemory. Khi RAM chạm nóc, Redis kích hoạt Eviction Policy (Chính sách đuổi khách).

Anh em thường chọn allkeys-lru (Least Recently Used - Đuổi thằng lâu nhất không xài). Nhưng sự thật phũ phàng: Redis KHÔNG xài LRU chuẩn!

Tại sao? Vì để làm LRU chuẩn (dùng Linked List + HashMap), Redis sẽ phải tốn thêm khoảng 20-30% RAM chỉ để lưu cái danh sách liên kết đó, cộng thêm việc tiêu tốn rất nhiều CPU mỗi khi data thay đổi vị trí.

Giải pháp của các pháp sư Redis: Approximated LRU (LRU Xấp xỉ). Khi cần xóa data, Redis không quét toàn bộ DB. Nó chỉ bốc ngẫu nhiên 5 keys (mặc định), xem trong 5 thằng này thằng nào cũ nhất thì trảm thằng đó.

Bí kíp sinh tồn 2: Nếu bạn thấy Redis dọn rác ngu (xóa nhầm data mới), hãy mở redis.conf và tăng thông số maxmemory-samples 10. Nó sẽ bốc ngẫu nhiên 10 keys để so sánh. Độ chính xác sẽ gần bằng LRU chuẩn, nhưng hãy nhớ: bốc càng nhiều, CPU chạy càng tốn!

4. LFU (Least Frequently Used) - Chân ái của kĩ sư hệ thống

LRU có một "tử huyệt": Giả sử có một sản phẩm cực HOT (ai cũng xem). Nhưng đúng 10 giây qua chả hiểu sao không ai click vào. Cùng lúc đó, một sản phẩm RÁC vừa mới được 1 người lỡ tay click. Thuật toán LRU sẽ đánh giá: Sản phẩm HOT là "đồ cũ" (vì 10 giây trước), còn sản phẩm RÁC là "đồ mới" -> Nó nhẫn tâm xóa bài HOT đi.

Từ Redis 4.0, LFU (allkeys-lfu) ra đời để sửa cái ngu này. LFU theo dõi TẦN SUẤT (Frequency) thay vì Thời gian. Nhưng đếm tần suất cho hàng triệu keys thì tràn RAM mất!

Redis giải quyết bằng một trick cực tởm: Nó dùng một bộ đếm Logarithmic chỉ có... 8 bit (Đếm tối đa được đến 255).

  • Truy cập 1 lần: Biến đếm = 1.
  • Truy cập 10 lần: Biến đếm = 5.
  • Truy cập 100 lần: Biến đếm = 10.
  • Truy cập 1 triệu lần: Biến đếm = 255.

Nó còn kèm theo thông số lfu-decay-time (Thời gian phân rã). Nếu key không được truy cập, bộ đếm tự tụt dần (Ví dụ từ 255 tụt xuống 200, 100...). Khi RAM đầy, thằng nào có biến đếm nhỏ nhất sẽ bị kick.

Bí kíp sinh tồn 3: Nếu Redis của bạn dùng làm Cache cho các hệ thống có trend rõ rệt (Ví dụ: Báo chí điện tử, E-commerce, Flash Sale), hãy đổi ngay sang allkeys-lfu. Nó sẽ giữ lại những món hàng Hot nhất, giúp Cache Hit-rate của bạn tối ưu đến mức hoàn hảo.

5. Tổng kết Phác đồ điều trị OOM "Hạng Nặng"

Đọc xong bài này, anh em hãy SSH ngay vào server Production và check lại các gạch đầu dòng sau:

  1. Check OS: Gõ sysctl vm.overcommit_memory. Đảm bảo nó đang là 1.
  2. Check Maxmemory: Phải luôn được cấu hình, và chỉ chiếm tối đa 60-70% physical RAM. (Gõ INFO memory trong redis-cli để xem chi tiết).
  3. Phân mảnh (Partitioning): Đừng ném Session, Queue, và Cache vào chung 1 con Redis.
  • Con Redis chứa Cache: Set allkeys-lfu. Đầy thì tự dọn.
  • Con Redis chứa Queue/Session: Set noeviction hoặc volatile-lru. Cài đặt Alert (Prometheus/Grafana) báo động đỏ về Slack ngay khi RAM chạm ngưỡng 80% để Ops/Dev vào nâng cấp RAM kịp thời!

Khắc cốt ghi tâm: Đừng đối xử với Redis như một cái thùng rác ma thuật nhét bao nhiêu cũng được. Cấu hình sai một dòng, ngày mai báo đài sẽ đưa tin app của công ty bạn sập toàn tập vì... "lỗi kĩ thuật không mong muốn" đấy!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí