Python 비동기 처리에서 asyncio.gather vs asyncio.as_completed 선택 기준

문제 상황

사용자 대시보드 API에서 3개 외부 서비스(결제, 알림, 통계)를 동시에 호출해야 했다. 처음엔 순차 호출로 구현했는데 응답 시간이 1.5초나 걸렸다. 비동기 처리가 필요했다.

asyncio.gather 사용

모든 결과가 필요한 경우 gather가 적합했다.

import asyncio
import aiohttp

async def fetch_payment(user_id):
    async with aiohttp.ClientSession() as session:
        async with session.get(f'/api/payment/{user_id}') as resp:
            return await resp.json()

async def fetch_notifications(user_id):
    # 비슷한 구조
    pass

async def get_dashboard(user_id):
    results = await asyncio.gather(
        fetch_payment(user_id),
        fetch_notifications(user_id),
        fetch_statistics(user_id)
    )
    return {
        'payment': results[0],
        'notifications': results[1],
        'statistics': results[2]
    }

응답 시간이 500ms로 단축됐다. 하나라도 실패하면 전체가 실패하는 구조였다.

asyncio.as_completed 적용

실시간 로그 수집 기능에서는 먼저 완료되는 것부터 처리해야 했다.

async def collect_logs(server_ids):
    tasks = [fetch_server_log(sid) for sid in server_ids]
    results = []
    
    for coro in asyncio.as_completed(tasks):
        try:
            result = await coro
            results.append(result)
            # 완료되는 즉시 클라이언트에 스트리밍
            yield result
        except Exception as e:
            # 개별 실패는 무시하고 계속 진행
            continue
    
    return results

선택 기준

gather 사용:

  • 모든 결과가 필요할 때
  • 결과 순서가 중요할 때
  • 하나라도 실패하면 전체 실패 처리가 맞을 때

as_completed 사용:

  • 완료되는 대로 즉시 처리할 때
  • 일부 실패해도 나머지는 처리해야 할 때
  • 사용자에게 점진적 응답을 보여줄 때

실무에서는 gather에 return_exceptions=True를 추가해 부분 실패를 허용하는 경우가 많았다. 상황에 맞는 도구를 선택하는 게 중요하다.