← 블로그로 돌아가기

파서의 재시도 로직: 재요청이 트래픽의 40%를 소모하는 방법과 이를 해결하는 방법

재요청은 프록시 트래픽의 40%까지 소모할 수 있습니다. 실제 손실을 계산하고 올바른 백오프, 서킷 브레이커 및 스마트 프록시 회전을 통해 이를 줄이는 방법을 보여줍니다.

📅2026년 9월 27일

프록시 요금이 수집된 데이터 양보다 빠르게 증가한다면, 문제는 거의 항상 retry-로직에 있습니다. 파서는 실패한 요청을 조용히 여러 번 반복하며, 타임아웃과 CAPTCHA에 트래픽을 소모하고, 개발자는 이러한 비용을 로그에서조차 확인하지 못합니다. 실제 손실을 계산하고 데이터 품질을 잃지 않고 이를 줄이는 방법을 살펴보겠습니다.

왜 retry-로직이 트래픽을 소모하는가

대부분의 파서는 단순한 retry-로직으로 작성됩니다: 요청이 실패하면 반복하고, 그렇게 3-5번까지 반복합니다. 문제는 각 반복 요청이 새로운 HTTP 요청일 뿐만 아니라 전체 사이클을 포함한다는 것입니다: TCP 핸드셰이크, TLS 협상, 페이지 전체 로드(하나의 데이터 블록만 필요하더라도), 때때로 이미지나 JS 파일의 재로드까지, 파서가 단순한 HTTP 클라이언트 대신 헤드리스 브라우저를 사용할 경우 발생합니다.

특히 주거용 프록시를 통해 작업할 때 반복 요청은 비용이 많이 듭니다, 여기서 트래픽은 요청 수가 아닌 양에 따라 요금이 부과됩니다. 상품 페이지의 실패한 요청 하나는 이미지와 스크립트로 인해 300-500KB의 비용이 발생할 수 있습니다. 파서가 타임아웃 시 3번 반복하면, 같은 실패한 요청에 대해 네 번 연속으로 비용을 지불하게 되며, 네 번째 시도가 성공할 것이라는 보장은 없습니다.

두 번째 이유는 반복으로 수정할 수 없는 오류에 대한 retry입니다. 사이트가 봇을 감지하여 403을 반환하면, 동일한 핑거프린트와 동일한 세션 쿠키로 반복 요청을 하면 거의 확실히 동일한 응답을 받게 됩니다. 파서는 IP 주소나 브라우저 핑거프린트가 변경되지 않는 한 성공할 수 없는 시도에 트래픽을 소모합니다.

실제로 반복에 얼마나 많은 트래픽이 소모되는가

문제의 규모를 이해하기 위해 간단한 예를 들어보겠습니다. 파서는 마켓플레이스에서 상품 카드를 수집하며, 평균 응답 크기는 250KB(HTML + JSON API + 일부 정적 파일)입니다. 차단 없이 안정적으로 작업할 때 실패한 요청의 비율은 5-8% 수준을 유지합니다. 그러나 저렴한 데이터 센터 프록시를 통해 공격적으로 파싱할 경우, 이 비율은 25-35%까지 상승할 수 있습니다. 이는 목표 제공자가 패턴을 빠르게 인식하고 CAPTCHA 또는 IP에 대한 임시 차단을 시작하기 때문입니다.

숫자로 계산해 보겠습니다. 100,000개의 상품 카드를 수집해야 한다고 가정해 보겠습니다:

실패한 요청 비율 1 요청당 반복 횟수 (평균) 최종 트래픽 과소비
5% 0.15 28.75GB +15%
15% 0.45 36.25GB +45%
30% 0.90 47.5GB +90%

보시다시피, 실패 비율이 30%이고 retry 전략이 "3번까지 반복"이라면, 실제 트래픽은 이론적 최소인 25GB에 비해 거의 두 배가 됩니다. 이 추가적인 20+GB는 프록시 비용의 직접적인 손실이며, 반복 로직을 재검토하면 줄일 수 있습니다.

파서의 retry-로직에서 흔히 발생하는 실수

retry-로직을 수정하기 전에, 90%의 사용자 정의 파서에서 발생하는 전형적인 안티 패턴을 인식해야 합니다:

  • 오류 코드 분석 없이 반복. 403, 429, 500, 타임아웃, 연결 끊김 등 모든 비정상 상황에서 반복이 시작되며, 이들에 대한 처리 전략은 달라야 합니다.
  • 고정된 지연 시간. 예를 들어, 첫 번째 시도든 다섯 번째 시도든 관계없이 2초의 지연을 두는 것은 사이트에 대해 너무 공격적이거나 대량의 요청에 대해 너무 느립니다.
  • 동일한 IP와 동일한 세션으로 반복. 사이트가 핑거프린트로 요청을 차단한 경우, 동일한 매개변수로 반복하면 결과가 바뀌지 않지만 트래픽만 소모됩니다.
  • 시도 횟수에 대한 상한선 없음. 일부 파서는 "죽은" URL에 대해 반복을 계속하며, 포기하기 전에 수십 번 반복합니다.
  • 일시적 오류와 영구적 오류의 구분 없음. 404(페이지 없음)와 503(서버 일시적 사용 불가)는 다른 로직을 요구하지만, 종종 동일하게 처리됩니다.

