Python 비동기 처리에서 asyncio.gather 예외 처리

문제 상황

외부 API 5개를 동시에 호출해 데이터를 집계하는 기능을 구현했다. asyncio.gather로 병렬 처리했는데, 한 개 API만 실패해도 전체가 실패하는 문제가 있었다.

async def fetch_all():
    results = await asyncio.gather(
        fetch_api_a(),
        fetch_api_b(),
        fetch_api_c()
    )
    return results

API 하나가 타임아웃되면 전체 결과를 받지 못했다.

해결 방법

return_exceptions=True 옵션을 사용하면 예외가 발생해도 계속 진행되고, 결과 리스트에 예외 객체가 포함된다.

async def fetch_all():
    results = await asyncio.gather(
        fetch_api_a(),
        fetch_api_b(),
        fetch_api_c(),
        return_exceptions=True
    )
    
    # 성공한 결과만 필터링
    valid_results = [
        r for r in results 
        if not isinstance(r, Exception)
    ]
    
    # 실패 로깅
    for i, r in enumerate(results):
        if isinstance(r, Exception):
            logger.error(f"API {i} failed: {r}")
    
    return valid_results

추가 개선

각 API마다 재시도 로직을 추가했다. tenacity 라이브러리를 사용하면 간단하게 구현할 수 있다.

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), 
       wait=wait_exponential(multiplier=1, min=2, max=10))
async def fetch_api_a():
    async with aiohttp.ClientSession() as session:
        async with session.get(url, timeout=5) as resp:
            return await resp.json()

이렇게 수정한 후 부분 장애 상황에서도 서비스가 정상 동작하게 되었다. 모니터링 결과 전체 실패율이 5%에서 0.3%로 감소했다.