모바일 개발에서의 재시도 정책 — 본질, 전략 및 원칙

저자: IT Sectr 게시일: 2026-03-11 읽는 시간: 10 분

재시도 정책 — 실패한 네트워크 호출을 모바일 애플리케이션이 자동으로 언제, 어떻게 재시도할지 결정하는 규칙 집합입니다. 불안정한 연결이나 일시적인 서버 오류가 발생할 때, 잘 설계된 재시도 정책은 사용자 개입 없이 애플리케이션의 신뢰성을 향상시킵니다. Google Developer Relations(2025) 연구에 따르면, 재시도 정책을 올바르게 구현하면 네트워크 작업이 빈번한 모바일 애플리케이션에서 손실된 요청의 비율이 40~60% 감소합니다.

주요 내용

  • 재시도 정책 — 네트워크 장애 또는 일시적인 서버 오류 시 요청을 자동으로 재시도하는 전략입니다.
  • 지수 백오프 — 서버 부하를 줄이기 위해 재시도 간 지연을 증가시키는 방법입니다.
  • 지터 — 떼몰림(thundering herd) 효과를 방지하는 무작위 지연 변동입니다.
  • 멱등성 — 안전한 재시도를 위한 핵심 요구사항: 반복된 요청이 부작용을 일으키지 않아야 합니다.
  • 서킷 브레이커 — 리소스를 보존하기 위해 장기간 서비스를 사용할 수 없을 때 재시도를 중단하는 메커니즘입니다.

재시도 정책이란?

재시도 정책 — 네트워크 요청 실패 시 클라이언트 동작을 정의하는 소프트웨어 전략입니다. 어떤 오류를 재시도해야 하는지, 몇 번, 어떤 지연으로, 언제 시도를 중단할지를 결정합니다. 모바일 애플리케이션에서는 모바일 네트워크의 불안정성과 일시적인 서버 측 장애 가능성 때문에 재시도 정책이 매우 중요합니다.

기본 재시도 정책에는 세 가지 매개변수가 포함됩니다: 최대 재시도 횟수(maxRetries), 초기 지연(baseDelay), 백오프 전략입니다. 또한 재시도를 트리거할 HTTP 상태 코드 목록과 모든 시도를 중단하기 위한 타임아웃을 지정할 수 있습니다.

Martin Kleppmann의 저서 “Designing Data-Intensive Applications”에 따르면, 분산 시스템에서 장애의 50%는 일시적이며 재시도로 해결됩니다. 이는 서버 아키텍처를 변경하지 않고 모바일 애플리케이션의 내결함성을 개선하는 가장 효과적이고 저렴한 방법 중 하나로 재시도 정책을 만듭니다.

어떤 오류를 재시도해야 하는가

일시적인 오류(재시도 가능)는 재시도 정책이 대응해야 하는 유일한 장애 유형입니다. 여기에는 연결 타임아웃(SocketTimeoutException), 일시적인 서버 사용 불가(HTTP 503, 502), DNS 오류가 포함됩니다. 영구적인 오류(HTTP 400, 401, 403, 404)는 네트워크나 서버가 아닌 요청 자체의 문제를 나타내므로 재시도해도 의미가 없습니다.

AWS Architecture Blog에 따르면, 오류를 재시도 가능과 재시도 불가능으로 올바르게 분류하는 것이 재시도 정책 설계 시 가장 중요한 결정입니다. HTTP 401에서 멱등성이 없는 요청을 재시도하면 계정이 잠길 수 있고, HTTP 400을 재시도하면 데이터가 중복될 수 있습니다. 재시도할 코드 목록을 항상 명시적으로 구성하세요.

기본 재시도 전략

고정 간격 — 가장 간단한 전략으로, 각 재시도가 동일한 시간 간격 후에 발생합니다. 예를 들어 지연이 2초인 경우 애플리케이션은 2, 2, 2초 후에 요청을 재시도합니다. 고정 간격은 구현이 간단하고 예측 가능하지만, 대량 장애 시 서버에 균일한 부하를 만듭니다.

