Python asyncio로 API 병렬 호출 최적화하기
문제 상황
재택근무 전환 이후 데이터 동기화 배치 작업의 실행 시간이 문제가 되었다. 약 500개의 사용자 데이터를 외부 API로 조회하는데, 순차 처리로 인해 25분 이상 소요되고 있었다.
기존 코드는 단순한 for문 반복이었다.
def sync_user_data(user_ids):
results = []
for user_id in user_ids:
response = requests.get(f'{API_URL}/users/{user_id}')
results.append(response.json())
return results
asyncio와 aiohttp 적용
Python 3.7 이상에서 안정화된 asyncio를 사용하기로 했다. aiohttp로 비동기 HTTP 요청을 처리했다.
import asyncio
import aiohttp
async def fetch_user(session, user_id):
async with session.get(f'{API_URL}/users/{user_id}') as response:
return await response.json()
async def sync_user_data(user_ids):
async with aiohttp.ClientSession() as session:
tasks = [fetch_user(session, user_id) for user_id in user_ids]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
# 실행
results = asyncio.run(sync_user_data(user_ids))
개선 사항
- 실행 시간: 25분 → 5분
- 동시 요청 수 제한이 필요해 semaphore 추가
- return_exceptions=True로 일부 실패해도 전체 중단 방지
async def sync_user_data(user_ids, concurrency=50):
semaphore = asyncio.Semaphore(concurrency)
async def fetch_with_limit(session, user_id):
async with semaphore:
return await fetch_user(session, user_id)
async with aiohttp.ClientSession() as session:
tasks = [fetch_with_limit(session, uid) for uid in user_ids]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
asyncio는 I/O 바운드 작업에서 효과가 확실했다. CPU 집약적인 작업엔 multiprocessing이 맞겠지만, API 호출이 대부분인 배치 작업에선 이 방식이 충분했다.