aiohttp ClientTimeout: 단계별 타임아웃 설정
Context
이미지 다운로드 로직에서 SoftTimeout 에러가 연쇄 발생했다. 원인을 추적해보니 dead host에 대해 session.get(url)에 timeout이 없어서, OS 레벨 TCP connect timeout(~30s)이 3번 발동하는 구조였다.
3 × 30s (TCP timeout) + 6s (retry sleep) ≈ 96s / URL
dead host 하나가 96초짜리 병목이 되어 전체 세션을 잡아먹고 있었다.
What I Learned
aiohttp.ClientTimeout은 total 하나만 있는 게 아니라 연결 단계별로 따로 걸 수 있다.
| 필드 | 적용 범위 |
|---|---|
total | 전체 작업 (연결 + 요청 + 응답 읽기) 최대 시간 |
connect | 새 연결 수립 또는 풀에서 여유 연결 대기 최대 시간 |
sock_connect | 실제 소켓 연결(peer 연결) 최대 시간 |
sock_read | 응답 데이터를 peer로부터 읽는 사이 최대 대기 시간 |
기본값:
aiohttp.ClientTimeout(
total=5*60, # 5분
connect=None, # 제한 없음
sock_connect=None,
sock_read=None,
)
dead host 문제처럼 연결 자체가 안 되는 케이스는 total만으로는 대응이 늦다. sock_connect를 짧게 걸면 연결 시도 단계에서 일찍 끊을 수 있다.
timeout = aiohttp.ClientTimeout(
total=10,
sock_connect=3, # 연결 자체가 3초 안에 안 되면 포기
sock_read=5,
)
async with session.get(url, timeout=timeout) as resp:
...
Note
timeout=None(기본값)으로 놔두면 OS 레벨 TCP timeout에 의존하게 된다. Linux 기준 약 30s인데, retry까지 붙으면 한 URL당 수십-수백 초가 소요될 수 있다. 외부 리소스를 fetch하는 코드엔 반드시 명시적 timeout을 걸자.