Tech Alert: 타임아웃 기본값 30초, 그게 서비스를 죽이고 있습니다
타임아웃을 기본값인 30초로 설정했습니다.
일반적인 상황에서 외부 API는 200ms 내외로 응답하기 때문에 30초라는 임계치가 체감될 일은 거의 없습니다.
하지만 진짜 문제는 외부 API의 지연이 발생하는 순간입니다.
평소 작동하지 않던 이 30초의 타임아웃이 오히려 커넥션 풀을 점유하게 만들며 시스템 전체의 연쇄 장애를 유발하는 도화선이 됩니다.
30초의 공백
외부 API 응답이 지연됐습니다. 첫 번째 요청이 30초를 기다립니다.
그런데 그 30초가 끝나기 전에 두 번째, 세 번째… 백 번째 요청이 들어옵니다.
전부 스레드 점유. 전부 대기.
30초 동안 스레드 풀이 고갈됩니다. 새로운 요청은 대기 큐로 밀립니다. 큐도 가득 차면 요청이 거부됩니다.
외부 API 하나가 느려진 것뿐인데, 우리 서비스 전체가 멈춥니다.
왜 이런 일이 생기나요
타임아웃은 “이 시간까지 응답 없으면 포기"입니다.
문제는 그 시간이 너무 길면, 포기하기 전까지 스레드가 계속 묶인다는 겁니다.
스레드는 유한한 자원입니다. 동시에 처리할 수 있는 요청 수에 한계가 있습니다. 그 한계를 외부 서비스 하나가 전부 잡아먹으면, 나머지 요청은 처리될 수가 없습니다.
타임아웃 30초는 장애 전파 시간을 30초로 설정해 둔 것과 같습니다.
타임아웃은 하나가 아닙니다
많은 분들이 타임아웃을 하나로 생각하시는데, 실제로는 세 가지가 독립적으로 존재합니다.
Connection Timeout은 서버에 연결을 맺는 시간입니다. 3초 이내가 권장입니다. 이 시간이 넘으면 서버가 없거나 네트워크 문제입니다.
Read Timeout은 연결 후 응답을 받는 시간입니다. p99 응답시간의 1.5~2배가 적당합니다. 평소 p99가 200ms라면 타임아웃은 300~400ms입니다.
Write Timeout은 요청 데이터를 전송하는 시간입니다. 대용량 업로드가 아니라면 Read Timeout과 동일하게 설정합니다.
대부분의 프레임워크 기본값은 이 세 가지가 뭉뚱그려져 있거나 무한대입니다. 반드시 직접 명시해야 합니다.
타임아웃 이후도 설계해야 합니다
타임아웃이 반복되는 상황에서 스레드만 소진되는 게 아닙니다. DB 커넥션 풀도 함께 고갈됩니다.
타임아웃으로 요청이 실패해도, 해당 커넥션이 즉시 반환되지 않는 경우가 있습니다. 풀 안에서 조용히 누수가 쌓입니다. 겉으로는 서비스가 정상처럼 보이지만, 가용 커넥션은 계속 줄고 있습니다.
모니터링 대시보드가 초록불인데 장애가 나는 이유가 여기에 있습니다. 커넥션 풀 누수가 어떻게 감지되지 않은 채 시스템을 잠식하는지는 아래 글에서 확인하세요.
👉 Your Connection Pool Is Leaking — You Just Don’t Know It Yet
핵심 정리
타임아웃은 방어선입니다. 그 방어선이 너무 느슨하면, 외부 장애 하나가 전체 시스템을 끌고 내려갑니다.
Connection / Read / Write를 분리해서 설정하세요. 기본값은 절대 그대로 쓰지 마세요. p99 응답시간 기반으로 값을 계산하세요. 그리고 타임아웃 이후 Circuit Breaker까지 함께 설계하세요.
설정 한 줄이 시스템 전체의 생존을 결정합니다.