Hành Trình Debug Lỗi Redis OOM Trong Laravel: Tìm Ra Hơn 2.8 Triệu Keys Và Cách Khắc Phục Triệt Để

Có những ngày làm kỹ thuật trôi qua rất yên bình, nhưng cũng có những ngày bắt đầu bằng một tiếng ping Slack “Queue đứng rồi anh ơi!”, “Server không vào được nữa anh ơi!
Và đây là một trong những ca sự cố mà tôi nhớ rất rõ — bởi vì nó dẫn tôi đến việc tìm thấy hơn 2.8 triệu Redis keys chỉ trong môi trường phát triển.

Đó là lúc tôi buộc phải lôi toàn bộ “đồ nghề” ra: từ metrics, redis-cli, bash script, đến việc ngồi soi từng pattern key một. Và những gì tôi học được trong hành trình đó, tôi muốn chia sẻ lại ở đây — để nếu bạn cũng dùng Redis giống tôi, bạn sẽ tránh được một ngày làm việc dở khóc dở cười.


1. Dấu hiệu ban đầu – khi slack báo động không ngừng

Sự cố được phát hiện qua lỗi sau trên slack:

OOM command not allowed when used memory > 'maxmemory'.
RedisException: OOM command not allowed when used memory > 'maxmemory'. in /var/www/app/vendor/laravel/framework/src/Illuminate/Redis/Connections/PhpRedisConnection.php:405

Nếu bạn đã từng gặp lỗi này, bạn biết nó nguy hiểm cỡ nào:

  • Redis từ chối mọi lệnh ghi
  • Queue dừng
  • Horizon đứng hình
  • Laravel không thể push thêm job
  • Toàn bộ request liên quan Redis gần như fail đồng loạt

Điều đầu tiên tôi làm là mở AWS ElastiCache lên. Và cảnh tượng trước mắt:

  • Memory đã 100%
  • Biểu đồ tăng đều, gần như hoàn hảo theo dạng diagonal slope
  • Không hề có eviction (vì đang dùng volatile-lru — mặc định của AWS ElastiCache)

Đây là dấu hiệu kinh điển của memory leak trong Redis.

Giờ thì không còn đường lui nữa: phải lội vào trong Redis để xem nó đang chứa cái gì.


2. Bắt đầu điều tra – mò mẫm từng dấu vết trong Redis

Tôi ssh vào ECS container chạy Laravel (Alpine), cài nhanh redis-cli:

apk add redis vim

Rồi truy cập Redis database mà Laravel đang dùng:

redis-cli -h <redis-host> -n 1 --scan

Mục tiêu của tôi:

Tìm ra nhóm keys nào đã ăn hết RAM của Redis.

Bởi Redis mà đầy RAM thì chỉ có 1 trong 2 nguyên nhân:

  1. Key quá to
  2. Too many keys

Và theo trực giác, tôi nghiêng về too many keys.


3. Nhận diện pattern lạ – cuộc điều tra bắt đầu có hy vọng

Trong dự án laravel_dev, tôi lướt qua các module có khả năng sinh nhiều keys.
Sau một hồi đọc log và grep source, tôi để ý đến pattern:

laravel_dev_cache_:App\\\\Jobs*

Đây là nhóm keys do một service lưu metadata job, kiểu dạng log hoặc tracking.

Tôi quyết định scan thử:

redis-cli -h <redis-host> -n 1 --scan --pattern "laravel_dev_cache_:App\\\\Jobs*" | wc -l

Redis trả về:

≈ 2.800.000 keys

Tôi ngồi im vài giây.

Đây chính là thủ phạm.


4. Đào sâu hơn – không chỉ nhiều keys mà còn “bất tử”

Tôi kiểm tra TTL của một vài key:

redis-cli -h <redis-host> -n 1 TTL "laravel_dev_cache_:App\\Jobs\\JobName:1234567890"

Kết quả:

-1

TTL = -1 nghĩa là:

Key không bao giờ tự hết hạn.

Vậy là rõ:
Code đang sinh keys mỗi ngày, mỗi giờ, mỗi phút → và không bao giờ xoá.
Một kiểu memory leak rất phổ biến khi dùng Redis sai mục đích.


5. Vấn đề khó nhằn: Xoá 2.8 triệu keys sao cho không làm Redis chết tiếp

Không thể dùng KEYS — vì:

  • Nó block Redis
  • Khi memory đang căng → khả năng treo Redis rất cao
  • AWS ElastiCache có thể tự động failover

