우버의 재시도 폭풍(Retry Storm) 방지 기법
우버는 깊은 의존성 체인에서 서비스 하나의 장애가 재시도 트래픽 증폭으로 이어져 전체 시스템 장애로 번지는 '재시도 폭풍' 문제를 다룹니다. 기존의 수동 재시도 설정과 재시도 예산(retry budget)만으로는 교차 서비스 증폭을 막기 어려워, 오류를 발생시킨 서비스와 전달만 하는 서비스를 구분하는 '오류 소유권(error ownership)' 기반의 컨텍스트 인식 메커니즘을 공유 인프라에 도입했습니다. 이를 통해 오류 발생 지점 근처에서만 재시도를 허용해 기준 대비 10% 이내의 트래픽 증가로 가용성을 유지할 수 있습니다.
2026년 9월 17일 우버는 재시도 폭풍(Retry Storm)을 어떻게 방어하는가 DM Deepanshu Mehndiratta 수석 스택 엔지니어 AS Alok Srivastava 프린시펄 엔지니어 VD Vibhor Dhingra 시니어 소프트웨어 엔지니어 1+ 이 기사 공유하기 페이스북 링크드인 X 소셜 링크
소개
재시도 폭풍은 역사적으로 비즈니스 운영과 브랜드 신뢰에 영향을 미쳐왔습니다. 재시도 구성 튜닝과 재시도 예산(retry budget)은 서비스 수준에서 의미 있는 완화책을 제공하지만, 이는 수동으로 구성되며 깊은 의존성 체인과 팬아웃(fan-out) 패턴으로 인한 교차 서비스 증폭에 대한 가시성이 부족합니다. 그 결과, 스택 깊숙이 위치한 단일 서비스 장애가 촉발하는 도미노 효과로부터 인프라를 보호하기가 어려울 수 있습니다.
핵심 이유는 오늘날의 재시도 동작이 컨텍스트를 인식하지 못한다는 점입니다. 재시도가 몇 번 발생하는지는 제어할 수 있지만, 언제 발생하는지는 정확하게 제어할 수 없습니다. 이는 서비스가 발생시킨 오류와 그 서비스를 통과해 전달된 오류를 신뢰성 있게 구분하는 데 따르는 어려움에서 비롯됩니다. 그 결과, 재시도가 조건부가 아닌 균일하게 적용됩니다. 이러한 접근 방식은 일시적이거나 낮은 비율의 장애에서는 잘 작동하지만, 중간 정도 또는 심각한 성능 저하 상황에서는 역효과를 낳습니다. 이미 어려움을 겪고 있는 서비스에 대해 공격적으로 재시도하는 것은 부하를 증가시키고, 장애를 가속화하며, 상위 의존성 전반에 재시도 트래픽을 증폭시킵니다. 국지적인 장애로 시작된 것이 빠르게 스택 전체의 사고로 확대되어 궁극적으로 최종 사용자 경험을 저하시키거나, 최악의 경우 완전히 망가뜨릴 수 있습니다.
다운스트림 서비스의 오류 코드를 업스트림으로 변환해 재시도에 컨텍스트를 제공할 수 있다고 반론할 수 있습니다. 이론적으로 가능하지만, 우버의 규모에서는 대규모 팬인(fan-in) 및 팬아웃(fan-out), 계속 변화하는 호출 흐름, 빈번한 적응형 변경 필요성 때문에 확장성이 없습니다. 따라서 우리는 오류를 더 효율적으로 처리하기 위해 공유 인프라에 컨텍스트 인식 메커니즘을 개발했습니다. 이 블로그에서 그 메커니즘을 설명합니다.
배경
그림 1과 같은 간단한 호출 체인을 생각해보면, 노드 A에 도착하는 총 요청 수를 Ƞ라고 할 때, 추론에 따라 모든 노드 B, C, D, E, F, G는 정상 상태(어떤 노드도 오류를 내지 않을 때)에서 Ƞ개의 요청을 처리합니다.
그림 1: 1:1 팬아웃 호출 체인. 각 노드는 들어오는 요청당 다운스트림을 정확히 한 번 호출합니다.
서비스 D가 오류를 내기 시작하고 각 서비스가 한 번 재시도하도록 구성된 경우(1회 정상 시도 + 다운스트림 실패 시 1회 재시도), 각 노드가 처리하는 총 요청 수를 살펴보겠습니다.
그림 2: 서비스가 오류를 내는 호출 체인.
노드: A B C D E F G 깊이: 0 1 2 3 4 5 6 처리 요청 수: Ƞ, 2×Ƞ, 4×Ƞ, 8×Ƞ, 8×Ƞ, 8×Ƞ, 8×Ƞ
모든 홉에서 재시도 수 R이 동일하다고 가정하면, 이는 간단한 공식으로 정리할 수 있습니다. ɗ는 호출 체인에서 노드의 깊이를 나타내며, 노드가 오류를 만들거나 통과시키는 경우 해당 노드가 처리하는 요청 수는 Rɗ × Ƞ입니다.
재시도 예산(Retry Budgets)
재시도 예산을 도입해 이를 최적화할 수 있습니다. 모든 홉에서 동일한 재시도 예산을 B라고 하면 새 공식은 (1+B)ɗ × Ƞ가 됩니다.
이제 재시도 예산이 10%일 때 처리되는 요청 수를 살펴보겠습니다:
노드: A B C D E F G 깊이: 0 1 2 3 4 5 6 처리 요청 수: Ƞ, 1.1×Ƞ, 1.21×Ƞ, 1.33×Ƞ, 1.33×Ƞ, 1.33×Ƞ, 1.33×Ƞ
위 예시에서 오류는 노드 D에서 발생하며, 재시도를 노드 D와 노드 C 사이에서만 허용하고 노드 A와 노드 B가 이 오류에 대해 재시도하는 것을 완전히 제한하면, 노드 D, E, F, G에 과부하를 주지 않으면서 호출 경로의 유사한 가용성을 보장할 수 있습니다.
오류 소유권(Error Ownership)
오류가 발생한 노드 D와 노드 C 사이의 엣지로 재시도를 제한하면서 동일한 재시도 예산 예시를 생각해보겠습니다.
노드: A B C D E F G 깊이: 0 1 2 3 4 5 6 처리 요청 수: Ƞ, Ƞ, Ƞ, 1.1×Ƞ, 1.1×Ƞ, 1.1×Ƞ, 1.1×Ƞ
여기서 D부터 리프 노드 G까지 모든 노드가 처리하는 총 요청 수를 기준치 대비 단 10% 초과 수준으로 억제하면서, 오류가 처음 발생한 시점에 최대 10%의 오류에 대해 최소 1회의 재시도를 허용합니다.