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

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