Python asyncio로 API 병렬 호출 성능 개선하기

문제 상황

매일 새벽 돌아가는 데이터 수집 배치 작업이 있었다. 외부 API를 호출해 약 500개 항목의 데이터를 가져오는데, 순차 처리 방식이라 10분 이상 소요됐다.

# 기존 코드
def fetch_all_data(ids):
    results = []
    for id in ids:
        response = requests.get(f'https://api.example.com/items/{id}')
        results.append(response.json())
    return results

각 API 호출이 1~2초 걸리는데 순차 처리하니 당연히 느렸다.

해결 방법

aiohttp와 asyncio를 사용해 병렬 처리로 변경했다.

import asyncio
import aiohttp

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

async def fetch_all_data(ids):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_item(session, id) for id in ids]
        return await asyncio.gather(*tasks)

# 실행
results = asyncio.run(fetch_all_data(ids))

주의사항

처음엔 동시 요청 수 제한 없이 실행했다가 API 서버에서 429 에러가 발생했다. Semaphore로 동시 요청 수를 20개로 제한하니 안정적으로 동작했다.

async def fetch_all_data(ids, limit=20):
    semaphore = asyncio.Semaphore(limit)
    
    async def fetch_with_limit(session, id):
        async with semaphore:
            return await fetch_item(session, id)
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_with_limit(session, id) for id in ids]
        return await asyncio.gather(*tasks)

결과

  • 실행 시간: 10분 → 1분 30초
  • 동시 요청 수 제한으로 API 서버 부하 관리
  • 기존 동기 코드 대비 약간의 복잡도 증가는 있지만 성능 개선이 확실했다

I/O 바운드 작업에서 asyncio는 확실히 효과적이었다.