타임아웃 설계와 실무 기준

한 줄 정의

Timeout은 네트워크 요청의 각 단계에서 응답을 기다리는 최대 시간으로, 설정하지 않으면 무한 대기로 장애가 전파된다.

실무에서 왜 중요한가

Timeout을 제대로 설정하지 않으면 다음 문제가 생긴다.

  • 외부 API가 느려질 때 우리 서버의 스레드가 전부 대기 상태에 빠진다.
  • 타임아웃 없이 무한 대기하다가 커넥션 풀이 고갈된다.
  • Connection timeout과 Read timeout을 구분하지 못해서 적절한 값을 설정하지 못한다.
  • 하나의 느린 외부 서비스가 전체 시스템을 마비시킨다 (장애 전파).

Timeout 종류

Client                              Server
  │                                    │
  │── TCP SYN ──────────────────────▶  │
  │        ↕ Connection Timeout        │
  │◀── TCP SYN+ACK ────────────────   │
  │                                    │
  │── HTTP Request ─────────────────▶  │
  │        ↕ Read (Response) Timeout   │  ← 서버 처리 시간
  │◀── HTTP Response ───────────────   │
  │                                    │
Timeout구간기본 권장값
Connection TimeoutTCP 연결 수립까지1~3초
Read Timeout요청 전송 후 응답 수신까지3~10초 (API에 따라 다름)
Write Timeout요청 데이터 전송까지5~10초

Connection Timeout

TCP 3-way handshake가 완료되기까지의 시간이다. 서버가 다운되었거나 방화벽에 의해 차단된 경우 이 timeout에 걸린다.

// RestTemplate
factory.setConnectTimeout(3000);  // 3초
 
// WebClient
HttpClient httpClient = HttpClient.create()
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);

서버가 응답하지 않는 상황에서 OS 기본 TCP timeout(보통 75초~2분)까지 대기하게 되므로, 반드시 짧게 설정해야 한다.

Read Timeout (Response Timeout)

TCP 연결이 수립된 후, 서버의 응답을 기다리는 시간이다. 서버의 처리 시간이 긴 경우 이 timeout에 걸린다.

// RestTemplate
factory.setReadTimeout(5000);  // 5초
 
// WebClient
WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(
        HttpClient.create()
            .responseTimeout(Duration.ofSeconds(5))
    ))
    .build();

Read timeout은 API의 예상 응답 시간에 맞게 설정한다. 결제 API처럼 처리 시간이 긴 경우 넉넉하게, 단순 조회 API는 짧게 설정한다.

실무 설정 예시

# application.yml - 외부 API별 timeout 설정
external-api:
  payment:
    connect-timeout: 3000
    read-timeout: 30000    # 결제는 처리 시간이 길 수 있음
  notification:
    connect-timeout: 2000
    read-timeout: 5000     # 알림은 빠르게 응답해야 함
  search:
    connect-timeout: 1000
    read-timeout: 3000     # 검색은 빠른 응답 기대
@Configuration
public class ExternalApiConfig {
 
    @Bean
    public RestTemplate paymentRestTemplate(
            @Value("${external-api.payment.connect-timeout}") int connectTimeout,
            @Value("${external-api.payment.read-timeout}") int readTimeout) {
 
        HttpComponentsClientHttpRequestFactory factory =
            new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(connectTimeout);
        factory.setReadTimeout(readTimeout);
        return new RestTemplate(factory);
    }
}

Timeout과 장애 전파

사용자 → 우리 서버 (스레드 200개) → 외부 결제 API (응답 지연)

1. 결제 API가 느려짐 (평소 1초 → 30초)
2. 우리 서버의 스레드가 30초씩 대기
3. 200개 스레드가 모두 결제 API 대기 상태
4. 다른 요청 (조회, 목록 등)도 처리 불가
5. → 전체 서비스 장애

대응 방법:

  1. 적절한 Read Timeout: 예상 응답 시간의 2~3배 이내
  2. Circuit Breaker: 연속 실패 시 빠르게 실패 처리 (Resilience4j 등)
  3. Bulkhead: 외부 API별 스레드 풀을 분리해서 한 API의 장애가 다른 기능에 영향을 주지 않게 함
// Resilience4j Circuit Breaker 예시
@CircuitBreaker(name = "payment", fallbackMethod = "paymentFallback")
public PaymentResult processPayment(PaymentRequest request) {
    return paymentClient.pay(request);
}
 
private PaymentResult paymentFallback(PaymentRequest request, Exception e) {
    // 대체 처리: 결제 큐에 넣고 나중에 처리
    return PaymentResult.pending();
}

서버 측 Timeout

클라이언트 timeout만 설정하면 안 되고, 서버 측 timeout도 함께 고려해야 한다.

설정의미기본값 (Tomcat)
server.tomcat.connection-timeout요청 헤더 수신 대기 시간20초
spring.mvc.async.request-timeout비동기 요청 처리 시간30초
HikariCP connectionTimeoutDB 커넥션 획득 대기 시간30초

서버의 요청 처리 시간이 클라이언트의 Read timeout보다 길면, 클라이언트는 timeout으로 끊었지만 서버는 계속 처리하는 상황이 발생한다.

자주 나는 실수

  • timeout을 설정하지 않아서 외부 API 장애가 전체 서비스로 전파된다.
  • Connection timeout과 Read timeout을 구분하지 않고 같은 값을 설정한다.
  • Read timeout을 너무 짧게 설정해서 정상 요청도 실패한다.
  • 서버 측 timeout을 고려하지 않아서 클라이언트는 끊었는데 서버는 계속 처리한다.
  • 모든 외부 API에 같은 timeout을 설정해서 특성에 맞지 않는다.

핵심 요약

Timeout은 Connection(연결), Read(응답 대기), Write(전송)로 구분됩니다. 설정하지 않으면 외부 API 장애가 전체 서비스로 전파될 수 있습니다.

Connection timeout은 1~3초, Read timeout은 API 특성에 맞게 설정합니다. Circuit Breaker와 Bulkhead를 함께 사용하면 장애 전파를 더 효과적으로 차단할 수 있습니다.

꼬리 질문

관련 문서