백엔드 서비스를 운영하다 보면 외부 API를 호출하는 코드에서 Read timed out 오류를 만나는 경우가 있습니다. 결제 승인, 알림 발송, 인증 서버 호출, 사내 API 연동처럼 HTTP 요청을 보내는 구간에서 자주 보입니다.
이 오류는 이름만 보면 단순한 타임아웃처럼 보이지만, 실제 추적은 생각보다 섬세하게 접근해야 합니다. 연결 자체가 안 된 것인지, 연결은 됐지만 응답이 늦은 것인지, 응답 일부는 왔지만 읽는 중에 멈춘 것인지에 따라 확인해야 할 지점이 달라집니다.
HTTP Read timed out은 어떤 의미인가요?
HTTP Read timed out은 클라이언트가 서버에 요청을 보낸 뒤, 응답 데이터를 읽는 과정에서 제한 시간을 넘겼다는 의미입니다. 여기서 중요한 점은 “서버에 연결하지 못했다”가 아니라 “응답을 기다리다가 시간이 초과됐다”는 쪽에 가깝다는 것입니다.
예를 들어 Java에서 외부 API를 호출할 때 다음과 같은 오류가 발생할 수 있습니다.
java.net.SocketTimeoutException: Read timed out
이 메시지는 TCP 연결이 아예 실패했다는 뜻은 아닙니다. 요청을 보냈고, 응답을 읽어야 하는 시점에서 정해진 read timeout 안에 데이터가 도착하지 않았다고 이해하면 됩니다.
Connect timeout과 Read timeout은 다릅니다
실무에서는 이 두 가지를 함께 “타임아웃”이라고 부르기 때문에 원인 분석이 흐려지는 경우가 있습니다. 하지만 두 설정은 전혀 다른 구간을 제어합니다.
| 구분 | 의미 | 주요 의심 지점 |
|---|---|---|
| Connect timeout | 상대 서버와 연결을 맺는 시간이 초과됨 | DNS, 방화벽, 라우팅, 포트, 서버 접근성 |
| Read timeout | 연결 후 응답 데이터를 읽는 시간이 초과됨 | 상대 API 처리 지연, 응답 지연, 커넥션 풀, 프록시, 서버 내부 처리 |
Read timed out을 Connect timeout처럼 접근하면 엉뚱한 곳을 보게 됩니다. 서버 접근은 가능한데 응답이 늦는 상황이므로, 먼저 요청이 실제로 상대 서버까지 도달했는지와 상대 서버가 언제 응답을 시작했는지를 확인해야 합니다.
HTTP Read timed out 추적은 요청 흐름을 나눠서 봐야 합니다
HTTP Read timed out 문제를 추적할 때는 한 번에 원인을 단정하지 않는 편이 좋습니다. 요청 흐름을 단계별로 나누면 어느 지점에서 시간이 길어졌는지 훨씬 명확해집니다.
보통 다음 순서로 봅니다.
Client Application
- HTTP Client
- Connection Pool
- DNS / Network
- Proxy / Gateway / Load Balancer
- Target API Server
- Target Application Logic
Read timeout은 이 흐름 중 “응답을 기다리는 구간”에서 발생합니다. 그래서 클라이언트 코드의 timeout 설정만 보는 것은 부족합니다. 요청을 받은 서버가 느린지, 중간 장비가 응답을 지연시키는지, 커넥션 풀이 고갈되어 실제 호출 전에 대기한 것인지도 함께 봐야 합니다.
먼저 에러 로그에 필요한 정보가 있는지 확인합니다
가장 먼저 볼 것은 애플리케이션 로그입니다. 단순히 예외 메시지만 남아 있으면 분석이 길어집니다. 최소한 호출 대상, timeout 설정값, 요청 시작 시각, 실패 시각, 요청 ID 정도는 함께 남아 있어야 합니다.
[ExternalApiClient] request failed
url=https://api.example.com/payments/confirm
method=POST
connectTimeout=3000ms
readTimeout=5000ms
elapsed=5012ms
error=java.net.SocketTimeoutException: Read timed out
traceId=8f21a7c9
여기서 elapsed가 read timeout 설정과 거의 같다면, 실제로 응답을 기다리다가 제한 시간에 걸렸을 가능성이 큽니다. 반대로 elapsed가 훨씬 길다면 커넥션 풀 대기, 재시도, 상위 로직의 대기 시간이 섞였을 수 있습니다.
Java와 Spring Boot에서 Read timed out 설정 확인하기
HTTP Read timed out을 추적할 때는 현재 애플리케이션에서 어떤 HTTP Client를 쓰는지부터 확인해야 합니다. RestTemplate, WebClient, Apache HttpClient, OkHttp, Feign Client마다 설정 위치와 기본값이 다를 수 있습니다.
RestTemplate read timeout 예시
Spring Boot에서 RestTemplate을 사용한다면 request factory에 timeout 설정이 들어갑니다. 설정이 빠져 있거나 기본값에 의존하면 문제가 생겼을 때 동작을 예측하기 어렵습니다.
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
return new RestTemplate(factory);
}
여기서 connectTimeout은 연결을 맺는 시간이고, readTimeout은 응답을 읽기 위해 기다리는 시간입니다. Read timed out이 발생했다면 우선 read timeout 값이 실제 서비스 요구사항에 맞는지 확인해야 합니다.
WebClient read timeout 예시
WebClient를 사용한다면 Reactor Netty 설정을 확인합니다. 비동기 클라이언트라고 해서 timeout 설정을 덜 중요하게 봐도 되는 것은 아닙니다.
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.responseTimeout(Duration.ofSeconds(5));
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
이 설정은 응답을 기다리는 시간을 제한합니다. 외부 API가 정상적으로 응답하더라도 간헐적으로 5초를 넘는다면 Read timed out이 발생할 수 있습니다.
Feign Client read timeout 예시
Feign을 사용하는 프로젝트에서는 설정이 application.yml에 있는지 확인하는 경우가 많습니다. 서비스별로 timeout을 다르게 가져가는 구조라면 어느 client 설정이 적용되는지도 함께 봐야 합니다.
feign:
client:
config:
paymentClient:
connectTimeout: 3000
readTimeout: 5000
여기서 주의할 점은 timeout 값을 무작정 늘리는 방식입니다. read timeout을 크게 잡으면 에러는 줄어드는 것처럼 보일 수 있지만, 호출 스레드나 커넥션이 오래 붙잡히는 시간이 늘어납니다. 외부 API의 정상 응답 시간이 어느 정도인지 확인한 뒤 조정해야 합니다.
HTTP Read timed out 원인 추적 순서
HTTP Read timed out 원인은 한 가지로 고정되지 않습니다. 그래서 실무에서는 가능성이 높은 순서대로 지워나가는 방식이 좋습니다. 아래 순서는 제가 외부 연동 문제를 볼 때 자주 사용하는 흐름입니다.
1. 같은 요청이 재현되는지 확인합니다
먼저 애플리케이션 서버에서 같은 대상 URL로 직접 요청해 봅니다. 로컬 PC에서 되는지보다, 실제 애플리케이션이 실행되는 서버나 컨테이너 안에서 되는지가 더 중요합니다.
curl -v \
--connect-timeout 3 \
--max-time 10 \
https://api.example.com/health
--connect-timeout은 연결 제한 시간이고, --max-time은 전체 요청 제한 시간입니다. 이 요청에서도 일정 시간 뒤 응답이 끊긴다면 애플리케이션 코드 바깥의 문제일 가능성이 생깁니다.
2. 요청 대상 API의 처리 시간을 확인합니다
상대 API가 내부적으로 오래 걸리면 클라이언트 입장에서는 Read timed out으로 보입니다. 같은 회사 내부 API라면 서버 로그에서 요청 수신 시각과 응답 완료 시각을 확인해야 합니다.
client log
10:15:01.120 request start
10:15:06.132 Read timed out
server log
10:15:01.180 request received
10:15:08.450 response completed
이 경우 클라이언트의 read timeout은 5초인데, 서버 응답은 7초 이상 걸렸습니다. 클라이언트 설정만 문제가 아니라 서버 처리 시간이 timeout 기준을 넘은 것입니다.
3. 중간 프록시와 게이트웨이 timeout을 확인합니다
애플리케이션과 대상 서버 사이에 Nginx, API Gateway, Load Balancer, CloudFront 같은 계층이 있으면 각 계층의 timeout 설정도 봐야 합니다. 클라이언트 timeout은 10초인데 중간 프록시가 5초에서 연결을 끊는 구조라면 로그가 헷갈리게 남을 수 있습니다.
client read timeout: 10000ms
nginx proxy_read_timeout: 5s
target server response time: 7000ms
이런 구조에서는 대상 서버가 7초 만에 정상 응답하더라도 중간 프록시가 먼저 끊을 수 있습니다. 그래서 호출 경로에 있는 timeout 값은 한 번에 펼쳐놓고 비교하는 편이 좋습니다.
4. 커넥션 풀 대기 시간을 분리해서 봅니다
HTTP Client가 커넥션 풀을 사용한다면 실제 read timeout 이전에 커넥션을 얻기 위해 대기하는 시간이 있을 수 있습니다. 이 시간을 요청 처리 시간으로 합쳐서 보면 원인을 잘못 판단할 수 있습니다.
total elapsed: 8000ms
connection pool wait: 3000ms
actual remote call: 5000ms
result: Read timed out
겉으로는 8초짜리 외부 API 호출처럼 보이지만, 실제 원격 호출은 5초이고 앞의 3초는 커넥션 풀 대기일 수 있습니다. 이런 경우에는 read timeout만 늘리는 것이 아니라 pool size, pending acquire timeout, 호출량, 커넥션 반납 여부를 같이 확인해야 합니다.
5. 요청 바디와 응답 크기를 확인합니다
응답 데이터가 큰 API에서는 서버가 응답을 시작했더라도 전체 데이터를 읽는 데 시간이 걸릴 수 있습니다. 파일 다운로드, 대용량 JSON, 긴 목록 조회 API에서는 이 지점도 확인해야 합니다.
특히 API가 페이지네이션 없이 많은 데이터를 내려주는 구조라면 read timeout이 간헐적으로 발생할 수 있습니다. 데이터가 적을 때는 정상이고 특정 조건에서만 실패한다면 응답 크기와 쿼리 조건을 함께 봐야 합니다.
Read timed out 로그를 남길 때 포함하면 좋은 정보
HTTP Read timed out 문제는 재현이 어렵거나 간헐적으로 나타나는 경우가 많습니다. 그래서 발생 시점의 로그 품질이 중요합니다. 로그가 부족하면 같은 문제가 다시 발생해도 분석을 처음부터 반복하게 됩니다.
다음 정보는 가능하면 구조화해서 남기는 편이 좋습니다.
- 요청 URL 또는 API 이름
- HTTP method
- connect timeout, read timeout 설정값
- 요청 시작 시각과 종료 시각
- 전체 소요 시간
- 재시도 횟수
- traceId 또는 requestId
- 상대 서버 응답 코드가 있었다면 상태 코드
- 커넥션 풀 사용 여부와 pool wait 시간
log.warn("external api timeout. api={}, method={}, connectTimeout={}, readTimeout={}, elapsed={}, retry={}, traceId={}",
"payment-confirm",
"POST",
3000,
5000,
elapsedMillis,
retryCount,
traceId,
e
);
여기서 요청 바디 전체를 무조건 남기는 것은 조심해야 합니다. 개인정보, 인증 토큰, 결제 정보가 섞일 수 있기 때문입니다. 필요한 식별자만 마스킹해서 남기는 쪽이 안전합니다.
Read timed out이 발생했을 때 timeout 값을 늘리기 전에 볼 것
HTTP Read timed out이 발생하면 가장 쉬운 대응은 read timeout 값을 늘리는 것입니다. 하지만 이것은 원인 해결이 아니라 증상을 늦게 드러나게 만드는 조치가 될 수 있습니다.
상대 API의 정상 응답 시간을 먼저 확인합니다
외부 API가 평소 300ms 안에 응답하는데 특정 시점에만 5초를 넘긴다면 timeout 값을 10초로 늘리는 것보다 지연 원인을 확인하는 것이 먼저입니다. 반대로 API 특성상 원래 몇 초가 걸리는 작업이라면 현재 read timeout이 너무 짧을 수 있습니다.
기준은 호출 목적에 따라 달라집니다. 사용자 요청 흐름 안에 있는 API라면 너무 긴 timeout은 사용자 경험을 나쁘게 만들 수 있습니다. 배치나 내부 동기화 작업이라면 조금 더 긴 timeout을 허용할 수 있습니다.
재시도 정책이 중복되어 있지 않은지 확인합니다
Read timed out이 발생했을 때 재시도를 걸 수는 있습니다. 다만 HTTP Client, Feign, 서비스 코드, 메시지 큐 소비 로직에서 각각 재시도를 넣으면 실제 호출 횟수가 의도보다 많아질 수 있습니다.
service retry: 3
http client retry: 2
queue retry: 3
possible attempts = 3 * 2 * 3
겉보기에는 “3번만 재시도”하는 것 같아도 계층이 겹치면 호출 수가 늘어납니다. 특히 결제, 포인트 적립, 주문 생성처럼 부작용이 있는 API는 멱등성 키와 재시도 정책을 함께 설계해야 합니다.
timeout 값을 계층별로 정렬합니다
클라이언트, 프록시, 서버 각각의 timeout 값이 서로 맞지 않으면 어느 계층에서 끊었는지 분석하기 어려워집니다. 최소한 호출 경로에 있는 주요 timeout 값은 문서로 정리해 두는 것이 좋습니다.
| 계층 | 확인할 값 | 주의점 |
|---|---|---|
| 애플리케이션 HTTP Client | connectTimeout, readTimeout | API 성격별로 다르게 가져갈 수 있음 |
| 커넥션 풀 | max connection, acquire timeout | 원격 호출 전 대기 시간이 섞일 수 있음 |
| 프록시 또는 게이트웨이 | proxy read timeout, idle timeout | 클라이언트보다 먼저 끊을 수 있음 |
| 대상 서버 | request timeout, 처리 시간 | 내부 DB, 외부 연동 지연이 원인일 수 있음 |
Read timed out 대응 방법
HTTP Read timed out의 해결 방법은 원인에 따라 달라집니다. 하나의 정답이 있는 문제라기보다, 어떤 구간에서 시간이 초과됐는지에 따라 조치를 나눠야 합니다.
상대 API가 느린 경우
대상 서버의 처리 시간이 read timeout보다 길다면 서버 처리 시간을 줄이거나, 클라이언트 timeout을 업무 흐름에 맞게 조정해야 합니다. 단순 조회 API라면 쿼리 조건, 인덱스, 페이지네이션, 응답 필드 축소를 먼저 검토할 수 있습니다.
외부 업체 API처럼 직접 개선할 수 없는 경우에는 타임아웃 기준, 재시도 정책, 사용자 안내 방식을 정해야 합니다. 이때 중요한 것은 실패를 무한히 기다리지 않는 것입니다. 정해진 시간 안에 결과가 오지 않으면 실패로 처리하고, 필요하면 별도 상태 조회나 보상 처리 흐름을 두는 편이 낫습니다.
커넥션 풀이 부족한 경우
커넥션 풀이 부족하면 원격 서버가 느리지 않아도 요청이 밀립니다. 이 경우에는 pool size만 키우기보다 실제 동시 호출 수, 커넥션 반납 여부, keep-alive 설정, 특정 API로 호출이 몰리는지부터 확인합니다.
무작정 커넥션 수를 늘리면 대상 서버에 더 많은 부하를 줄 수 있습니다. 호출하는 쪽만 보는 것이 아니라 호출받는 쪽이 감당할 수 있는지도 함께 봐야 합니다.
중간 계층 timeout이 짧은 경우
프록시나 게이트웨이 timeout이 클라이언트보다 짧으면 중간에서 연결이 먼저 끊길 수 있습니다. 이 경우에는 애플리케이션 timeout과 중간 계층 timeout을 함께 조정해야 합니다.
client read timeout: 5000ms
gateway timeout: 6000ms
server expected max processing time: 4000ms
이런 식으로 계층별 제한 시간이 의도에 맞게 정렬되어 있으면 분석이 쉬워집니다. 반대로 각 계층의 timeout이 제각각이면 어떤 지점에서 끊긴 것인지 로그만 보고 판단하기 어렵습니다.
Read timed out을 줄이기 위한 코드 작성 기준
HTTP Read timed out을 완전히 없앨 수는 없습니다. 네트워크와 외부 시스템을 사용하는 이상 지연은 언제든 발생할 수 있습니다. 대신 실패를 빨리 감지하고, 원인을 추적할 수 있게 만들고, 서비스 흐름이 과하게 흔들리지 않게 설계하는 것이 중요합니다.
API 성격별로 timeout을 분리합니다
모든 외부 API에 같은 timeout을 적용하면 관리가 단순해 보이지만, 실제 서비스에서는 맞지 않는 경우가 많습니다. 빠르게 실패해야 하는 사용자 요청 API와 다소 오래 걸려도 되는 배치성 API는 기준이 다릅니다.
external-api:
payment:
connect-timeout: 3000
read-timeout: 5000
report:
connect-timeout: 3000
read-timeout: 30000
결제 승인처럼 사용자가 기다리는 요청은 너무 길게 잡지 않는 것이 좋습니다. 반면 리포트 생성이나 정산 데이터 조회처럼 내부 작업에 가까운 API는 더 긴 시간을 허용할 수 있습니다.
멱등성이 없는 요청은 재시도에 주의합니다
Read timed out은 클라이언트가 응답을 못 읽었다는 뜻이지, 서버가 요청을 처리하지 않았다는 뜻은 아닙니다. 이 부분은 자주 오해합니다.
예를 들어 결제 승인 요청에서 Read timed out이 발생했더라도, 상대 서버에서는 이미 결제가 완료됐을 수 있습니다. 이 상태에서 같은 요청을 그대로 재시도하면 중복 처리 위험이 생깁니다.
POST /payments/confirm
Idempotency-Key: order-20260530-0001
부작용이 있는 요청은 멱등성 키를 사용하거나, timeout 이후 상태 조회 API로 최종 결과를 확인하는 구조가 필요합니다. 단순히 “실패했으니 다시 호출”하는 방식은 위험합니다.
사용자 요청 흐름에서는 fallback을 고려합니다
외부 API가 늦어질 수 있는 구조라면 사용자 요청 전체가 끝까지 붙잡히지 않도록 설계해야 합니다. 즉시 결과가 꼭 필요한 요청인지, 접수 후 비동기로 처리해도 되는 요청인지 나눠볼 수 있습니다.
예를 들어 알림 발송, 로그 전송, 부가 통계 저장처럼 사용자 응답에 필수적이지 않은 작업은 동기 HTTP 호출보다 큐 기반 비동기 처리로 분리하는 편이 더 안전할 수 있습니다. 반대로 결제 승인처럼 즉시 결과가 필요한 작업은 timeout, 상태 조회, 보상 처리까지 함께 설계해야 합니다.
Read timed out 체크리스트
HTTP Read timed out이 발생했을 때 아래 순서로 확인하면 원인을 좁히기 쉽습니다. 중요한 것은 timeout 값을 먼저 늘리는 것이 아니라, 실제로 어느 구간에서 시간이 지났는지 확인하는 것입니다.
- Connect timeout인지 Read timeout인지 구분했는가?
- 에러 발생 시 전체 elapsed time을 확인했는가?
- HTTP Client의 read timeout 설정값을 확인했는가?
- 같은 요청을 서버 또는 컨테이너 내부에서 curl로 재현해 봤는가?
- 대상 API 서버 로그에서 요청 수신 시각과 응답 완료 시각을 확인했는가?
- 중간 프록시, 게이트웨이, 로드밸런서 timeout 값을 확인했는가?
- 커넥션 풀 대기 시간이 포함되어 있지 않은가?
- 응답 크기나 특정 요청 조건에서만 느려지는지 확인했는가?
- 재시도 정책이 여러 계층에서 중복 적용되고 있지 않은가?
- 부작용이 있는 요청에 멱등성 처리가 되어 있는가?
마무리: Read timed out은 응답 대기 구간을 추적하는 문제입니다
HTTP Read timed out은 단순히 timeout 값을 늘려서 끝낼 문제가 아닙니다. 연결은 되었지만 응답을 읽는 과정에서 제한 시간을 넘긴 것이므로, 클라이언트 설정, 상대 API 처리 시간, 중간 계층 timeout, 커넥션 풀 상태를 함께 확인해야 합니다.
실무에서는 이 문제를 “어느 서버가 느렸는가”보다 “요청 흐름 중 어느 구간에서 시간이 소비됐는가”로 보는 편이 더 정확합니다. 로그에 요청 시작 시각, 종료 시각, timeout 설정값, traceId, 대상 API 이름이 남아 있으면 분석 속도가 크게 달라집니다.
정리하면 Read timed out을 추적할 때는 다음 기준이 중요합니다. Connect timeout과 구분하고, read timeout 값을 확인하고, 서버 응답 시간을 대조하고, 중간 계층 timeout을 맞춰보고, 재시도와 멱등성까지 함께 검토해야 합니다. 그래야 단순한 설정 변경이 아니라 서비스 흐름에 맞는 안정적인 대응이 가능합니다.
'개발 > 기타' 카테고리의 다른 글
| HTTP 429 Too Many Requests 대응 전략: 백엔드에서 Rate Limit을 다루는 실무 기준 (0) | 2026.06.01 |
|---|---|
| 배치 프로세싱(Batch API)을 활용한 비실시간 작업 비용 최적화 (0) | 2026.03.24 |
| Git과 GitHub 차이, 왜 필요한가? (0) | 2026.02.10 |
| 개발자에게 추천하는 AI 도구, 생산성을 높이는 선택 기준 (0) | 2026.01.20 |
| AWS 비용 폭탄 막는 방법, 초보자가 반드시 알아야 할 관리 전략 (0) | 2026.01.19 |
