🔥 대용량 데이터를 Spring Boot REST API 통신할 때 ReadTimeoutException이 발생한다면?

2025. 4. 15. 11:10IT정보/프로그래밍 가이드

반응형

 

Spring Boot 기반 마이크로서비스 환경에서 REST API로 모듈 간 통신을 하다 보면 간혹 마주치는 오류가 있습니다.
바로 ReadTimeoutException. 단순히 타임아웃 시간을 늘려도 근본 해결이 안 되는 경우가 많습니다.

이 글에서는 Spring WebClient로 대용량 데이터를 처리할 때 발생하는 ReadTimeoutException의 원인과 실전 해결 전략을 공유합니다.


✅ 1. 문제 상황 요약

Spring Boot에서 A 모듈이 B 모듈의 API를 호출하는 과정에서 아래와 같은 오류 발생:

org.springframework.web.reactive.function.client.WebClientRequestException: ReadTimeoutException

주요 원인:

  • 한 번에 너무 많은 요청/응답 데이터를 처리하려는 구조
  • 상대 모듈의 처리 속도가 느려서 응답 지연
  • WebClient or RestTemplate의 readTimeout 설정보다 실제 처리 시간이 더 김

✅ 2. 해결 방법

🔹 1) 클라이언트 타임아웃 늘리기 (임시 방편)

HttpClient httpClient = HttpClient.create()
    .responseTimeout(Duration.ofSeconds(30));

WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

✅ 빠른 테스트에는 유용하지만 루트 원인 해결은 아님


🔹 2) 요청을 Chunk로 나누기 (배치 분할)

List<List<Map<String, Object>>> chunks = Lists.partition(hugeList, 100);

for (List<Map<String, Object>> chunk : chunks) {
    webClient.post()
        .uri("http://module-b/api")
        .bodyValue(chunk)
        .retrieve()
        .bodyToMono(String.class)
        .block();
}

✅ 대량 데이터를 분할 전송하면 타임아웃 발생 확률 크게 감소


🔹 3) WebClient + CompletableFuture 비동기 처리

List<CompletableFuture<ResponseEntity<String>>> futures = chunks.stream()
    .map(chunk -> CompletableFuture.supplyAsync(() ->
        webClient.post()
            .uri("http://module-b/api")
            .bodyValue(chunk)
            .retrieve()
            .toEntity(String.class)
            .block()
    , executor))
    .collect(Collectors.toList());

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

✅ 병렬 처리로 전체 처리 속도 단축


🔹 4) 수신 서버도 비동기 or 큐 처리 적용

@PostMapping("/api")
public ResponseEntity<String> receive(@RequestBody List<Item> items) {
    asyncService.process(items); // 내부적으로 @Async or MQ 처리
    return ResponseEntity.ok("요청 수신 완료");
}

수신 즉시 응답 후 비동기 처리 구조로 안정성 확보


🔹 5) 서버 및 네트워크 타임아웃 설정 확인

server:
  tomcat:
    connection-timeout: 30s

spring:
  mvc:
    async:
      request-timeout: 60s

Nginx, 로드밸런서, WAS 모두 timeout 체크 필요


🔹 6) gRPC 또는 메시지 큐 전환 고려

  • gRPC: HTTP/2 기반 고성능 스트리밍 통신 가능
  • Kafka, RabbitMQ: 대용량 데이터의 비동기 처리에 최적화

✅ REST 한계를 넘는 아키텍처 수준의 대안


✅ 실전 팁 정리

  • readTimeout은 성능 문제가 아닌 응답 대기 한계
  • 처리량이 많다면 배치 분할
  • 속도가 느리다면 비동기 처리
  • 병렬 호출 많다면 스레드풀 튜닝
  • 구조가 문제라면 메시징 or gRPC 도입 고려

💡 "서버 병목, 클라이언트 병목, 네트워크 병목" 3가지를 동시에 봐야 진짜 해결됩니다


✅ 실제 테스트 후기 (WebClient + CompletableFuture 비동기)

저는 WebClient + CompletableFuture 조합으로 외부 API를 호출하는 구조를 구성했습니다.
실제 20,000건 데이터를 테스트한 결과

  • 단일 호출: 10분 이상 소요
  • WebClient + 비동기 처리: 1분 이내로 완료!

처리 환경과 설정에 따라 다를 수 있지만,
비동기 구조의 성능 개선 효과는 상당히 큽니다.


💬 마무리

ReadTimeoutException은 단순 타임아웃 조정으로 해결되지 않는 복합적인 문제입니다.

✔ 데이터가 많다면 나눠서 보내고
✔ 속도가 느리면 비동기 처리하고
✔ 병렬이 많다면 실행 환경 튜닝하고
✔ 구조 자체가 병목이라면 메시징 전환을 고려하세요.

실행 구조부터 바꿔야 진짜 성능이 보입니다.
다음 글에서는 제가 처리한 WebClient + 비동기 처리 기반으로 글 작성해볼께요.

 

#springboot #webclient #readtimeoutexception #restapi통신 #모듈간통신 #비동기api처리 #대용량데이터분할 #completablefuture #webclient비동기 #타임아웃에러 #grpc도입 #springkafka연동 #springtimeout설정 #mq비동기처리 #spring대용량성능튜닝springboot #webclient #readtimeoutexception #restapi통신 #모듈간통신 #비동기api처리 #대용량데이터분할 #completablefuture #webclient비동기 #타임아웃에러 #grpc도입 #springkafka연동 #springtimeout설정 #mq비동기처리 #spring대용량성능튜닝 태그 삭제

반응형