Python asyncio로 API 병렬 호출 최적화

문제 상황

매일 새벽 실행되는 배치 작업이 있었다. 외부 API를 호출해 데이터를 수집하는 작업인데, 처리해야 할 항목이 5000개를 넘어가면서 실행 시간이 40분을 초과하기 시작했다.

기존 코드는 requests 라이브러리로 순차 호출하는 구조였다.

import requests

def fetch_data(item_id):
    response = requests.get(f'https://api.example.com/items/{item_id}')
    return response.json()

results = []
for item_id in item_ids:  # 5000+ items
    data = fetch_data(item_id)
    results.append(data)

asyncio와 aiohttp로 전환

비동기 방식으로 전환하기로 했다. aiohttp를 사용해 동시에 여러 요청을 처리하도록 수정했다.

import asyncio
import aiohttp

async def fetch_data(session, item_id):
    async with session.get(f'https://api.example.com/items/{item_id}') as response:
        return await response.json()

async def fetch_all(item_ids):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_data(session, item_id) for item_id in item_ids]
        return await asyncio.gather(*tasks)

results = asyncio.run(fetch_all(item_ids))

발생한 문제들

1. 동시 연결 제한

처음에는 5000개 요청을 한번에 보냈더니 서버에서 429 에러가 발생했다. Semaphore로 동시 요청 수를 제한했다.

semaphore = asyncio.Semaphore(50)  # 최대 50개 동시 요청

async def fetch_data(session, item_id):
    async with semaphore:
        async with session.get(f'https://api.example.com/items/{item_id}') as response:
            return await response.json()

2. 타임아웃 처리

일부 요청이 응답 없이 멈추는 경우가 있었다. ClientTimeout을 설정해 해결했다.

timeout = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession(timeout=timeout) as session:
    # ...

결과

  • 실행 시간: 40분 → 8분
  • 동시 요청 수: 50개로 제한
  • 타임아웃: 30초

기존 동기 방식 대비 약 80%의 시간을 절약했다. 배치 작업 특성상 순서가 중요하지 않아 asyncio가 적합한 케이스였다.