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가 적합한 케이스였다.