증분 간격 — 재시도마다 지연이 선형적으로 증가합니다: 첫 번째 재시도는 1초 후, 두 번째는 2초 후, 세 번째는 3초 후 등입니다. 이 전략은 반복적인 장애 시 서버에 더 많은 복구 시간을 제공하지만, 동시에 장애가 발생하는 많은 클라이언트에게 여전히 예측 가능합니다.

전략지연 공식누적 시간(3회 시도)적용
고정delay = D3 × D단순 시나리오, 로컬 타임아웃
증분delay = N × D6 × D점진적 부하 감소
지수delay = D × 2^N7 × D대량 장애, 클라우드 서비스
지수 + 지터delay = random(0, D × 2^N)변동높은 부하, 마이크로서비스

전략 선택은 애플리케이션의 특성에 따라 달라집니다. 모바일 장치에서 데이터 동기화의 백그라운드 작업에는 지터가 있는 지수 전략이 최적입니다 — 서버와 사용자 장치에 최소한의 부하로 가장 높은 성공 확률을 제공합니다.

지수 백오프와 지터

지수 백오프 — 각 시도마다 재시도 간 지연이 두 배가 되는 전략입니다. 초기 지연이 1초인 경우 지연 순서는 1, 2, 4, 8, 16초입니다. 이는 서버에 기하급수적으로 증가하는 복구 시간을 제공합니다.

지터 — 여러 클라이언트의 동시 재시도 요청(떼몰림 문제)을 방지하는 무작위 지연 변동입니다. 지터가 없으면 동일한 재시도 정책을 가진 수천 개의 클라이언트가 동시에 요청을 재시도하여 서버에 피크 부하가 발생합니다. 지터는 재시도를 시간적으로 분산시킵니다.

Kotlin과 코루틴으로 구현

Kotlin 코루틴을 사용하면 메인 스레드를 차단하지 않고 지터가 있는 지수 백오프를 구현할 수 있습니다. kotlinx-coroutines의 retry 함수는 재시도 조건과 요청 본문이 있는 블록을 받아 지연 및 시도 횟수를 자동으로 관리합니다.

kotlin
suspend fun RetryPolicy.executeWithRetry(
    block: suspend () -> Result<T>
): Result<T> {
    var lastError: Throwable? = null
    repeat(maxRetries + 1) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            if (!isRetriable(e) || attempt == maxRetries) {
                return Result.failure(e)
            }
            val delay = (baseDelayMs * (1 shl attempt))
                .toLong()
            val jitteredDelay = (delay * (0.5 + Random.nextDouble())).toLong()
            delay(jitteredDelay)
            lastError = e
        }
    }
    return Result.failure(lastError!!)
}

executeWithRetry 함수는 네트워크 호출이 포함된 람다를 받아 지수 백오프와 지터로 실행합니다. 오류를 재시도할 수 없거나 최대 시도 횟수를 초과하면 함수는 오류를 반환합니다. 재시도의 균일한 분포를 위해 지연에 0.5에서 1.5 사이의 무작위 계수를 곱합니다.

서킷 브레이커와 재시도 중단

서킷 브레이커 — 장기간 서비스를 사용할 수 없을 때 무한 재시도 요청을 방지하는 디자인 패턴입니다. 오류 수가 임계값을 초과하면 서킷 브레이커는 OPEN 상태로 전환하고 요청을 실행하지 않고 즉시 오류를 반환하여 서버에 복구 시간을 제공합니다.

모바일 애플리케이션에서 서킷 브레이커는 계획된 유지보수 또는 통신사 네트워크 장애로 API를 사용할 수 없을 때 특히 유용합니다. 서킷 브레이커가 없으면 애플리케이션이 무한 재시도에 배터리와 트래픽을 소비하여 사용자 경험을 저하시키고 장치 배터리 수명을 단축시킵니다.

서킷 브레이커 상태 다이어그램

서킷 브레이커에는 세 가지 상태가 있습니다: CLOSED(정상 작동, 요청 실행), OPEN(장애, 요청 차단), HALF_OPEN(복구 확인을 위한 테스트 요청). OPEN 상태에서 지정된 타임아웃 후 브레이커는 HALF_OPEN으로 전환되어 하나의 요청을 실행합니다 — 성공 시 CLOSED로, 실패 시 OPEN으로 돌아갑니다.

