오류 복구: 대규모 환경에서의 우아한 성능 저하(Graceful Degradation)
SchemaBridge Team · 2026-01-16 · Resilience, Error Handling, Fault Tolerance
대규모 환경에서 재시도와 백오프를 다루는 법. 관리형 복원력이 "재시도 폭풍"을 막는 방식.
핵심 요약 단순한 재시도 로직은 장애를 증폭시켜, 자신이 두드리고 있는 바로 그 서비스를 다운시키는 재시도 폭풍으로 키웁니다. 복원력은 상한이 있는 재시도(일시적인 HTTP 실패에 대한 짧은 지수 백오프, 백엔드가 과부하를 보고할 때의 적응형 속도 조절)와 더불어, 새 워크플로 버전을 위한 자동 롤백이 포함된 점진적 롤아웃, 그리고 자동화가 결정해서는 안 되는 모든 것을 위한 사람의 복구 도구(Retry, Resolve, Start over)에서 나옵니다.
"재시도 폭풍(Retry Storm)": 단순한 오류 처리가 실패하는 이유
분산 시스템 초창기에 "오류 처리"란 보통 코드 일부를 try/catch로 감싸고, 경우에 따라 세 번 재시도하는 간단한 while 루프를 추가하는 것을 의미했습니다. 장애가 드물고 국지적인 작고 격리된 환경에서는 이 방식도 통합니다. 하지만 대규모 환경, 즉 서로 연결된 마이크로서비스 메시에서 단순한 재시도는 재시도 폭풍(또는 "Thundering Herd")으로 알려진 시스템 전체 붕괴의 지름길입니다.
블랙프라이데이 세일 중 높은 부하로 기본 데이터베이스가 약간 느려지는 상황을 상상해 보십시오. 쿼리 지연 시간이 10ms에서 1100ms로 늘어납니다. API에는 표준 1초 타임아웃이 설정되어 있습니다. 갑자기 1,000개의 동시 워커가 한꺼번에 이 타임아웃에 걸립니다. 모두 오류를 잡아내고, 단순한 루프의 로직에 따라 즉시 쿼리를 재시도합니다.
이제 처음의 1,000개 요청을 처리하는 것만으로도 버거워하던 데이터베이스에 추가로 1,000개의 요청이 한꺼번에 몰립니다. 부하는 순식간에 두 배가 됩니다. 데이터베이스 CPU는 100%로 치솟고 지연 시간은 5초로 늘어납니다. 워커는 다시 실패하고 다시 재시도합니다. 시스템은 실패의 양의 피드백 루프에 빠집니다. 사실상 자신의 인프라에 DDoS 공격을 가한 셈입니다. 사소한 성능 저하를 시스템 전체 장애로 만들어 버린 것입니다.
실패를 받아들이기: 오류는 API의 일부다
SchemaBridge에서는 오류가 "예외적인" 것이라는 생각에서 벗어납니다. 이벤트 기반 세계에서 실패는 성공만큼이나 흔합니다. 네트워크는 분할되고, Pod는 예측할 수 없게 동작하며, 서드파티 API에는 유지보수 시간이 있습니다. 우리는 오류 복구를 코드 속 catch 블록이 아니라 플랫폼의 관리형 수명 주기로 다룹니다. 그리고 자동으로 재시도하는 대상에는 의도적으로 엄격한 제한을 둡니다.
SchemaBridge가 실패를 분류하는 방식
효과적으로 복구하려면 먼저 무언가가 왜 실패했는지 이해해야 합니다. SchemaBridge는 이 판단을 두 계층에서 내립니다.
| 계층 | 실패 | 자동으로 일어나는 일 |
| :--- | :--- | :--- |
| HTTP 호출(Gateway) | 5xx, 429 또는 I/O 오류(타임아웃, 연결 재설정) | 프로세스 내에서 재시도, 지수 백오프로 최대 3회 추가 시도 |
| HTTP 호출(Gateway) | 그 밖의 모든 4xx(400, 401, 403, 404…) | 재시도하지 않음 — 잘못된 비밀번호는 두 번째 시도에서도 절대 성공하지 않음 |
| 단계(vertex) | 속도 제한이 있는 백엔드의 일시적 과부하(external_overload) | vertex별 한도 내에서 자동으로 재디스패치 |
| 단계(vertex) | 그 밖의 모든 경우 | 첫 시도에서 종료 처리, 실행은 빨간색으로 표시되고 운영자를 기다림 |
기본값은 의도적으로 보수적입니다. 단계의 실패는 일시적이라는 것이 입증되지 않는 한 영구적입니다. 반복해도 안전하지 않을 수 있는 부작용을 조용히 다시 실행하느니, 실패를 사람에게 드러내는 편이 낫다고 봅니다.
상한이 있는 재시도의 구조: 백오프의 수학
HTTP 호출을 위한 지수 백오프
Gateway 호출이 일시적인 오류를 만나도 플랫폼은 대상을 매초 두드리지 않습니다. 시도 사이의 대기 시간을 지수적으로 늘립니다.
$$Wait = Base \times 2^{Retry - 1}$$
- 첫 시도 실패
- 재시도 1: 1초 대기
- 재시도 2: 2초 대기
- 재시도 3: 4초 대기
총 네 번의 시도가 약 7초에 걸쳐 이루어집니다. 대상이 Retry-After 헤더와 함께 429 Too Many Requests로 응답하면 플랫폼은 이를 따르며, 최대 30초로 제한됩니다. 이 짧고 상한이 있는 진행 방식은 어려움을 겪는 서비스를 희생양으로 만들지 않으면서 네트워크의 일시적 끊김을 흡수합니다. 이 백오프는 무작위화되어 있지 않다는 점(지터가 없음)에 유의하십시오. 즉 일시적 끊김을 완화하기 위한 설계이지, 수천 개의 독립적인 클라이언트를 조율하기 위한 것이 아닙니다.
과부하된 백엔드를 위한 적응형 재디스패치
실제로 포화된 백엔드에 짧은 백오프는 맞지 않는 도구입니다. 속도 제한이 있는 단계 유형(Gateway 단계와 에이전트형 단계)에서는 과부하 실패가 플랫폼의 대상별 승인 제어(admission control)로 피드백되며, 승인 제어는 해당 대상으로 작업을 받아들이는 속도를 낮춥니다(AIMD 컨트롤러로, TCP가 혼잡 제어에 사용하는 것과 같은 아이디어입니다). 그런 다음 실패한 단계는 같은 승인 경로를 통해 자동으로 재디스패치됩니다. 기본값은 최대 3회이며, vertex별로 maxAutoRetries로 설정할 수 있습니다(부작용을 절대 다시 보내서는 안 되는 단계라면 0으로 설정하십시오).
{
"type": "GATEWAY",
"config": {
"url": "https://api.example.com/v1/charges",
"method": "POST",
"maxAutoRetries": 5
}
}
속도 조절이 고정된 타이머가 아니라 승인 제어에서 나오므로, 재시도는 대상이 필요로 하는 만큼 정확히 느려집니다. 과부하가 한도보다 오래 지속되면 단계는 실패하고 실행은 운영자를 기다립니다.
점진적 롤백: 워크플로 릴리스의 안전성
재시도가 소용없을 때도 있습니다. 새 워크플로 버전이 버그를 가져왔다면 아무리 재시도해도 고쳐지지 않습니다. 롤백이 필요합니다.
SchemaBridge는 워크플로 버전에 대해 자동 롤백이 포함된 점진적 롤아웃을 지원하며, 이는 시스템 워크플로(gradual_deploy)로 구현되어 있습니다.
- 단계적 확대: 설정 가능한 비율의 트래픽(기본값 10%, 최소 1%)이 새 버전으로 라우팅되며, 그 비율은 일정 간격으로 단계적으로 늘어납니다.
- 상태 점검: 각 단계에서 모니터링 실행이 새 버전의 오류 수를 고정된 임계값과 비교합니다. 임계값을 넘으면 롤아웃은 새 버전의 트래픽을 0%로 되돌립니다.
이를 통해 결함 있는 워크플로 릴리스의 영향 범위는 점검이 이를 잡아낸 시점까지 도달한 트래픽 비율로 제한됩니다. 이는 워크플로 버전에 적용되며 인프라 구성에는 적용되지 않습니다. 또한 이것은 안전망일 뿐 보장이 아닙니다. 임계값이 작동하기 전에 새 버전이 받은 트래픽은 여전히 영향을 받았습니다.
사람이 개입하는 복구: 운영자의 도구 상자
어떤 실패는 루프가 아니라 판단력을 필요로 합니다. 은행 대사(reconciliation) 결과가 청구서 금액과 0.01달러 차이가 나서 워크플로가 실패했다면, 어떤 코드도 무엇을 해야 할지 결정할 수 없고 결정해서도 안 됩니다.
SchemaBridge에서 이런 실행은 그저 FAILED 상태로 끝나며, 운영자가 조치하는 데 필요한 모든 것을 갖추고 있습니다.
- 가시성: 실패한 단계는 실행 그래프와 모니터에서 빨간색으로 표시됩니다.
- 포렌식: 운영자는 단계의 입력, 출력, 오류 메시지(예: "mismatch: 100.00 vs 100.01")를 볼 수 있습니다.
- 개입: 운영자는 알맞은 복구 방법을 선택합니다.
- Retry는 실패한 단계를 다시 실행하고 그 지점부터 워크플로를 이어 갑니다.
- Resolve & continue는 실패를 받아들이고, 해당 단계에 대해 운영자가 제공한 출력을 기록한 뒤 워크플로의 나머지를 진행합니다.
- Start over는 전체 워크플로를 새 실행으로 다시 실행합니다.
- Resume on new version은 실패한 실행을 버그가 수정된 워크플로 버전에서 실패 지점부터 이어 갑니다.
이로써 "오류"는 공포와 데이터베이스 땜질의 원인이 아니라 표준적인 운영 워크플로가 됩니다.
예시 시나리오: 결제 게이트웨이 장애
매일 자정에 대량의 갱신 건을 일괄 처리하는 구독형 SaaS 회사를 생각해 보십시오. 어느 날 밤, 주요 결제 게이트웨이가 한 리전에서 장시간 장애를 겪습니다.
기존 방식
단순한 cron 작업으로 만든 레거시 시스템은 게이트웨이가 다운되면 세 번 재시도한 뒤 구독을 "실패 / 결제 거절"로 표시합니다. 엔지니어들이 일어날 무렵에는, 실제로는 게이트웨이가 다운되었을 뿐인데도 시스템이 결제가 거절되었다고 판단했기 때문에 고객 계정이 비활성화되어 있을 수 있습니다. 지원 대기열은 가득 차고, "계정 해지" 이메일 뒤에는 고객 이탈이 따릅니다.
SchemaBridge 방식
이제 같은 결제 엔진이 SchemaBridge에서 실행된다고 솔직하게 그려 보겠습니다.
1. 상한이 있는 재시도: 503이 발생하기 시작하면 각 결제 호출은 짧은 백오프로 재시도됩니다. 일시적 끊김은 흡수되지만 몇 시간씩 이어지는 장애는 흡수되지 않으며, 해당 갱신 실행들은 실패합니다.
2. 실패는 결정이 아니다: 실패한 단계는 "거절" 경로로 흘러가는 대신 실행을 멈추므로, 거절로 잘못 해석된 장애 때문에 누군가의 계정이 해지되는 일은 없습니다. 실패한 실행은 게이트웨이의 오류가 첨부된 채 모니터에 빨간색으로 남아 있습니다.
3. 복구는 일괄 작업이다: 게이트웨이가 다시 정상화되면 운영자가 실패한 실행들을 선택해 일괄 재시도합니다.
기대되는 결과
장애는 여전히 발생하고, 아침에 누군가 여전히 조치해야 합니다. 달라지는 것은 피해입니다. 잘못된 해지도, 불필요한 이메일도 없으며, 복구는 데이터베이스 정리 작업이 아니라 한 번의 일괄 작업입니다. 이것이 바로 복원력의 비즈니스 가치입니다.
내결함성 설계를 위한 전문가 체크리스트
복원력 있는 시스템을 구축하려면 다음 휴리스틱을 따르십시오.
1. 모든 쓰기에 되돌리기를 계획하라: 레코드를 생성한다면, 실패 시 그것을 어떻게 삭제하거나 보관할지 알고 있어야 합니다. 그 되돌리기가 사람이 실행하는 단계라 하더라도 말입니다. 대칭성은 일관성의 핵심입니다.
2. 직접 관리하는 클라이언트 재시도에는 지터를 추가하라: 많은 독립적인 클라이언트가 같은 일정으로 재시도할 때, 무작위성은 이들이 동기화되어 무리를 이루는 것을 막아 줍니다.
3. 잘못된 분기보다 멈춘 실행을 택하라: 인프라 장애가 비즈니스 결과로 위장하게 두지 마십시오. 사람을 기다리는 실패한 실행이 조용히 잘못된 실행보다 낫습니다.
4. 오류 경로를 감사하라: 우리는 흔히 "해피 패스"는 테스트하면서 "실패 경로"는 무시합니다. 스테이징 환경에서 503과 400을 반환하는 엔드포인트로 단계를 지정하고, 각 경우에 실행이 예상대로 동작하는지 확인하십시오.
결론: 실패는 복원력을 위한 기회다
오류 복구는 버그를 막는 일이 아니라 재난을 막는 일입니다. 자동 재시도에 상한을 두고, 과부하가 시스템을 몰려들게 하는 대신 느려지게 하며, 운영자에게 정밀한 복구 도구를 제공함으로써, "재시도 폭풍"에 대한 두려움 없이 복잡한 통합을 구축할 자유를 얻게 됩니다.
Building the Bridge 시리즈의 일부입니다. 함께 보기: 시각적 로직으로 클라우드 리소스를 관리하는 방법을 다룬 워크플로로서의 인프라.