Khi bộ nhớ Redis cạn kiệt: Bài học từ sự cố sản xuất thực tế
By hientd, at: 17:11 Ngày 05 tháng 9 năm 2025
Thời gian đọc ước tính: __READING_TIME__ phút


Đôi khi, các hệ thống sản xuất gặp sự cố theo những cách bạn không ngờ tới, ngay cả khi đã có giám sát và mở rộng quy mô. Tuần này, nhóm của chúng tôi đã gặp phải một vấn đề nghiêm trọng khiến máy chủ sản xuất của chúng tôi bị ngừng hoạt động hai lần trong hai ngày. Nguyên nhân gốc rễ? Redis bị cạn kiệt bộ nhớ.
Điều khiến sự cố này trở nên thú vị là Redis không bị lỗi do những nguyên nhân thông thường (như khóa tồn tại lâu hoặc thiếu chính sách xóa). Thay vào đó, thủ phạm nằm ẩn trong cách các tác vụ Celery của chúng tôi tương tác với Redis khi tải nặng.
Điều gì đã xảy ra
-
Trong giờ cao điểm, nhiều khách hàng tải lên các tệp lớn cùng một lúc.
-
Mỗi lần tải lên kích hoạt một tính toán nền thông qua Celery.
-
Vào thời điểm cao điểm, chúng tôi thấy lên tới 10.000 yêu cầu/giây.
Lớp cơ sở tác vụ Celery tùy chỉnh của chúng tôi được thiết kế để giới hạn tốc độ các tác vụ bằng cách ghi khóa khóa vào Redis:
self.first_task_at_lock_key = f"{base_task_id}-first_task_at"
self.count_tasks_lock_key = f"{base_task_id}-count_tasks"
Vấn đề?
Khi dữ liệu đầu vào (sale_orders) rất lớn, các khóa được tạo này có thể đạt 10 KB mỗi khóa. Với hai khóa mỗi tác vụ, Redis đã lưu trữ ~20 KB mỗi tác vụ.
Ở quy mô lớn:
-
1.000 tác vụ = ~20 MB bộ nhớ Redis
-
Với hàng nghìn tác vụ được xếp hàng mỗi giây, việc sử dụng bộ nhớ tăng không kiểm soát cho đến khi Redis từ chối ghi:
redis.exceptions.ResponseError:
OOM command not allowed when used memory > 'maxmemory'
Và thế là, máy chủ sản xuất của chúng tôi bị sập. Nhật ký theo dõi được hiển thị bên dưới:
django_redis.exceptions.ConnectionInterrupted: Redis ResponseError: OOM command not allowed when used memory > 'maxmemory'.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "venv/lib/python3.8/site-packages/sentry_sdk/integrations/django/views.py", line 67, in sentry_wrapped_callback
return callback(request, *args, **kwargs)
File "venv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "venv/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
return self.dispatch(request, *args, **kwargs)
File "venv/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "venv/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "venv/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "venv/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch
self.initial(request, *args, **kwargs)
File "venv/lib/python3.8/site-packages/sentry_sdk/integrations/django/__init__.py", line 270, in sentry_patched_drf_initial
return old_drf_initial(self, request, *args, **kwargs)
File "venv/lib/python3.8/site-packages/rest_framework/views.py", line 416, in initial
self.check_throttles(request)
File "venv/lib/python3.8/site-packages/rest_framework/views.py", line 359, in check_throttles
if not throttle.allow_request(request, self):
File "venv/lib/python3.8/site-packages/rest_framework/throttling.py", line 132, in allow_request
return self.throttle_success()
File "venv/lib/python3.8/site-packages/rest_framework/throttling.py", line 140, in throttle_success
self.cache.set(self.key, self.history, self.duration)
File "venv/lib/python3.8/site-packages/django_redis/cache.py", line 38, in _decorator
raise e.__cause__
File "venv/lib/python3.8/site-packages/django_redis/client/default.py", line 175, in set
return bool(client.set(nkey, nvalue, nx=nx, px=timeout, xx=xx))
File "venv/lib/python3.8/site-packages/redis/client.py", line 1801, in set
return self.execute_command('SET', *pieces)
File "venv/lib/python3.8/site-packages/redis/client.py", line 901, in execute_command
return self.parse_response(conn, command_name, **options)
File "venv/lib/python3.8/site-packages/redis/client.py", line 915, in parse_response
response = connection.read_response()
File "venv/lib/python3.8/site-packages/redis/connection.py", line 756, in read_response
raise response
redis.exceptions.ResponseError: OOM command not allowed when used memory > 'maxmemory'.
Nguyên nhân gốc rễ
-
Khóa quá lớn, vì dữ liệu thô được truyền trực tiếp vào tác vụ Celery.
-
Redis về cơ bản được sử dụng như một bộ nhớ cache + kho lưu trữ tải tác vụ, mà nó không được tối ưu hóa cho việc đó.
-
Độ đồng thời cao đã khuếch đại vấn đề, đẩy Redis vượt quá giới hạn bộ nhớ được cấu hình.
Khắc phục
Chúng tôi đã thay đổi chiến lược xếp hàng của mình:
Thay vì truyền dữ liệu thô trực tiếp vào các tác vụ, chúng tôi:
-
Lưu trữ dữ liệu thô trong cơ sở dữ liệu.
-
Chỉ truyền ID + băm nhẹ vào tác vụ Celery.
Điều này đã giảm kích thước khóa từ ~10 KB → <100 byte.
Kết quả
-
Việc sử dụng bộ nhớ cao điểm giảm từ 20 MB/s → ~0,2 MB/s (giảm 99%).
-
Độ ổn định của Redis được khôi phục.
-
Máy chủ của chúng tôi hoạt động trơn tru ngay cả trong quá trình tải lên tệp lớn đồng thời.
Những điểm chính
-
Cẩn thận với tải ẩn: Truyền dữ liệu lớn vào các tác vụ Celery có vẻ vô hại cho đến khi nó được mở rộng.
-
Redis không phải là kho dữ liệu: Hãy sử dụng nó cho các khóa/giá trị nhẹ, không phải để lưu trữ tải lớn.
-
Luôn đo lường tác động bộ nhớ: Vài KB mỗi khóa là ổn… cho đến khi bạn nhân nó với hàng nghìn tác vụ/giây.
-
Thiết kế cho quy mô sớm: Ngay cả những thiếu hiệu quả “nhỏ” cũng có thể gây ra sự cố lớn trong lưu lượng truy cập thực tế.
Suy nghĩ cuối cùng
Những sự cố như thế này rất đau đớn nhưng lại rất quý giá. Chúng buộc chúng ta phải xem xét lại các giả định, cải thiện kiến trúc và xây dựng các hệ thống mạnh mẽ hơn. Trong trường hợp của chúng tôi, đây là lời nhắc nhở rằng hiệu quả trong thiết kế tác vụ nền cũng quan trọng như tính chính xác của logic nghiệp vụ.
Chúng tôi hy vọng việc chia sẻ điều này sẽ giúp những người khác tránh được sự cố tương tự.