Python 비동기 HTTP 요청 처리 시 타임아웃 이슈 해결
문제 상황
레거시 동기 HTTP 클라이언트를 aiohttp 기반 비동기로 전환하는 작업을 진행했다. 기존에는 requests 라이브러리로 순차 처리했던 100여 개의 외부 API 호출을 동시에 처리하도록 변경했다.
배포 후 특정 요청들이 간헐적으로 응답을 받지 못하고 무한 대기하는 현상이 발생했다. 모니터링 결과 전체 요청 중 5% 정도가 타임아웃 없이 계속 pending 상태로 남아있었다.
초기 접근
timeout = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
return await response.json()
ClientTimeout을 설정했지만 문제는 계속 발생했다. 로그를 확인해보니 connection pool에서 연결을 재사용하는 과정에서 keep-alive timeout과 충돌하는 케이스가 있었다.
해결 방법
개별 요청마다 명시적 타임아웃을 설정하고, TCPConnector의 옵션을 조정했다.
connector = aiohttp.TCPConnector(
limit=100,
ttl_dns_cache=300,
force_close=True # 연결 재사용 비활성화
)
timeout = aiohttp.ClientTimeout(
total=30,
connect=5,
sock_read=10
)
async with aiohttp.ClientSession(
connector=connector,
timeout=timeout
) as session:
try:
async with session.get(url, timeout=timeout) as response:
return await response.json()
except asyncio.TimeoutError:
logger.warning(f"Timeout for {url}")
return None
force_close=True로 연결 재사용을 비활성화하니 문제가 해결됐다. 성능은 약간 감소했지만 안정성이 더 중요했다.
결론
aiohttp의 connection pooling은 성능에 유리하지만, 외부 API처럼 제어 불가능한 엔드포인트를 다룰 때는 신중해야 한다. 필요에 따라 연결 재사용을 포기하는 것도 합리적 선택이다.