Chỉ có một cách an toàn: SCAN + DEL dần dần.

Tôi viết một đoạn bash loop:

while IFS= read -r key; do
  echo "Deleting key: '$key'"
  redis-cli -h <redis-host> -n 1 DEL "$key"
done < <(redis-cli -h <redis-host> -n 1 --scan --pattern "laravel_dev_cache_:App\\\\Jobs*")

Mất khoảng 30–40 phút, lượng keys giảm rõ rệt.
Biểu đồ memory từ 100% rớt xuống 10%.
Horizon hồi sinh. Queue bắt đầu đẩy job trở lại. Cache tiếp tục hoạt động

Nếu muốn chạy background mà không cần giữ terminal:

nohup sh -c '
while IFS= read -r key; do
  echo "Deleting key: '\''$key'\''"
  redis-cli -h <redis-host> -n 1 DEL "$key"
done < <(redis-cli -h <redis-host> -n 1 --scan --pattern "laravel_dev_cache_:App\\\\Jobs*")
' > /tmp/redis_delete.log 2>&1 &

Một cảm giác rất “đã” — giống như nới được một cái van áp lực.


6. Tìm hiểu nguyên nhân trong code – thủ phạm thực sự nằm ở logic

Tôi trace lại luồng xử lý và phát hiện:

  • Mỗi job đều sinh ra 1 metadata key khi kết hợp với Laravel Scheduler chưa đúng cách/
  • Key lưu dạng JSON với prefix laravel_dev_cache_:App\\Jobs:...
  • Không có TTL
  • Không có cơ chế cleanup
  • Job chạy nhiều hơn dự kiến → số lượng keys tăng âm thầm một cách đều đặn hàng phút.

Đây là kiểu bug không gây lỗi ngay lập tức, nhưng sẽ phá hủy hệ thống sau vài tuần hoặc vài tháng — nên rất khó phát hiện nếu không quan sát metrics.


7. Fix triệt để – để sự cố này không bao giờ quay lại

Tôi áp dụng 3 thay đổi lớn.

7.1 Bắt buộc gán TTL cho mọi key

Trong Laravel:

Cache::put($key, $value, now()->addMinutes(60));

Trong Redis thuần:

Redis::setex($key, 3600, $value);

Nếu bạn để key tồn tại mãi mãi → bạn đang đặt bom hẹn giờ trong hệ thống.


7.2 Gom tất cả vào Hash thay vì tạo hàng triệu keys rời rạc

Từ:

laravel_dev_cache_:App\\Jobs:<jobId>

Chuyển sang:

HSET laravel_dev:jobs:<date> <jobId> <data>
EXPIRE laravel_dev:jobs:<date> 86400

Thay vì 1.8M keys → chỉ còn < 30 keys/ngày.


7.3 Giới hạn dung lượng log

Giữ 200 job gần nhất chẳng hạn:

Redis::ltrim('laravel_dev:jobs_log', -200, -1);

8. Bài học sau sự cố – những thứ tôi ước gì mình biết sớm hơn

Sau ca này, tôi rút ra những kinh nghiệm rất thực chiến:

8.1. Redis không phải database.

Dùng Redis để lưu dữ liệu dạng “log” là tự sát.

8.2. Mọi key phải có TTL — luôn luôn.

Không trừ trường hợp đặc biệt.

8.3. Số lượng keys cũng là chỉ số quan trọng như memory.

Rất ít đội monitor key-count, nhưng nên làm.

8.4. Cần có cơ chế alert sớm.

  • Memory > 80%
  • Key-count tăng nhanh
  • Horizon queue lag

8.5. Khi Redis đầy, đừng hoảng — scan và xoá đúng cách là cứu được.

Không được dùng KEYS.


9. Kết luận – Một ca sự cố đáng nhớ, và bài học cho những lần sau

Sự cố Redis OOM trong dự án laravel_dev không chỉ là một lỗi kỹ thuật.
Nó nhắc tôi rằng:

  • Hệ thống có thể chạy rất mượt trước khi đột ngột “bung lụa”.
  • Metrics là thứ không bao giờ được xem nhẹ.
  • Redis mạnh, nhưng dùng sai cách thì nguy hiểm như bất kỳ database nào.

Hy vọng hành trình này giúp bạn tránh được một ngày làm việc “chữa cháy” như tôi.

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like
Read More

MOVE ON

Trong cuộc sống thường ngày đang có rất nhiều người bị mắc kẹt trong những nỗi đau từ…