Python 코드로 구현한 지수적 backoff

간단하지만 효과적인 해결책은 지수적 지연과 지터(무작위 변동)를 사용하는 것입니다. 이는 무의미한 반복을 줄이고 시간에 따라 부하를 분산시킵니다. 시도 간의 고정된 대기 시간 대신 지연 시간이 지수적으로 증가하여 사이트가 차단 후 "식힐" 시간을 제공하며, 파서가 거의 확실히 실패할 요청에 대해 트래픽을 낭비하지 않도록 합니다.

import time
import random
import requests

def fetch_with_backoff(url, proxies, max_retries=4, base_delay=1.0):
    retryable_codes = {429, 500, 502, 503, 504}
    non_retryable_codes = {404, 410}

    for attempt in range(max_retries + 1):
        try:
            response = requests.get(url, proxies=proxies, timeout=10)

            if response.status_code == 200:
                return response

            if response.status_code in non_retryable_codes:
                # 반복할 필요 없음 — 페이지가 물리적으로 존재하지 않음
                return None

            if response.status_code not in retryable_codes:
                return None

        except (requests.exceptions.Timeout,
                requests.exceptions.ConnectionError):
            pass  # 일시적인 네트워크 오류 — 반복 가능

        if attempt == max_retries:
            return None

        # 지수적 지연과 지터
        delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
        time.sleep(delay)

    return None

이 코드의 핵심 아이디어는 오류를 세 가지 범주로 나누는 것입니다: 반복으로 수정할 수 없는 오류(404, 410), 지연을 두고 반복할 수 있는 오류(429, 500-504, 타임아웃) 및 추가 시도에 대한 비용 없이 즉시 실패로 간주되는 나머지 오류. 이러한 분류만으로도 무의미한 트래픽을 20-30% 줄일 수 있습니다.

팁: Retry-After 헤더를 처리에 추가하세요 — 많은 사이트가 반복 전에 기다려야 할 초를 알려줍니다. 이 헤더를 무시하는 것은 불필요한 차단과 트래픽의 일반적인 원인입니다.

Circuit breaker: 언제 멈춰야 하는가

지수적 backoff는 단일 요청 수준에서 도움이 되지만, 전체 도메인이나 특정 프록시 노드가 수백 개의 URL에 대해 일시적으로 사용할 수 없을 때는 보호하지 않습니다. 여기서는 circuit breaker 패턴이 필요합니다 — "자동 차단기"로, 최근 기간 동안의 오류 비율을 추적하고, 이 비율이 임계값을 초과하면 시도를 일시적으로 중단합니다. 계속해서 닫힌 문을 두드리는 대신에 말입니다.

class CircuitBreaker:
    def __init__(self, failure_threshold=0.5, window_size=50, cooldown=60):
        self.failure_threshold = failure_threshold
        self.window_size = window_size
        self.cooldown = cooldown
        self.results = []
        self.open_until = 0

    def is_open(self):
        return time.time() < self.open_until

    def record(self, success: bool):
        self.results.append(success)
        if len(self.results) > self.window_size:
            self.results.pop(0)

        if len(self.results) == self.window_size:
            failure_rate = 1 - sum(self.results) / self.window_size
            if failure_rate > self.failure_threshold:
                self.open_until = time.time() + self.cooldown
                self.results.clear()

로직은 간단합니다: 최근 50개의 요청 중 절반 이상이 실패하면, 파서는 이 도메인이나 프록시에 대한 시도를 60초 동안 중단합니다. 이 시간 동안 IP를 변경하거나 요청 속도를 줄이거나 다른 프록시 풀로 전환할 수 있습니다. 이는 요청 빈도를 초과한 후 일시적으로 IP 범위를 차단하는 목표 사이트와 작업할 때 특히 중요합니다 — 닫힌 문을 계속 두드리는 것은 단순히 트래픽을 낭비하는 것입니다.

반복 시 스마트 프록시 회전

불필요한 retry에 대한 가장 효과적인 조치 중 하나는 이미 거부된 동일한 IP로 요청을 반복하지 않는 것입니다. 로직은 간단합니다: 오류가 IP 차단과 관련이 있다면(403, 429, CAPTCHA로 리디렉션), 반복 전에 프록시를 변경하면 성공 확률이 크게 높아지고 시도 횟수를 줄일 수 있습니다.

