본문 바로가기
IT

커넥션 타임아웃과 리드 타임아웃

by urosie 2026. 1. 13.
반응형

HTTP 통신을 하다 보면 Connection Timeout과 Read Timeout이라는 용어를 자주 접하게 됩니다. 두 타임아웃 모두 네트워크 통신에서 발생하지만, 각각 다른 단계에서 적용됩니다. 이 글에서는 두 타임아웃의 차이점과 실무에서 알아야 할 내용들을 정리했습니다.

커넥션 타임아웃 vs 리드 타임아웃

커넥션 타임아웃 (Connection Timeout)

TCP 연결을 맺는 과정에서의 제한 시간입니다.

  • 클라이언트가 서버에 연결 요청 시작
  • TCP 3-way handshake 완료까지 대기하는 시간
  • 서버가 응답하지 않거나 네트워크가 불안정할 때 발생
  • 일반적으로 짧게 설정 (3~5초)
// RestTemplate 예시
HttpComponentsClientHttpRequestFactory factory = 
    new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);  // 5초

리드 타임아웃 (Read Timeout)

이미 연결이 맺어진 상태에서 데이터를 읽을 때의 제한 시간입니다.

  • 연결 성공 후, 서버로부터 응답 데이터를 받을 때까지 대기하는 시간
  • 서버가 요청 처리에 오래 걸리거나 응답을 보내지 않을 때 발생
  • API 특성에 맞게 설정 (10~60초)
factory.setReadTimeout(30000);  // 30초

타임라인으로 이해하기

[클라이언트]                                    [서버]

연결 시도 ─────────────────────────────────────→
         ←─── Connection Timeout 적용 ────→
         ←───────────────────────────────────── 연결 수락
                                                
요청 전송 ─────────────────────────────────────→
                                                요청 처리 중...
         ←──── Read Timeout 적용 ────→         (30초 소요)
         (응답 대기 중...)
                                                
타임아웃! (30초 경과)
연결 종료                                       여전히 처리 중...
                                                처리 완료! (하지만 클라이언트는 이미 떠남)

Spring에서의 타임아웃 설정

RestTemplate

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        HttpComponentsClientHttpRequestFactory factory = 
            new HttpComponentsClientHttpRequestFactory();
        
        factory.setConnectTimeout(5000);   // 연결 타임아웃: 5초
        factory.setReadTimeout(30000);     // 읽기 타임아웃: 30초
        
        return new RestTemplate(factory);
    }
}

WebClient (Spring WebFlux)

@Bean
public WebClient webClient() {
    return WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(
            HttpClient.create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .responseTimeout(Duration.ofSeconds(30))
        ))
        .build();
}

리드 타임아웃 발생 시 동작

리드 타임아웃이 발생하면 클라이언트와 서버가 각각 다르게 동작합니다.

클라이언트 측

  • SocketTimeoutException 또는 ReadTimeoutException 발생
  • TCP 연결을 강제로 종료
  • 더 이상 서버의 응답을 기다리지 않음
try {
    String response = restTemplate.getForObject(url, String.class);
} catch (ResourceAccessException e) {
    log.error("Read timeout 발생", e);
    // 클라이언트는 여기서 종료
}

서버 측

중요: 서버는 타임아웃을 인지하지 못하고 계속 작업을 수행합니다!

@GetMapping("/slow-api")
public String slowApi() {
    log.info("요청 받음");
    Thread.sleep(30000);  // 클라이언트는 20초에 타임아웃
    log.info("처리 완료, 응답 반환");  // 이 로그는 찍힘!
    return "result";  // 하지만 클라이언트는 이미 연결을 끊음
}

왜 서버는 연결이 끊긴 걸 모를까?

HTTP는 기본적으로 단방향 통신이기 때문입니다:

  1. 처리 중에는 확인 안 함: 서버는 요청을 받고 처리하는 동안 클라이언트 상태를 확인하지 않습니다
  2. 응답 시도할 때 알게 됨: 응답을 보내려고 할 때 비로소 연결 상태를 확인합니다
  3. 로그에는 정상으로 보임: 컨트롤러 레벨에서는 정상 종료로 보이는 경우가 많습니다

서버가 연결 끊김을 감지할 수 있는 경우

1. 응답 데이터가 큰 경우

@GetMapping("/large-response")
public void largeResponse(HttpServletResponse response) throws IOException {
    try (PrintWriter writer = response.getWriter()) {
        for (int i = 0; i < 1000000; i++) {
            writer.write("large data...");  // IOException 발생 가능
        }
    } catch (IOException e) {
        log.error("클라이언트 연결 끊김 감지", e);
    }
}

2. WAS 레벨 로그

Tomcat 같은 WAS에서는 감지할 수 있습니다:

java.io.IOException: Broken pipe

실무에서 발생하는 문제점

1. 리소스 낭비

서버는 이미 끊긴 연결을 위해 계속 작업하므로 CPU, 메모리, DB 커넥션 등을 낭비합니다.

2. 데이터 정합성 문제

상황: 결제 API 호출
- 클라이언트: 20초 Read Timeout 설정
- 서버: 결제 처리에 30초 소요

결과:
- 서버: DB에 결제 완료 저장 ✅
- 클라이언트: 타임아웃으로 실패 처리 ❌
→ 데이터 불일치 발생!

서버 측 타임아웃 설정

클라이언트만이 아니라 서버 측에서도 타임아웃을 설정할 수 있습니다.

1. WAS(Tomcat) 레벨

# application.yml
server:
  tomcat:
    connection-timeout: 20s
    keep-alive-timeout: 60s
    threads:
      max: 200
      min-spare: 10
