2025. 4. 15. 11:10ㆍIT정보/프로그래밍 가이드
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대용량성능튜닝 태그 삭제
'IT정보 > 프로그래밍 가이드' 카테고리의 다른 글
오라클 대용량 데이터 처리, 실무에서 꼭 알아야 할 팁들 (0) | 2025.04.22 |
---|---|
자바 개발자라면 꿈 같은 Stream API 10가지 패턴 (0) | 2025.03.31 |
Java Stream 장단점과 사용법 쉽게 이해하기 (0) | 2025.01.08 |
Spring 기반 MyBatis 대용량 데이터 처리 및 성능 최적화 (0) | 2025.01.07 |
HTTP 502 오류 분석 및 해결을 위한 준비 사항 (0) | 2024.12.31 |