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.ClientTimeouttotal 하나만 있는 게 아니라 연결 단계별로 따로 걸 수 있다.

필드적용 범위
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을 걸자.

← All TIL