SwiftUI 레이트 제한 및 백프레셔 (자신의 앱으로부터 백엔드 보호)

발행: (2026년 3월 9일 AM 02:13 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

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

try await api.fetchFeed()

이것은 정상적으로 동작합니다… 하지만 한 번에 많은 요청이 발생하면 문제가 생깁니다.

예시

  • 무한 새로고침 루프
  • 과도한 백그라운드 동기화
  • 동일한 데이터를 요청하는 여러 뷰
  • 네트워크 복구 후 발생하는 재시도 폭풍
  • 동시에 동기화되는 여러 기기

갑자기 여러분의 앱이 자체 백엔드에 가장 큰 위협이 됩니다.

🧠 핵심 원칙

건강한 시스템은 요청 속도를 제어합니다. 모든 것이 정상적으로 작동하더라도, 제어되지 않은 트래픽은 API를 과부하시킬 수 있습니다.

🧱 1. Rate Limiting이란?

Rate limiting은 일정 시간 창에서 발생할 수 있는 요청 수를 제어합니다.

예시 규칙

10 requests per second

더 많은 요청이 들어오면 대기, 대기열에 넣기, 혹은 거부해야 합니다. 이는 백엔드와 클라이언트 장치를 모두 보호합니다.

🧬 2. 토큰 버킷 모델

일반적인 접근 방식은 토큰 버킷 알고리즘입니다.

개념

Bucket contains tokens
Each request consumes one token
Tokens refill over time

버킷이 비어 있으면, 요청은 대기해야 합니다.

🧱 3. 간단한 속도 제한기 구현

actor RateLimiter {
    private let maxTokens: Int
    private let refillInterval: TimeInterval

    private var tokens: Int
    private var lastRefill: Date

    init(maxTokens: Int, refillInterval: TimeInterval) {
        self.maxTokens = maxTokens
        self.refillInterval = refillInterval
        self.tokens = maxTokens
        self.lastRefill = Date()
    }

    func acquire() async {
        refill()
        while tokens == 0 {
            try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 s
            refill()
        }
        tokens -= 1
    }

    private func refill() {
        let now = Date()
        let elapsed = now.timeIntervalSince(lastRefill)
        if elapsed >= refillInterval {
            tokens = maxTokens
            lastRefill = now
        }
    }
}

이렇게 하면 요청이 제어된 속도로 이루어집니다.

🚦 4. Using the Rate Limiter

Wrap your API calls:

let limiter = RateLimiter(maxTokens: 5, refillInterval: 1)

func fetchFeed() async throws -> Feed {
    await limiter.acquire()
    return try await api.fetchFeed()
}

Now your app cannot exceed 5 requests per second.

🔄 5. 백프레셔란 무엇인가?

Backpressure는 시스템이 처리할 수 있는 것보다 더 많은 작업을 생성하는 것을 방지합니다.

시나리오

스크롤 뷰가 50개의 이미지 로드를 트리거함

백프레셔 없이: 50개의 네트워크 요청이 즉시 발생합니다.
백프레셔와 함께: 요청이 큐에 쌓여 점진적으로 실행됩니다.

이는 CPU, 메모리, 네트워크 및 백엔드 API를 보호합니다.

📦 6. 요청 큐 패턴

actor RequestQueue {
    private var queue: [() async -> Void] = []

    func enqueue(_ task: @escaping () async -> Void) {
        queue.append(task)
        processNext()
    }

    private func processNext() {
        guard !queue.isEmpty else { return }
        let task = queue.removeFirst()
        Task {
            await task()
            processNext()
        }
    }
}

이 패턴은 대기 중인 작업들의 제어된 실행을 보장합니다.

🔋 7. Why Mobile Apps Need Rate Limiting

모바일 환경은 예측하기 어렵습니다. 일반적인 문제는 다음과 같습니다:

  • API 할당량
  • 느린 셀룰러 연결
  • 백그라운드 동기화 급증
  • 다중 탭 새로 고침 루프

제한이 없으면 백엔드가 과부하되고 배터리 소모가 빨라지며 UI가 불안정해집니다. 속도 제한은 시스템을 안정화시킵니다.

🌐 8. 회로 차단기와 결합하기

Circuit Breakers → stop requests during failures
Rate Limiting   → control requests during success

함께하면 완전한 네트워크 복원력을 형성합니다.

🧪 9. 속도 제한 테스트

테스트 시나리오

  • 빠른 새로고침 루프
  • 네트워크 호출을 트리거하는 대형 스크롤 리스트
  • 백그라운드 동기화 폭발
  • 재시도 폭풍

검증 항목

  • 요청 속도가 안정적으로 유지되는지
  • 큐가 올바르게 처리되는지
  • 백엔드 과부하가 발생하지 않는지

⚠️ 10. 공통 안티 패턴

  • 무제한 병렬 요청
  • 실패 후 즉시 재시도
  • 조정 없이 뷰당 네트워크 호출
  • 서버 속도 제한 무시
  • 동일 요청 중복 제거 안 함

이러한 문제는 요청 폭풍, API 차단, 배터리 소모, 백엔드 불안정을 초래합니다.

🧠 멘탈 모델

User Action
 → Request Queue
   → Rate Limiter
     → Network Request
       → API

Not: “Fire every request immediately.” → 주의: “모든 요청을 즉시 전송하지 마세요.”

🚀 최종 생각

  • 예측 가능한 네트워크 동작
  • 백엔드 보호
  • 원활한 성능
  • 배터리 사용 효율 향상
  • 프로덕션 수준의 복원력
0 조회
Back to Blog

관련 글

더 보기 »

Google I/O 2026을 준비하세요

Google I/O가 5월 19일~20일에 돌아옵니다! Google I/O가 다시 시작됩니다. 온라인에 참여해 최신 AI 혁신과 전사 제품 업데이트를 공유합니다. Gemini부터…