Python 비동기 처리 실전 적용기 - asyncio와 aiohttp

문제 상황

데이터 수집 배치 작업이 점점 느려지고 있었다. 외부 API 500개를 순차 호출하는데 약 10분이 걸렸고, 대부분의 시간이 I/O 대기였다.

import requests

def fetch_data(urls):
    results = []
    for url in urls:
        response = requests.get(url)
        results.append(response.json())
    return results

asyncio 도입

Python 3.7부터 안정화된 asyncio를 적용하기로 했다. aiohttp를 사용해 비동기 HTTP 요청을 처리했다.

import asyncio
import aiohttp

async def fetch_one(session, url):
    async with session.get(url) 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)

# 실행
results = asyncio.run(fetch_data(urls))

주의사항

동시 요청 수 제한이 필요했다. Semaphore로 동시성을 제어했다.

async def fetch_data(urls, concurrency=50):
    semaphore = asyncio.Semaphore(concurrency)
    
    async def fetch_with_limit(session, url):
        async with semaphore:
            return await fetch_one(session, url)
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_with_limit(session, url) for url in urls]
        return await asyncio.gather(*tasks, return_exceptions=True)

에러 핸들링도 중요했다. 일부 요청 실패가 전체를 멈추지 않도록 return_exceptions=True를 사용했다.

결과

10분 → 2.5분으로 단축됐다. CPU 사용률은 5% 미만으로 유지되며 I/O를 효율적으로 처리했다. 내년에는 다른 배치 작업들도 순차적으로 전환할 예정이다.

Python 비동기 처리 실전 적용기 - asyncio와 aiohttp