kotlin
class CircuitBreaker(
    private val failureThreshold: Int = 3,
    private val timeoutMs: Long = 30000
) {
    private var state = State.CLOSED
    private var failureCount = 0
    private var lastFailureTime: Long = 0

    suspend fun T.protect(block: suspend () -> T): T {
        checkState()
        return try {
            val result = block()
            onSuccess()
            result
        } catch (e: Exception) {
            onFailure()
            throw e
        }
    }
}

Kotlin의 서킷 브레이커 구현에는 오류 카운터와 복구 타이머가 포함됩니다. protect 메서드는 래핑된 요청을 실행하기 전에 현재 상태를 확인하고 장애 시 오류 카운터를 업데이트합니다. failureThreshold에 도달하면 timeoutMs가 만료될 때까지 모든 요청이 즉시 거부됩니다.

모바일 애플리케이션에서의 재시도 정책

모바일 네트워크에는 재시도 정책을 특히 중요하게 만드는 특성이 있습니다. Wi-Fi와 모바일 데이터 간 전환, 지하철과 터널에서의 신호 손실, 통신사 수준의 일시적 차단 — 이러한 모든 시나리오는 재시도로 성공적으로 처리할 수 있는 요청 실패로 이어집니다.

Android에서 Retrofit 라이브러리와 OkHttp는 Interceptor를 통해 내장된 재시도 메커니즘을 제공합니다. iOS에서는 URLSessionConfiguration과 사용자 정의 위임을 통해 작업이 해결됩니다. 크로스 플랫폼 개발의 경우 Ktor(KMP)는 구성 가능한 전략으로 내장된 재시도 지원을 포함합니다.

iOS에서 Combine으로 구현

Combine — Apple의 반응형 프로그래밍 프레임워크입니다. Combine의 retry 연산자는 오류 발생 시 지정된 횟수만큼 publisher를 반복하지만 재시도 간 지연을 구성할 수 없습니다. 완전한 재시도 정책을 위해 지연이 있는 catchflatMap의 사용자 정의 조합이 사용됩니다.

swift
extension Publisher {
    func retryWithBackoff(
        retries: Int = 3,
        baseDelay: TimeInterval = 1.0
    ) -> AnyPublisher<Output, Failure> {
        return self.catch { error -> AnyPublisher in
            guard retries > 0 else {
                return Fail(error).eraseToAnyPublisher()
            }
            return Just(())
                .delay(for: .seconds(baseDelay), scheduler: DispatchQueue.main)
                .flatMap { self.retryWithBackoff(
                    retries: retries - 1,
                    baseDelay: baseDelay * 2
                ) }
                .eraseToAnyPublisher()
        }
        .eraseToAnyPublisher()
    }
}

Combine에서 Publisher를 위한 retryWithBackoff 확장은 카운터를 감소시키고 지연을 두 배로 늘리는 재귀 호출을 통해 지수 백오프를 구현합니다. delay 연산자는 재시도 간에 일시 중지를 만들고, catch는 오류를 가로채서 재시도할지 또는 실패를 반환할지 결정합니다.

재시도 정책의 일반적인 실수

첫 번째 실수 — 멱등성을 확인하지 않고 요청을 재시도하는 것입니다. 서버가 리소스를 생성했지만 네트워크 장애로 확인을 반환하지 않은 경우 재시도하면 중복이 생성됩니다. POST 요청의 경우 헤더에 멱등성 키(Idempotency-Key)를 항상 사용하거나 GET, PUT 및 DELETE에 대해서만 재시도하세요.

두 번째 실수 — 무한 재시도입니다. 항상 최대 시도 횟수(모바일 애플리케이션의 경우 3~5회)와 모든 시도의 전체 타임아웃을 설정하세요. 무한 재시도는 배터리를 소모하고, 특히 데이터베이스 마이그레이션이나 API 변경 시 서버에 기생 부하를 만듭니다.

세 번째 실수 — 애플리케이션 컨텍스트를 무시하는 것입니다. 사용자가 애플리케이션을 닫거나 백그라운드로 전환한 경우 활성 재시도 정책이 올바르게 취소되어야 합니다. 화면이 닫힐 때 자동 재시도 취소를 위해 SupervisorScope와 함께 코루틴 또는 UI 수명 주기와 함께 Combine을 사용하세요.

