에러 관련 경험을 포스팅합니다.
목차
1. 이슈 및 운영 환경
이슈 내용은 외부 API 호출 시, 간헐적으로 실패하는 이슈였습니다.
서버 환경은 아래와 같았습니다.
- Springboot 2.5
- Java 11
특히, 요청량이 과도한 경우에 잦은 빈도로 에러가 발생했습니다.
에러 로그는 대략 아래와 같았습니다.
...(중략)
Caused by: java.util.concurrent.RejectedExecutionException: Thread limit exceeded replacing blocked worker
at java.util.concurrent.ForkJoinPool.tryCompensate(ForkJoinPool.java:1819) ~[?:?]
at java.util.concurrent.ForkJoinPool.compensatedBlock(ForkJoinPool.java:3448) ~[?:?]
at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3434) ~[?:?]
at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1898) ~[?:?]
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2072) ~[?:?]
at jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:554) ~[java.net.http:?]
at jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) ~[java.net.http:?]
...(중략)
2. 원인
과도한 스레드, 워커 교체, ForkJoinPool... 등의 키워드로 파악해 봤을 때
외부 요청 시, 동시성(Concurrency) 확보를 위한 비동기 호출 과정에서 문제가 생긴걸로 유추했습니다.
그리고 대상 코드에 로깅을 추가(Thread name 출력 추가)하여 재현하던 중
특이점을 발견했습니다.
Thread name: ForkJoinPool.commonPool-worker-62
Thread name: ForkJoinPool.commonPool-worker-63
Thread name: ForkJoinPool.commonPool-worker-64
Thread name: ForkJoinPool.commonPool-worker-65
Thread name: ForkJoinPool.commonPool-worker-66
Thread name: ForkJoinPool.commonPool-worker-67
Thread name: ForkJoinPool.commonPool-worker-68
Thread name: ForkJoinPool.commonPool-worker-69
...
이슈 가능성이 있는 기능 구현부에서 ForkJoinPool에서 워커 스레드가 과도하게 생성되었습니다.
스레드 개수가 약 300개를 초과하는 순간, 아래의 에러로그와 함께 기능이 중단되었습니다.
Thread limit exceeded replacing blocked worker
3. 간단한 해결
CompletableFuture를 사용하고 있었습니다.
이를 제한된 Thread Pool을 사용하도록 할 수 있습니다.
다음은 예시 코드입니다.(실제 코드아님)
Executor testExecutor = Executors.newFixedThreadPool(10);
CompletableFuture<String> name = CompletableFuture.supplyAsync(() -> "Baeldung");
CompletableFuture<Integer> nameLength = name.thenApplyAsync(value -> {
printCurrentThread(); // will print "pool-2-thread-1"
return value.length();
}, testExecutor);
참고: https://www.baeldung.com/java-completablefuture-threadpool
고정된 스레드풀로 CompletableFuture를 사용하고 난 후
아래와 같이 로그가 출력되었습니다.
Thread name: ForkJoinPool.commonPool-worker-5
Thread name: ForkJoinPool.commonPool-worker-1
Thread name: ForkJoinPool.commonPool-worker-4
Thread name: ForkJoinPool.commonPool-worker-3
Thread name: ForkJoinPool.commonPool-worker-6
Thread name: ForkJoinPool.commonPool-worker-2
...(중략)
'개발 이야기 > Springboot' 카테고리의 다른 글
JUnit 5, Mocking with ArgumentCaptor (0) | 2025.04.24 |
---|---|
JUnit 5에서 조건부 @Disabled 적용하기 (0) | 2025.04.20 |
TestContainers에서 Can't get Docker image: RemoteDockerImage에러 발생 시 (0) | 2025.02.02 |
Springboot App 시작, 종료시 로직 실행 (0) | 2024.08.31 |
Springboot 에서 Gzip으로 압축(with Kotlin) (0) | 2024.07.06 |