유튜브 네트워크 오류 재시도 설정 추가

This commit is contained in:
김성경 2026-07-28 16:07:08 +09:00
parent 5903211eb9
commit 8096837b13

View File

@ -433,6 +433,10 @@ class YouTubeAnalyticsService:
logger.debug("[YouTubeAnalyticsService._fetch_region] SUCCESS")
return result
# 5xx/네트워크 오류 재시도 설정 (구글 backendError 등 일시적 장애 대응)
_MAX_RETRIES = 3
_RETRY_BACKOFF_SECONDS = (0.5, 1.0, 2.0)
async def _call_api(
self,
params: dict[str, str],
@ -453,51 +457,67 @@ class YouTubeAnalyticsService:
Raises:
YouTubeQuotaExceededError: 할당량 초과 (429)
YouTubeAuthError: 인증 실패 (401, 403)
YouTubeAPIError: 기타 API 오류
YouTubeAPIError: 기타 API 오류 (5xx/네트워크 오류는 최대 3 재시도 발생)
Note:
- 타임아웃: 30
- 할당량 초과 자동으로 YouTubeQuotaExceededError 발생
- 인증 실패 자동으로 YouTubeAuthError 발생
- 5xx 응답 네트워크 오류는 지수 백오프(0.5s1s2s) 최대 3 재시도
"""
headers = {"Authorization": f"Bearer {access_token}"}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(
self.BASE_URL,
params=params,
headers=headers,
)
# 할당량 초과 체크
if response.status_code == 429:
logger.warning("[YouTubeAnalyticsService._call_api] QUOTA_EXCEEDED")
raise YouTubeQuotaExceededError()
# 인증 실패 체크
if response.status_code in (401, 403):
logger.warning(
f"[YouTubeAnalyticsService._call_api] AUTH_FAILED - status={response.status_code}"
last_error: Exception | None = None
for attempt in range(self._MAX_RETRIES + 1):
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(
self.BASE_URL,
params=params,
headers=headers,
)
raise YouTubeAuthError(f"YouTube 인증 실패: {response.status_code}")
# HTTP 에러 체크
response.raise_for_status()
# 할당량 초과 체크
if response.status_code == 429:
logger.warning("[YouTubeAnalyticsService._call_api] QUOTA_EXCEEDED")
raise YouTubeQuotaExceededError()
return response.json()
# 인증 실패 체크
if response.status_code in (401, 403):
logger.warning(
f"[YouTubeAnalyticsService._call_api] AUTH_FAILED - status={response.status_code}"
)
raise YouTubeAuthError(f"YouTube 인증 실패: {response.status_code}")
except (YouTubeAuthError, YouTubeQuotaExceededError):
raise # 이미 처리된 예외는 그대로 전파
except httpx.HTTPStatusError as e:
logger.error(
f"[YouTubeAnalyticsService._call_api] HTTP_ERROR - "
f"status={e.response.status_code}, body={e.response.text[:500]}"
)
raise YouTubeAPIError(f"HTTP {e.response.status_code}")
except httpx.RequestError as e:
logger.error(f"[YouTubeAnalyticsService._call_api] REQUEST_ERROR - {e}")
raise YouTubeAPIError(f"네트워크 오류: {e}")
except Exception as e:
logger.error(f"[YouTubeAnalyticsService._call_api] UNEXPECTED_ERROR - {e}")
raise YouTubeAPIError(f"알 수 없는 오류: {e}")
# HTTP 에러 체크
response.raise_for_status()
return response.json()
except (YouTubeAuthError, YouTubeQuotaExceededError):
raise # 이미 처리된 예외는 재시도 없이 그대로 전파
except httpx.HTTPStatusError as e:
logger.error(
f"[YouTubeAnalyticsService._call_api] HTTP_ERROR - "
f"status={e.response.status_code}, body={e.response.text[:500]}"
)
# 4xx는 재시도해도 동일하게 실패하므로 즉시 전파, 5xx만 재시도
if e.response.status_code < 500:
raise YouTubeAPIError(f"HTTP {e.response.status_code}")
last_error = YouTubeAPIError(f"HTTP {e.response.status_code}")
except httpx.RequestError as e:
logger.error(f"[YouTubeAnalyticsService._call_api] REQUEST_ERROR - {e}")
last_error = YouTubeAPIError(f"네트워크 오류: {e}")
except Exception as e:
logger.error(f"[YouTubeAnalyticsService._call_api] UNEXPECTED_ERROR - {e}")
raise YouTubeAPIError(f"알 수 없는 오류: {e}")
if attempt < self._MAX_RETRIES:
backoff = self._RETRY_BACKOFF_SECONDS[attempt]
logger.warning(
f"[YouTubeAnalyticsService._call_api] RETRY {attempt + 1}/{self._MAX_RETRIES} "
f"in {backoff}s - {last_error}"
)
await asyncio.sleep(backoff)
raise last_error