네 번째 실수 — 재시도 횟수를 기록하지 않는 것입니다. 로깅이 없으면 얼마나 많은 요청이 재시도되었는지, 어떤 오류가 발생했는지, 재시도 정책이 얼마나 효과적인지 알 수 없습니다. 메트릭을 추가하세요: 재시도 횟수, 재시도 후 성공, 지연 분포. 이 데이터는 특정 애플리케이션에 최적의 전략 매개변수를 조정하는 데 도움이 됩니다.

자주 묻는 질문

모바일 애플리케이션에서 요청을 몇 번 재시도해야 하나요?

대부분의 시나리오에서 재시도의 최적 횟수는 3~5회입니다. 백그라운드 동기화의 경우 5~7회가 허용됩니다. 대화형 요청(예: 양식 제출)의 경우 3회를 초과하지 마세요. 더 많은 재시도는 성공 확률을 높이지 않지만 사용자의 배터리와 데이터 트래픽을 소모합니다.

지수 백오프를 간단히 설명하면?

지수 백오프 — 재시도 간 지연이 두 배가 되는 것입니다: 1초, 2, 4, 8, 16... 서버가 과부하 상태인 경우 첫 번째 재시도 간의 짧은 일시 중지로 서버가 빠르게 응답할 수 있으며, 이후 재시도마다 증가하는 일시 중지는 서버에 더 많은 복구 시간을 제공합니다.

어떤 HTTP 상태 코드를 재시도해야 하나요?

일시적인 오류만 재시도하세요: 408(Request Timeout), 429(Too Many Requests), 502(Bad Gateway), 503(Service Unavailable), 504(Gateway Timeout). 4xx 오류(408과 429 제외)는 클라이언트 문제를 나타내며 — 재시도해도 의미가 없고 사용자 데이터에 위험할 수 있습니다.

재시도 정책과 서킷 브레이커의 차이는?

재시도 정책은 장애 발생 시 단일 요청의 재시도를 관리합니다. 서킷 브레이커는 서비스와의 연결 상태를 관리합니다: 오류가 누적되면 회로를 열고(OPEN) 새 요청을 차단합니다. 재시도는 개별 호출 수준에서 작동하고 서킷 브레이커는 서비스 통합 수준에서 작동합니다.

모바일 장치에서 재시도 정책을 테스트하는 방법은?

테스트를 위해 Android에서는 NetworkInterceptor(OkHttp), iOS에서는 URLProtocol(URLSession)을 사용하여 네트워크 장애를 시뮬레이션하세요. 오류 빈도, 사용 불가 기간, 응답 코드를 설정합니다. MockWebServer(OkHttp) 또는 OHHTTPStubs(iOS)를 사용한 단위 테스트는 실제 네트워크 없이 재시도 로직을 검증합니다.

요약

  • 재시도 정책 — 구성 가능한 지연 및 시도 횟수 매개변수로 일시적인 장애 시 네트워크 요청을 자동으로 재시도하는 전략입니다.
  • 지터가 있는 지수 백오프 — 모바일 애플리케이션의 기본 전략으로, 대량 장애 시 서버 부하를 줄이고 떼몰림 효과를 방지합니다.
  • 멱등성 — GET이 아닌 요청을 안전하게 재시도하기 위한 필수 조건입니다. 이것이 없으면 재시도로 인해 데이터 중복이나 원치 않는 부작용이 발생합니다.
  • 서킷 브레이커는 장기간 서비스를 사용할 수 없을 때 무한 재시도를 방지하고 장치 리소스를 절약하여 재시도 정책을 보완합니다.
  • 오류 분류를 재시도 가능(503, 502, 타임아웃)과 재시도 불가능(400, 401, 403)으로 나누는 것은 재시도 정책의 올바른 작동에 매우 중요합니다.
  • 대화형 시나리오에서 최대 3~5회, 백그라운드 동기화의 경우 최대 7회의 재시도가 Google Developer Relations에 따른 모바일 애플리케이션의 최적 값입니다.
  • 권장사항 — 모바일 애플리케이션의 모든 네트워크 요청에 대해 지수 백오프, 서킷 브레이커 및 로깅이 포함된 재시도 정책을 구현하세요.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기