System Design

Retry storm: vì sao exponential backoff vẫn chưa đủ, phải thêm jitter

Tóm tắt

Khi một sự cố làm nhiều request thất bại cùng lúc, các client sẽ retry cùng lúc — kể cả khi dùng exponential backoff, vì đồng hồ của chúng vẫn đồng bộ. Thêm jitter (chờ một khoảng ngẫu nhiên) làm các lần retry rải đều ra, server không bị dồn tải.

Xem trên Facebook

Một dịch vụ gặp sự cố ngắn: hàng loạt request thất bại. Các client được lập trình tốt đều retry — và chính điều đó có thể biến sự cố ngắn thành sự cố dài. Khi nhiều client retry cùng lúc, server vừa hồi phục lại bị dồn tải và sập tiếp, hiện tượng gọi là retry storm hay thundering herd. Dùng exponential backoff giúp một phần, nhưng chưa đủ: cần thêm jitter.

Vì sao exponential backoff vẫn chưa đủ?

Exponential backoff tăng dần thời gian chờ giữa các lần thử (ví dụ 0,8s, 1,6s, 3,2s…). Nhưng nếu 1.000 client cùng thất bại tại một thời điểm, chúng cũng cùng chờ 0,8s rồi cùng retry — đồng hồ của chúng vẫn đồng bộ, tạo ra những “đợt sóng” tải lặp lại.

Kịch bản trong video

Backoff không jitterBackoff + full jitter
Cách chờ0.8s, 1.6s… giống hệt nhau cho mọi clientNgẫu nhiên trong khoảng [0, base·2ⁿ]
Hình dạng tảiCả 12 request đập vào cùng lúc như “bức tường”Request rải thành dòng
Bị từ chối (503)6 lần0 lần
Thời gian hoàn tất4.6 giây1.9 giây

Cùng số lần retry — chỉ khác thời điểm bắn lại.

Mã minh hoạ

function delay(attempt, base = 200, cap = 10000) {
  const max = Math.min(cap, base * 2 ** attempt);
  return Math.random() * max;           // full jitter
}
async function withRetry(fn, retries = 5) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (e) {
      if (i >= retries || !isTransient(e)) throw e;
      await new Promise(r => setTimeout(r, delay(i)));
    }
  }
}

Các biến thể jitter

  • Full jitter: chờ ngẫu nhiên trong [0, mức backoff]; rải đều nhất.
  • Equal jitter: một nửa cố định cộng một nửa ngẫu nhiên; đảm bảo có khoảng chờ tối thiểu.
  • Decorrelated jitter: thời gian chờ phụ thuộc lần chờ trước, tránh đồng bộ lâu dài.

Áp dụng thế nào cho đúng?

  • Luôn kết hợp: exponential backoff + jitter + giới hạn số lần retry + timeout tổng.
  • Chỉ retry lỗi tạm thời (503, timeout, mất kết nối); không retry lỗi 4xx do request sai.
  • Đảm bảo idempotency cho thao tác được retry (dùng idempotency key với API thanh toán, tạo đơn…).
  • Kết hợp circuit breaker để ngừng retry hẳn khi service đích đang chết.
  • Tôn trọng Retry-After nếu server trả về.

Kiểm tra code của bạn

  1. Tìm mọi nơi có vòng lặp retry tự viết.
  2. Kiểm tra có jitter, giới hạn và phân biệt lỗi tạm thời/vĩnh viễn không.
  3. Mô phỏng sự cố ngắn bằng load test và quan sát hình dạng tải.

Nguồn & tham khảo

  1. Timeouts, retries and backoff with jitter — Amazon Builders' Library