Wildberries, Ozon 또는 Avito와 같은 마켓플레이스를 파싱할 때, 일반 요청은 데이터 센터 프록시를 통해 이루어지며, 이들은 더 빠르고 저렴합니다. 차단 감지기가 여러 번 연속으로 작동하면, 파서는 주거용 프록시로 전환하여, 이들은 안티봇 시스템의 필터에 덜 걸립니다. 이러한 하이브리드 접근 방식은 전체 트래픽 소비를 줄입니다.

오류 유형 재시도 전략 IP 변경 필요?
연결 타임아웃 Backoff, 1-2회 재시도 아니오
403 / CAPTCHA 즉시 회전 예, 필수
429 (요청 제한) Retry-After에 따른 Backoff 바람직함
500-503 Backoff, 2-3회 재시도 아니오
404 / 410 재시도 없음 —

모바일 트래픽을 에뮬레이션하는 파서(예: 마켓플레이스 또는 소셜 미디어의 모바일 버전에서 데이터 수집)에는 모바일 프록시를 사용하는 것이 좋습니다 — 이들은 통신사가 동일한 IP를 수천 명의 실제 사용자에게 동시에 제공하기 때문에 보안 시스템의 의심을 덜 받습니다.

retry 메트릭 모니터링

메트릭 없이 retry-로직 최적화는 추측이 됩니다. 각 요청 시 기록해야 할 최소한의 지표는 다음과 같습니다:

  • Retry 비율 — 최소한 한 번의 재시도가 필요했던 요청의 비율.
  • 재시도 후 성공 — 최종적으로 성공한 재시도의 비율(이 비율이 낮으면 재시도가 단순히 트래픽을 소모합니다).
  • 성공적인 결과에 대한 트래픽 — 전송된 데이터의 총량을 성공적으로 수집된 레코드 수로 나눈 값. 이는 효율성의 핵심 메트릭입니다.
  • 오류 코드별 오류 분포 — 타임아웃, 403, 429 또는 기타 오류로 인해 트래픽이 누수되는 주요 원인을 이해하는 데 도움이 됩니다.
  • 특정 프록시 노드에 대한 retry 비율 — 한 IP가 80%의 retry 비율을 제공하고 나머지가 10%인 경우, 문제는 국소적이며 특정 노드를 변경하여 해결할 수 있습니다.

Google Sheets의 간단한 표나 이 다섯 가지 메트릭이 포함된 CSV 로그를 매시간 업데이트하면, 이상 징후를 확인하고 전략을 적시에 조정할 수 있는 충분한 데이터를 제공합니다 — 예를 들어, 특정 사이트 섹션에 대한 요청 빈도를 줄이거나 프록시 풀에서 주거용 IP의 비율을 늘리는 것입니다.

retry에 대한 트래픽 최적화 체크리스트

  1. 오류 코드를 retryable과 non-retryable로 나누세요 — 404/410은 반복하지 마세요.
  2. 고정된 지연 대신 지수적 backoff와 지터를 구현하세요.
  3. 사이트가 제공하는 경우 Retry-After 헤더를 존중하세요.
  4. 403 및 봇 감지 의심 시 반복 전에 IP를 변경하세요.
  5. 시도 횟수에 대한 엄격한 한계를 설정하세요 (일반적으로 3-4회면 충분합니다).
  6. 높은 실패 비율을 가진 도메인 및 프록시 노드에 대해 circuit breaker를 구현하세요.
  7. retry 비율과 성공적인 결과에 대한 트래픽을 기록하세요 — 메트릭 없이는 최적화가 불가능합니다.
  8. 프록시 풀을 분리하세요: 안정적인 구역에는 저렴한 데이터 센터를, 문제 구역에는 주거용 또는 모바일 IP를 사용하세요.

결론

Retry-로직은 파서의 작은 세부 사항이 아니라 데이터 수집 비용에 영향을 미치는 주요 요소 중 하나입니다. 단순한 "모두 반복하기" 전략은 이론적 최소에 비해 실제 트래픽을 40-90% 증가시킬 수 있으며, 대부분의 반복은 첫 번째 시도와 동일한 실패로 끝납니다. 오류를 유형별로 나누고, 지수적 backoff, circuit breaker 및 스마트 IP 회전을 통해 이러한 손실을 여러 배 줄일 수 있습니다.

만약 귀하의 파서가 봇을 공격적으로 감지하는 사이트와 작업한다면 — 마켓플레이스, 소셜 미디어, 광고 플랫폼 등 — 작업에 따라 여러 유형의 프록시를 조합하는 것이 좋습니다. 기본 작업에는 데이터 센터 프록시가 적합하며, 차단에 대한 최대한의 저항이 필요한 경우에는 주거용 프록시를 사용하여 일반 사용자들의 실제 IP 주소를 사용하세요. 이러한 하이브리드 접근 방식은 동일한 데이터 양을 수집하면서 트래픽 소비를 눈에 띄게 줄입니다.