Python asyncio로 API 호출 속도 개선하기
문제 상황
회사가 재택근무로 전환되면서 VPN 대역폭 이슈로 기존에 잘 돌아가던 배치 작업이 타임아웃을 내기 시작했다. 약 200개의 외부 API를 순차적으로 호출하는 스크립트였는데, 평소 5분 걸리던 작업이 20분 넘게 걸렸다.
기존 코드는 requests 라이브러리로 동기 방식 호출이었다.
import requests
def fetch_data(urls):
results = []
for url in urls:
response = requests.get(url, timeout=10)
results.append(response.json())
return results
asyncio + aiohttp 적용
I/O 바운드 작업이 명확했기 때문에 asyncio로 전환했다. aiohttp를 사용해 비동기 HTTP 요청을 구현했다.
import asyncio
import aiohttp
async def fetch_one(session, url):
async with session.get(url, timeout=10) as response:
return await response.json()
async def fetch_data(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch_one(session, url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
# 실행
results = asyncio.run(fetch_data(urls))
동시 요청 수를 제한하고 싶다면 asyncio.Semaphore를 추가했다.
async def fetch_with_limit(urls, limit=20):
semaphore = asyncio.Semaphore(limit)
async def bounded_fetch(session, url):
async with semaphore:
return await fetch_one(session, url)
async with aiohttp.ClientSession() as session:
tasks = [bounded_fetch(session, url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
결과
- 실행 시간: 20분 → 4분
- 에러 핸들링이 필요해서
return_exceptions=True옵션 활용 - 세마포어로 동시 요청 20개로 제한해 API 서버 부하 조절
VPN 대역폭이 제한적인 상황에서도 병렬 처리로 충분히 개선할 수 있었다. 기존 스크립트 구조를 크게 바꾸지 않아도 되어 배포도 빠르게 진행했다.