유버의 엔지니어링 팀은 복잡하게 얽힌 서비스 간 의존성을 가진 시스템에서 발생하는 '리トライ 스톰' 현상으로 인한 비즈니스 운영 및 브랜드 신뢰도에 미치는 부정적 영향을 해결하기 위해 '에러 소유자십'이라는 개념을 기반으로 한 솔루션을 개발 및 구현했습니다. 이 접근법은 단일 서비스 장애가 연쇄적인 장애를 일으켜 시스템 전체에 파급되는 '도미노 효과'를 방지하는 것을 목표로 합니다. Uber Protects Against Retry Stormshttps://www.uber.com/us/en/blog/protecting-against-retry-storms/전통적인 리トライ 설정이나 리トライ 예산은 개별 서비스 수준에서는 효과적이었으나, 여러 서비스 간에 걸친 의존성의 깊이와 팬아웃 패턴으로 인한 크로스 서비스 증폭의 가시성을 충분히 고려하지 못했기 때문에, 스택의 깊은 곳에 있는 단일 서비스 장애로부터 인프라를 보호하는 것이 어려웠습니다. 근본적인 문제는 리トライ의 동작이 컨텍스트를 인식하지 못해 에러가 '서비스 자체에서 생성된 것'인지 '단순히 서비스를 통해 전파된 것'인지 구별할 수 없었기 때문입니다.
결과적으로, 리トライ는 조건부 적용이 아닌 무조건적으로 적용되었습니다. 무조건적인 리トライ 접근법은 일시적이고 빈도가 낮은 장애에는 효과적이지만, 심각한 장애 발생 시에는 역효과를 일으켜 장애를 가속화하고 업스트림의 의존성 전체에 리トライ 트래픽을 증폭시켜 '리トライ 스톰' 현상을 유발할 수 있으며, 이는 결과적으로局部的 장애가 시스템 전체로 확산되는 사고를 급격히 악화시키고 엔드 사용자 경험을 현저히 저하시킬 수 있습니다. 문제의 배경을 이해하기 위해 간단한 호출 체인을 가정해 보겠습니다. 각 노드는 수신된 요청에 대해 다운스트림을 정확히 한 번 호출합니다. 노드 A 에 도착하는 요청의 총수를 'Ƞ'라고 하면, 추론에 의해 모든 노드가 오류가 없는 '정상 상태'에서는 노드 B~G 가 모두 Ƞ 개의 요청을 처리합니다.
다음으로 서비스 D 에서 오류가 발생하고 각 서비스가 한 번 재시도하도록 구성된 경우, 한 번의 요청에는 정상적인 시도 1 회와 다운스트림 실패 시 재시도 1 회가 발생합니다.
이 경우, 처리된 요청 수는 다음 표와 같습니다.
각 노드에서의 재시도 횟수 'R'이 동일하다고 가정하면, 처리된 요청 수는 단순한 공식 'Rɗ × Ƞ'로 요약할 수 있습니다. 여기서 'ɗ'은 호출 체인에서의 노드 깊이로, 노드가 에러를 생성하거나 통과시킬 경우 해당 노드가 처리하는 요청 수를 나타냅니다. '리トライ 버지켓'을 도입하면 위 공식을 더욱 최적화할 수 있습니다. 각 노드에서 동일한 리トライ 버지켓 'B'를 가진다고 가정하면 새로운 공식은 '(1+B)ɗ × Ƞ'가 됩니다. 예를 들어 재시도율을 10%로 설정하면, 앞서 언급한 표는 다음과 같이 됩니다.
즉, 노드 D 에서 끝단 노드 G 까지 모든 노드에서 처리되는 요청의 총수를 베이스라인의 10% 만으로 제한하면서도, 처음 반환된 에러의 최대 10% 에 대해 적어도 한 번은 재시도를 허용합니다. 그렇다면 노드 C 에서 본 노드 D 의 가용성은 어떻게 될까요? 아래 표는 다양한 가용성 시나리오에서 리トライ 후의 가용성을 계산한 결과입니다.
위 표에 따르면, 호출 대상 노드의 가용성이 최대 10% 감소하더라도 단 한 번의 재시도만으로 호출원 노드에서 보았을 때의 가용성을 99%까지 향상시킬 수 있습니다. 그러나 이 계산은 호출 대상에서 발생하는 오류들이 서로 독립적이며 재시도를 통해 회복된다는 전제에 기반하고 있습니다. 서비스 과부하, 데이터베이스 호스트 고장, 데이터베이스 과부하, 샤딩 문제 등 많은 현실 세계 시나리오에서는 재시도를 수행해도 재시도 성공 확률이 여전히 낮습니다. 즉, 서비스 오류가 빈번하게 발생하면 오류가 우발적일 가능성이 낮으며, 재시도 횟수를 늘려도 무의미할 뿐만 아니라 상황을 악화시킬 수 있습니다. 이러한 근본적인 문제를 해결하기 위해 도입된 개념이 '오류 소유권'이며, 이를 '증상'과 '원인'의 아날로지로 설명할 수 있습니다. 어떤 서비스가 요청 처리를 위해 아웃바운드 콜을 수행하고 그 중 하나가 오류를 반환하면, 해당 서비스가 오류를 반환하더라도 단순한 '증상'에 불과합니다. 반면, 서비스의 아웃바운드 콜에서 오류가 발생하지 않았음에도 불구하고 서비스가 오류를 반환하는 경우, 해당 서비스가 오류의 원인이므로 오류의 '소유자'가 됩니다. 우버는 '서비스 의존성 분석 솔루션'을 사용하여 인바운드 장애와 아웃바운드 장애를 연결하고, 오류의 '클레임' 또는 '언클레임'을 수행하기 위한 규칙 세트를 도입했습니다.
오류 소유권 (Error Ownership) 이 확립되면 호출원 서비스는 아래 그림에 표시된 논리에 따라 요청을 재시도해야 하는지 판단합니다.
다운스트림 서비스가 서비스 의존성 분석 솔루션을 갖지 않은 경우, 오류 요청 헤더가 누락될 수 있어 첫 번째 오류 요청의 누락을 감지한 노드가 오류를 앵클링 (Anchor) 하여 재시도의 영향 범위를 제한하고 재시도 폭풍 (Retry Storm) 을 방지하면서도 오류를 반환한 서비스에 충분한 재시도를 가능하게 합니다. 오류 소유권에 기반한 재시도 허용 여부는 다음 결정 매트릭스로 판단됩니다.
그러나 서비스 자체의 내부 오류와 다운스트림 오류가 동시에 발생하는 '우연한 오류 (Accidental Error)' 시나리오에서는 오류가 잘못 앵클링되어 원래 수행해야 할 재시도 기회가 손실될 수 있습니다. 이 리스크를 해결하기 위해 서비스 의존성 분석 솔루션은 과거 장애 패턴의 메모리를 활용하여 인바운드 장애가 페일클로즈 의존성 내의 아웃바운드 장애와 상관관계가 있을 때만 오류를 앵클링하도록 하여, 우연한 오류 발생 시 재시도가 억제되는 리스크를 제거합니다.
또한 많은 서비스에서는 페일 클로즈 발생 시 리トライ가 설정되어 있지 않습니다. 이에 따라 Uber 는 리トライ 미들웨어에 플래그를 도입하여 문제를 대응하고 있습니다. 리トライ 기준이 충족되는지 여부를 신호로 전달함으로써 리トライ 미들웨어는 다운스트림에서 에러를 수신하면 재시행 조건이 충족되는지 계산하고, 그 결과를 서비스 의존성 분석 솔루션에 알림을 보냅니다.
리トライ 미들웨어는 아래 그림에 표시된 조건 중 하나라도 충족되면 리トライ 기준이 충족되었다고 마킹합니다. '최소 한 번 리トライ' 보증의 도입으로 콜 체인에서의 가용성 저하가 배제되었으며, 동시에 에러 소유권을 활용하여 리トライ 스톨로부터 보호받게 되었습니다.
오류 소유 (Error Ownership) 메커니즘은 이론적인 논의가 아니라 이미 우버의 사용자용 API 요청 경로에 배치된 재시도 미들웨어와 서비스 의존성 분석 솔루션으로 구현 및 운영되고 있으며, 각 서비스가 개별 오류 처리 로직을 구현하지 않고도 재시도 폭풍 (Retry Storm) 대응의 혜택을 받을 수 있습니다. 2025 년 11 월 18 일 발생한 대규모 장애 사례에서는 인프라에 기인한 문제로 인해 핵심 엔티티 서비스가 매우 높은 오류율을 반환했지만, 오류 소유가 유효하여 시스템이 자동으로 영향 범위를 격리할 수 있었습니다. 장애가 있는 서비스를 직접 호출하던 서비스에 대한 재시도 시도는 중단되었으며, 일부 호출원은 최대 20 만 건의 추가 요청을 중단했습니다. 또한, 직접 호출원의 더 상류에서 중단된 재시도를 집계한 결과, 서비스 메시 전체에서 950 만 건의 비정상 요청이 억제되었다는 것이 밝혀졌습니다. 오류 소유 접근 방식을 통해 우버는 장애 이벤트 발생 시 콜그래프를 흐르는 총 요청량을 획기적으로 줄였습니다. 재시도 폭풍이 발생할 수 있는 호출 경로의 깊이를 '재시도 폭풍 반경 (Retry Storm Radius)'으로 정의하면, 오류 소유 도입 후 최대 재시도 폭풍 반경은 사용자용 API 전체에서 최대값이 25 에서 3 으로, 평균값도 20 에서 2 로 크게 감소했습니다.
리トライ를 오직 실제로 문제를 해결할 수 있는 서비스, 즉 에러 소유권을 가진 서비스에만 적용함으로써 리トライ 스터움의 특징인 요청의 지수적 확산을 방지하고 단일 장애점으로 인한 연쇄 장애로부터 우버의 인프라를 보호하는 데 성공했습니다.
원문 보기 | 출처: Gigazine