Python 비동기 처리 asyncio 실전 적용기
문제 상황
데이터 수집 API 서버에서 외부 API 10개를 순차 호출하는 로직이 있었다. 각 호출당 평균 200ms씩 소요되어 전체 응답 시간이 2초를 넘어갔다. 사용자 경험상 1초 이내로 줄여야 했다.
asyncio 도입
Python 3.7부터 asyncio가 안정화되면서 실무 적용을 검토했다. requests 라이브러리를 aiohttp로 교체하고 async/await 패턴으로 전환했다.
import asyncio
import aiohttp
async def fetch_api(session, url):
async with session.get(url) as response:
return await response.json()
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch_api(session, url) for url in urls]
return await asyncio.gather(*tasks)
# 실행
results = asyncio.run(fetch_all(api_urls))
주의사항
-
Connection Pool 관리: aiohttp는 기본적으로 connection을 재사용한다. 하지만 너무 많은 동시 요청은 서버에 부담을 준다.
TCPConnector(limit=10)로 제한했다. -
에러 처리: 하나의 API 실패가 전체를 멈추지 않도록
gather(return_exceptions=True)옵션을 사용했다. -
블로킹 코드 주의: DB 조회나 파일 I/O 같은 동기 작업이 섞이면 효과가 반감된다.
run_in_executor로 별도 처리했다.
결과
평균 응답 시간이 2.1초에서 0.4초로 개선됐다. CPU 사용률은 거의 동일했고 메모리는 10% 정도 증가했다. 트래픽이 늘어도 스케일링 없이 버틸 수 있게 됐다.
비동기 코드는 디버깅이 까다롭지만, I/O 바운드 작업에서는 확실한 이점이 있었다. 다만 팀원 모두가 async/await에 익숙해지기까지 시간이 필요했다.