SwiftUI Circuit Breakers & Network Resilience Patterns (연쇄 실패 방지)

발행: (2026년 3월 8일 오전 05:48 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

대부분의 앱은 다음과 같이 API를 호출합니다:

try await api.fetchUser()

이렇게 하면 동작하지만… 서버가 오류를 일으키기 시작하면 문제가 발생합니다. 그때 앱은 다음과 같은 일을 할 수 있습니다:

  • 즉시 재시도
  • 수백 개의 실패 요청을 전송
  • 배터리 소모
  • 백엔드 과부하
  • UI 속도 저하
  • 장애 악화

이를 cascading failure 라고 합니다. 프로덕션 앱은 보호 메커니즘이 필요합니다.

🧠 핵심 원칙

시스템에 문제가 발생하면 상황을 악화시키지 마세요. 무한히 재시도하는 대신, 시스템은 실패를 감지하고 요청을 일시 중지해야 합니다.

🧱 1. 회로 차단기란?

회로 차단기는 실패하는 서비스에 대한 반복 호출을 방지합니다. 세 가지 상태가 있습니다:

Closed → Normal operation
Open → Requests blocked
Half-Open → Testing recovery

Flow

Requests Fail
→ Circuit Opens
→ Requests Blocked
→ Recovery Test
→ Circuit Closes

🔌 2. 회로 상태 정의

enum CircuitState {
    case closed
    case open(until: Date)
    case halfOpen
}
  • Closed → 요청 허용
  • Open → 요청이 일시적으로 차단됨
  • Half‑Open → 복구 테스트를 위해 제한된 요청 허용
final class CircuitBreaker {
    private(set) var state: CircuitState = .closed
    private var failureCount = 0
    private let failureThreshold = 5
    private let timeout: TimeInterval = 30

    func recordFailure() {
        failureCount += 1
        if failureCount >= failureThreshold {
            state = .open(until: Date().addingTimeInterval(timeout))
        }
    }

    func recordSuccess() {
        failureCount = 0
        state = .closed
    }
}

실패가 누적되면 회로가 열립니다.

🚦 4. 회로가 열려 있을 때 요청 차단

요청을 수행하기 전에:

func canExecute() -> Bool {
    switch state {
    case .closed:
        return true
    case .open(let until):
        return Date() > until
    case .halfOpen:
        return true
    }
}

사용법

guard breaker.canExecute() else {
    throw NetworkError.circuitOpen
}

이는 백엔드를 보호합니다.

🔄 5. 반열림 상태로 전환

When the timeout expires:

case .open(let until):
    if Date() > until {
        state = .halfOpen
        return true
    }

Only a small number of requests should test recovery. If they succeed → close the circuit; if they fail → reopen.

🔋 6. 모바일 앱에 회로 차단기가 필요한 이유

모바일 네트워크는 불안정합니다. 일반적인 장애 시나리오는 다음과 같습니다:

  • 서버 장애
  • DNS 문제
  • TLS 실패
  • 속도 제한
  • 셀룰러 패킷 손실

회로 차단기가 없으면 앱이 다음과 같은 일을 할 수 있습니다:

  • 백엔드에 스팸 요청을 보냄
  • 배터리를 빠르게 소모함
  • 장애를 확대함

회로 차단기는 이를 방지합니다.

🧱 7. API 클라이언트와 통합하기

Wrap network calls:

func performRequest(_ operation: () async throws -> T) async throws -> T {
    guard breaker.canExecute() else {
        throw NetworkError.circuitOpen
    }

    do {
        let result = try await operation()
        breaker.recordSuccess()
        return result
    } catch {
        breaker.recordFailure()
        throw error
    }
}

이렇게 하면 복원력이 자동으로 적용됩니다.

🌐 8. 재시도 전략과 결합하기

Circuit breakers work with retries. Example flow:

Request
→ Failure
→ Retry (exponential backoff)
→ Failure threshold reached
→ Circuit opens
→ Requests paused

This prevents retry storms.

🧪 9. 회로 차단기 테스트

테스트 시나리오:

  • 반복적인 서버 오류
  • 타임아웃 폭풍
  • 네트워크 연결 끊김
  • 장애 후 복구
  • 반열림 전환

검증 내용:

  • 장애 발생 시 요청이 중단되는지
  • 복구 후 요청이 재개되는지

⚠️ 10. Common Anti‑Patterns

Avoid:

  • infinite retries → 무한 재시도
  • retrying immediately after failure → 실패 직후 즉시 재시도
  • ignoring server rate limits → 서버 속도 제한 무시
  • retrying while offline → 오프라인 상태에서 재시도
  • not tracking failure counts → 실패 횟수 추적 안 함

These cause backend overload, cascading outages, and battery drain. → 이러한 행동은 백엔드 과부하, 연쇄 장애, 배터리 소모를 초래합니다.

🧠 멘탈 모델

Request
 → Failure Detection
   → Circuit Breaker
     → Pause Requests
       → Recovery Probe
         → Resume Traffic

주의: “그냥 다시 시도하세요.”

🚀 최종 생각

Circuit breakers는 앱에 다음과 같은 이점을 제공합니다:

  • 백엔드 보호
  • 보다 스마트한 재시도 동작
  • 배터리 사용량 감소
  • 정전 시 복원력
  • 예측 가능한 복구

이는 취약한 네트워크 클라이언트와 프로덕션 급 모바일 시스템의 차이점입니다.

0 조회
Back to Blog

관련 글

더 보기 »

모바일 개발에서 가장 위험한 메시지

모바일 앱을 빌드한다면, 적어도 한 번은 이 메시지를 본 적이 있을 겁니다: > “Hey… the build isn’t installing.” 그리고 바로 그 순간, 하루가 망가집니다. 당신은 …