@Bean
public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
        @Override
        protected void customizeConnector(Connector connector) {
            connector.setProperty("connectionTimeout", "20000");
        }
    };
}

2. Spring MVC 비동기 요청

spring:
  mvc:
    async:
      request-timeout: 30000  # 30초
@GetMapping("/async-api")
public DeferredResult<String> asyncApi() {
    DeferredResult<String> result = new DeferredResult<>(30000L);
    
    CompletableFuture.runAsync(() -> {
        String data = heavyProcess();
        result.setResult(data);
    });
    
    result.onTimeout(() -> {
        log.warn("서버 측 타임아웃 발생");
        result.setErrorResult("타임아웃");
    });
    
    return result;
}

3. 트랜잭션 타임아웃

@Transactional(timeout = 30)  // 30초
public void longTransaction() {
    // DB 작업이 30초 넘으면 롤백
}

4. 개별 작업 타임아웃 (권장)

@Service
public class MyService {
    
    private final ExecutorService executor = Executors.newCachedThreadPool();
    
    public String processWithTimeout() throws Exception {
        Future<String> future = executor.submit(() -> {
            return heavyProcess();
        });
        
        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            future.cancel(true);
            throw new RuntimeException("서버 처리 시간 초과");
        }
    }
}

장시간 작업 처리 패턴

FTP 파일 다운로드처럼 오래 걸리는 작업은 어떻게 처리할까요?

비동기 패턴 (@Async 활용)

@RestController
public class ProcessController {
    
    @Autowired
    private FtpDownloadService ftpService;
    
    @PostMapping("/api/process")
    public ResponseEntity<String> process(@RequestBody Request request) {
        
        // 1. 메인 비즈니스 로직 처리 (동기)
        String result = processBusinessLogic(request);
        
        // 2. DB 저장 (동기)
        saveToDatabase(result);
        
        // 3. FTP 다운로드만 백그라운드로 실행 (비동기)
        ftpService.downloadAsync(request.getFtpUrl());
        
        // 4. 즉시 응답 (FTP 완료 안 기다림)
        return ResponseEntity.ok("처리 완료");
    }
}

@Service
public class FtpDownloadService {
    
    @Async("ftpExecutor")  // 별도 스레드에서 실행
    public void downloadAsync(String ftpUrl) {
        log.info("백그라운드 FTP 다운로드 시작");
        
        try {
            File file = downloadFromFtp(ftpUrl);  // 30분 소요
            log.info("FTP 다운로드 완료: {}", file.getName());
            
        } catch (Exception e) {
            log.error("FTP 다운로드 실패", e);
        }
    }
}

@Async 설정

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "ftpExecutor")
    public Executor ftpExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("ftp-bg-");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
}

@Async 동작 원리

// @Async 없이
public void download() {
    downloadFromFtp();  // 30분 대기
    return;  // 30분 후 리턴
}

// @Async 적용
@Async
public void download() {
    downloadFromFtp();  // 별도 스레드에서 실행
    // 호출한 곳은 즉시 다음 코드로 진행
}

실행 흐름:

클라이언트 요청
    ↓
메인 로직 처리 (2초)
    ↓
DB 저장 (1초)
    ↓
FTP 다운로드 시작 (비동기, 즉시 리턴)
    ↓
클라이언트에게 응답 (총 3초) ✅
    ↓
연결 종료
    ↓
[백그라운드에서 FTP 다운로드 계속 진행... 30분]

실무 권장 설정

# application.yml
server:
  tomcat:
    connection-timeout: 20s
    threads:
      max: 200

spring:
  mvc:
    async:
      request-timeout: 60000

# RestTemplate 설정
rest:
  client:
    connect-timeout: 5000    # 연결: 짧게
    read-timeout: 30000      # 읽기: API 특성에 맞게

타임아웃 설정 가이드

타임아웃 종류 권장 시간 이유

Connection Timeout 3~5초 연결이 빠르게 실패해야 재시도 가능
Read Timeout 10~60초 API 응답 시간에 따라 조정
Transaction Timeout 30초 DB 락 방지
Async Request 60초 비동기 작업 여유 시간

해결 방법 정리

1. 비동기 처리 + @Async

  • 장시간 작업은 백그라운드로 처리
  • 즉시 응답 반환으로 타임아웃 회피

2. 폴링/웹훅 패턴

// 즉시 작업 ID 반환
@PostMapping("/tasks")
public String createTask() {
    String taskId = UUID.randomUUID().toString();
    asyncService.processTask(taskId);
    return taskId;
}

// 상태 조회
@GetMapping("/tasks/{taskId}")
public TaskStatus getStatus(@PathVariable String taskId) {
    return taskRepository.findById(taskId);
}

3. 서버 측 타임아웃 설정

  • 무한 실행 방지
  • 리소스 낭비 감소

4. 멱등성 보장

  • 재시도 가능하도록 설계
  • 중복 실행 방지 로직 추가

마치며

커넥션 타임아웃과 리드 타임아웃은 네트워크 통신의 서로 다른 단계에서 적용되는 중요한 개념입니다. 특히 리드 타임아웃 발생 시 클라이언트와 서버가 각각 다르게 동작한다는 점을 이해하고, 적절한 비동기 처리 패턴을 적용하면 안정적인 시스템을 구축할 수 있습니다.

실무에서는:

  • 클라이언트와 서버 양측에 타임아웃 설정
  • 장시간 작업은 @Async로 백그라운드 처리
  • 작업 상태를 추적할 수 있는 구조 설계
  • 적절한 에러 핸들링과 로깅

이러한 원칙들을 지키면 타임아웃으로 인한 문제를 최소화할 수 있습니다.

반응형

댓글