Mình từng bị page vì retry storm đúng một lần, lúc 2 giờ sáng, cho một service chẳng có lỗi gì ngoài việc nó nằm 20 hop phía sau một service khác đang lỗi thật. Mọi tầng ở giữa đều thấy timeout, tưởng lỗi thuộc về mình, và retry. Cái service lỗi thật sự hứng đủ 20 lần tải bình thường chỉ từ một dependency bị degrade. Uber vừa công bố con số cho đúng loại vấn đề này, và cách họ sửa nhỏ hơn mình tưởng nhiều.

Retry mặc định giả định sai điều quan trọng nhất

Logic retry tiêu chuẩn coi mọi lỗi như nhau: có gì đó fail, thử lại, có thể kèm backoff. Cái nó không biết là lỗi này khởi phát ngay tại hop này, hay đến từ ba service phía trước, đã bị ai đó retry hai lần rồi. Trong một call chain sâu, sự thiếu hiểu biết đó cộng dồn. Service A gọi B gọi C gọi D. D degrade. C thấy lỗi và retry. B thấy lỗi (hoặc độ trễ do C retry gây ra) và cũng retry. A làm y hệt. Một leaf node bị degrade giờ tạo ra lượng traffic nhân lên theo đúng số tầng không biết mình không phải là nguồn gốc.

Đội kỹ thuật Uber — Deepanshu Mehndiratta, Alok Srivastava, Vibhor Dhingra, Ankit Srivastava — gọi đây là “error ownership” (quyền sở hữu lỗi), và cách đặt tên chính là insight thật sự, không phải phần triển khai. Một lỗi thuộc về đúng service tạo ra nó. Mọi hop khác chỉ quan sát lỗi đó trong lúc forward request thì không phải chủ sở hữu, và không nên tiêu tốn retry budget của chính mình cho nó.

Cơ chế chỉ là một header, không phải một subsystem mới

Họ thêm x-uber-error-claim vào retry middleware. Khi một service tạo ra lỗi, nó claim lỗi đó. Khi một lỗi từ downstream đi qua một service mà không đổi, service đó không claim — nó chỉ forward tiếp cái claim đã tồn tại sẵn. Chỉ service sở hữu lỗi, dựa theo retry budget và backoff policy của chính nó, mới được quyết định có retry hay không. Mọi thứ phía trên kế thừa quyết định đó thay vì tự phát minh lại một cách độc lập.

Cơ chế chỉ gói gọn vậy thôi. Không có consensus protocol mới, không thêm round trip, không phải redesign lại service mesh — một header mang metadata quyền sở hữu đi xuyên qua call chain sẵn có, được check trước khi ra quyết định retry thay vì sau.

Con số mới là thứ khiến mình thực sự tin

Trong một lần Core Entity service bị degrade thật vào tháng 11/2025, cơ chế này chặn được 9.5 triệu request retry thừa. Uber tự ước tính rằng nếu không có error ownership, mức tăng traffic trên cái service đã degrade sẵn có thể cao hơn từ 46 đến 135% — cộng dồn lên trên một service vốn đã đang lỗi.

Con số bán kính ảnh hưởng mới là phần mình cứ đọc đi đọc lại. Độ sâu của retry storm giảm từ 25 tầng xuống 3. Độ sâu retry trung bình trên các API hướng người dùng giảm từ 20 xuống 2. Đó không phải một cải thiện tinh chỉnh nhỏ. Đó là khác biệt giữa “một dependency lỗi làm degrade cả call graph” và “một dependency lỗi chỉ làm degrade ba service gần nó nhất.”

Mình sẽ không coi con số 9.5 triệu là một hằng số tổng quát hóa được — nó gắn chặt với độ sâu call graph và cấu hình retry mặc định của Uber trước khi sửa, một kiến trúc nông hơn sẽ không thấy cùng hệ số nhân đó. Nhưng cơ chế thì tổng quát hóa tốt bất kể quy mô, và con số độ sâu 25-xuống-3 mới là con số thực sự giải thích lý do vì sao.

Chỗ mình sẽ áp dụng pattern này

Nếu bạn đang chạy service mesh, hoặc chỉ vài service có logic retry xếp lớp ở mỗi hop, pattern này portable được mà không cần hạ tầng như Uber. Cái header không cần phải giống hệt header của Uber — nó cần mã hóa hai thứ: ai tạo ra lỗi này, và đã có ai retry cho nó chưa. Một gRPC interceptor hay một HTTP client middleware mang metadata đó tốt không kém gì một custom header, miễn là mọi hop trong chain của bạn tôn trọng nó thay vì tự ra quyết định retry độc lập từ góc nhìn cục bộ của riêng mình.

Chỗ mình nghĩ dễ làm sai nhất nếu triển khai cẩu thả: tin cái claim mà không validate. Một service lỗi hoặc bị compromise có thể claim quyền sở hữu một lỗi nó không tạo ra, chặn mất những retry hợp lệ ở chỗ khác trong chain. Bài viết của Uber không đi sâu vào cách họ guard chuyện này nội bộ, và đó là câu hỏi đầu tiên mình muốn có câu trả lời trước khi đưa pattern này vào một hệ thống có mức độ tin cậy giữa các service thấp hơn service mesh nội bộ của Uber.

Điều thực sự thay đổi suy nghĩ của mình khi đọc bài này không phải là format cụ thể của cái header. Mà là logic retry ở gần như mọi nơi mình từng làm việc chỉ coi “cuộc gọi này có fail không” là tín hiệu duy nhất quan trọng, trong khi “ai thực sự sở hữu cái lỗi này” mới là thứ quyết định retry có giúp ích gì hay chỉ đang đổ thêm tải lên một thứ đã đang cháy.

Xuất nội dung

Bình luận