Contents
see List작성일 2026. 07. 12.
Java API 동시 호출이 폭주할 때 생기는 실제 장애
외부 API를 여러 개 호출하는 서비스는 순간 요청 수가 커지면 곧바로 병목이 드러납니다. 가장 흔한 증상은 아래 순서입니다.
- 연결 타임아웃이 쌓이며 95퍼센타일 응답시간이 급등
- 쓰레드 풀(ExecutorService)에서 큐가 꽉 차고 신규 요청이 거부
- 한 개 실패가 여러 하위 호출을 연쇄 실패시키며 전체 요청이 실패
- 장애가 있어도 운영자가 사후에야 원인을 찾음
이 문서는 "Java에서 동시 호출 경로를 제어하고, 실패를 격리하며, 복구 가능하게 만드는" 기준을 바로 실무에 적용할 수 있도록 정리했습니다.
기본 전략: 동시성은 반드시 제한하고, timeout은 항상 분리한다
중요한 원칙은 세 가지입니다.
- 무한 병렬화 금지: 전체 요청에서 하위 API 동시 호출 개수에 상한을 둡니다.
- 동일 경로 timeout 통일: 연결/쓰기/읽기 시간 제한을 각각 분리합니다.
- 부분 실패 허용: 전체를 바로 실패 처리하지 않고, 실패 경로만 빠르게 완화해 실패 범위를 축소합니다.
가장 많이 쓰는 조합은 ExecutorService + Semaphore + HttpClient timeout + CompletableFuture 집계입니다.
실전 코드 예시
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;
public class DownstreamBatch {
private static final ExecutorService apiPool =
Executors.newFixedThreadPool(32, r -> new Thread(r, "api-worker-" + System.nanoTime()));
private static final Semaphore inFlight = new Semaphore(24);
private static final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(1))
.build();
public static Map callAll(List endpoints) {
List> tasks = endpoints.stream().map(url ->
CompletableFuture.supplyAsync(() -> {
boolean acquired = false;
try {
acquired = inFlight.tryAcquire(1, Duration.ofMillis(200));
if (!acquired) return "SKIP_IN_FLIGHT_FULL";
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
.timeout(Duration.ofSeconds(2))
.GET()
.build();
return http.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(r -> r.body())
.get(2, TimeUnit.SECONDS);
} catch (TimeoutException e) {
return "TIMEOUT:" + url;
} catch (Exception e) {
return "ERROR:" + e.getClass().getSimpleName();
} finally {
if (acquired) inFlight.release();
}
}, apiPool)
).collect(Collectors.toList());
Map results = new java.util.LinkedHashMap<>();
for (CompletableFuture f : tasks) {
try { results.put("ok", f.get(3, TimeUnit.SECONDS)); }
catch (Exception e) { results.put("ok", "FALLBACK"); }
}
return results;
}
}
운영 체크포인트
위 코드를 그대로 붙여 쓰는 것보다 운영 정책을 함께 맞춰야 효과가 납니다.
- 동일 API 그룹별로 최대 동시 요청 수를 분리합니다.
- 각 그룹 timeout을 SLO 기준으로 정합니다. 1초 타임아웃만 쓰지 말고
connect/read/write를 분리하세요. - 예외를 숨기지 말고 메트릭으로 분류합니다.
TIMEOUT,ERROR,fallback를 따로 집계해야 원인 분리가 쉽습니다. - 재시도는 무조건 지수 백오프를 사용하고 동일 시간대에 동시에 재시도하지 않도록 jitter를 넣습니다.
기술 요약 체크리스트
- 요청마다 최소 1개 이상의 동시성 상한이 있는가?
- 전체 실패 시나리오를 부분 실패로 축소할 fallback이 있는가?
- timeout이 연결/요청/응답 단계로 분리돼 있는가?
- in-flight, queue length, timeout 비율, 실패 유형별 집계가 모니터링되는가?
- 장애 재현을 위해 부하 테스트에서 동일 패턴을 자동 검증하고 있는가?
java
| No | 작성일 | Title |
|---|---|---|
| 313 | 2015. 07. 14. | [ 정규식 ] xxxx-xx-xx 정규표현식 |
| 87 | 2014. 10. 29. | [ xml ] jats 1.0 dtd |
| 83 | 2014. 10. 28. | [ solr ] solr 검색엔진 설치방법 (db import) |
| 72 | 2014. 10. 21. | [ secure-coding for java ] Math.random() 사용하지 말것 |
| 48 | 2014. 09. 30. | [ java ] 자바 가비지컬렉터와 메모리관리 |
| 47 | 2014. 09. 30. | [ java ] 자바 String과 StringBuffer 의 오해 |
| 30 | 2014. 09. 25. | [ java ] 자바에서 날짜,시간 구하기 |