How Uber Protects Against Retry Storms

Bài tóm tắt Uber 2026 3. Quy mô lớn Độ khó: Khó

Uber · 2026 · 3. Quy mô lớn · Độ khó: Khó — bài gốc

Bài thứ hai của một cặp; nền của nó là phân tích phụ thuộc: Uber 2026.

Bối cảnh

  • Chỉnh cấu hình retry và retry budget giảm được ở mức từng service, nhưng chỉnh tay và không thấy được khuếch đại xuyên service qua chuỗi phụ thuộc sâu và fan-out. Gốc vấn đề: retry không biết ngữ cảnh; kiểm được bao nhiêu lần retry nhưng không kiểm được khi nào, vì không phân biệt được lỗi do service sinh ra với lỗi chỉ đi qua nó.
  • Toán khuếch đại: chuỗi A → B → C → D → E → F → G, D lỗi, mỗi hop retry một lần: số request ở độ sâu d là 2^d × n (D và các node sau nó nhận 8n). Với retry budget 10% ở mọi hop: (1,1)^d × n, D nhận 1,33n. Nếu chỉ cho retry ở cạnh C → D (nơi lỗi phát sinh) và cấm A, B retry lỗi này: D nhận 1,1n.
  • Retry chỉ có ích khi lỗi độc lập: callee còn 90% availability thì một lần retry với budget 10% đưa availability mà caller thấy lên khoảng 99%; còn 80% thì chỉ lên 88%. Lúc lỗi cao (quá tải, host DB xấu, lỗi shard) lỗi không ngẫu nhiên, retry nhiều chỉ làm tệ thêm.
  • Dịch mã lỗi từ hạ nguồn lên trên về lý thuyết được, nhưng theo Uber không scale với fan-in, fan-out lớn và luồng gọi luôn đổi.

Thay đổi chính

  • Quyền sở hữu lỗi: nếu service trả lỗi vì một lời gọi ra bị lỗi thì lỗi của nó chỉ là triệu chứng; nếu không lời gọi ra nào lỗi mà nó vẫn trả lỗi thì nó là nguyên nhân và là chủ của lỗi. Dùng phân tích phụ thuộc để tương quan lỗi vào với lỗi ra, rồi claim hoặc unclaim lỗi qua header.
  • Ma trận quyết định: callee không lỗi, caller lỗi → caller claim. Callee lỗi và claim (hoặc thiếu claim) → caller được retry, rồi truyền lên là unclaim. Callee lỗi và đã unclaim → caller không retry, truyền tiếp unclaim. Node đầu tiên thấy lỗi từ chủ lỗi là node duy nhất retry, nên vùng nhiễu loạn bị chặn ở một hop.
  • Lỗi trùng hợp: khi service vừa có lỗi nội bộ thật vừa có lỗi ở phụ thuộc fail-open cùng lúc, phân tích ưu tiên đổ cho hạ nguồn nên có thể unclaim nhầm và bỏ lỡ một lần retry đáng có. Dữ liệu 6 tháng cho thấy rất hiếm (trường hợp tệ: khoảng 2% với 80% cạnh có trên 100 lỗi callee mỗi phút). Vẫn chặn được bằng cách chỉ unclaim khi lỗi vào tương quan với lỗi ra ở phụ thuộc fail-close đã ghi nhận.
  • Bảo đảm retry ít nhất một lần: nhiều service không cấu hình retry cho phụ thuộc fail-close; cấm caller retry thì availability tụt. Thêm cờ báo "điều kiện retry đã được thoả" cho lỗi từ hạ nguồn: retry middleware tính và chuyển cho phân tích phụ thuộc. Khi context bị mất giữa đường, node ở đó claim lỗi, retry vẫn diễn ra cục bộ, nhưng header unclaim vẫn chặn được tầng trên; với 5 node phía trên, trường hợp tệ nhất không có cơ chế này là gấp 32 lần request.
  • Production: chạy trong retry middleware và phân tích phụ thuộc trên đường request của mọi API hướng người dùng; service thừa hưởng mà không cần logic riêng.
  • Sự cố 18/11/2025: một Core Entity service ở sâu hơn 5 tầng trả lỗi rất cao do sự cố hạ tầng bên dưới. Theo Uber, chỉ với retry budget thì service đang suy giảm sẽ nhận thêm 46–135% traffic, kéo dài sự cố; có error ownership, các caller trực tiếp ngừng retry ngay (có caller bỏ tới 200.000 request thừa), cộng dồn ở gốc là khoảng 9,5 triệu request thừa không xảy ra.
  • Kết quả: "bán kính retry storm" (độ sâu đường gọi lớn nhất mà storm còn xảy ra được) trên mọi API hướng người dùng từ tối đa 25 xuống tối đa 3, trung bình từ 20 xuống 2.

Vì sao

  • Retry chỉ có ích ở nơi lỗi phát sinh, nơi có cơ hội sửa được; mọi retry ở tầng trên chỉ nhân bản tải lên service đang hỏng.

Bài học cho SWE

  • Retry budget chặn mức độ, không chặn chỗ; khuếch đại theo độ sâu cần một tín hiệu đi kèm lỗi cho biết ai là chủ lỗi.
  • Phân biệt lỗi phát sinh với lỗi truyền qua; chỉ cho retry ở hop đầu tiên phía trên chủ lỗi.
  • Khi siết retry, giữ bảo đảm ít nhất một lần retry để không đánh đổi availability.
  • Cơ chế bảo vệ đặt ở hạ tầng chung (middleware) thì mọi service có nó mà không phải viết.

Liên quan