API Latency 증가는 먼저 구간을 나누어 봐야 합니다
API latency가 증가했다는 말은 단순히 응답 시간이 길어졌다는 뜻입니다. 하지만 이 말만으로는 원인을 알 수 없습니다. 요청을 보내는 우리 서버가 느린 것인지, 외부 API 서버가 느린 것인지, 중간 네트워크 구간에서 지연이 생긴 것인지, 아니면 HTTP 커넥션을 재사용하지 못해 매번 연결 비용을 치르고 있는지 구분해야 합니다.
실무에서는 평균 응답 시간만 보고 판단하면 자주 빗나갑니다. 평균 latency는 정상처럼 보이는데 p95, p99 latency만 급격히 올라가는 경우도 많습니다. 이런 경우 전체 요청이 느려진 것이 아니라 일부 요청이 특정 구간에서 오래 대기하고 있을 가능성이 큽니다.
평균 latency: 180ms
p95 latency: 2.8s
p99 latency: 7.5s
요청 성공률: 99.4%
에러율: 큰 변화 없음
이런 패턴이라면 애플리케이션이 완전히 장애 상태라기보다는, 일부 요청이 대기열에 쌓이거나 특정 네트워크 구간에서 지연되고 있다고 보는 편이 더 정확합니다. 특히 외부 API 호출이 포함된 서비스라면 내부 처리 시간과 외부 호출 시간을 반드시 분리해서 봐야 합니다.
HTTP API 호출 latency가 갑자기 증가했을 때 보이는 증상
HTTP API latency 문제는 대체로 몇 가지 증상으로 나타납니다. 사용자는 화면이 늦게 뜬다고 말하고, 서버 로그에는 timeout이 조금씩 늘어나며, 모니터링에서는 특정 외부 API 구간의 응답 시간이 튀는 형태로 보입니다.
문제는 이 증상들이 서로 비슷해 보인다는 점입니다. 외부 API 서버가 느려도 우리 서버의 요청 시간이 증가하고, 네트워크 지연이 있어도 우리 서버의 latency가 증가합니다. 커넥션 풀이 부족해도 결과적으로는 HTTP 호출이 늦어진 것처럼 보입니다.
대표적인 로그 패턴
2026-05-30 10:12:44.218 INFO external-api elapsed=184ms status=200
2026-05-30 10:13:02.911 INFO external-api elapsed=231ms status=200
2026-05-30 10:14:18.503 WARN external-api elapsed=4821ms status=200
2026-05-30 10:14:21.726 WARN external-api elapsed=7312ms status=200
2026-05-30 10:14:25.104 ERROR external-api read timed out
여기서 중요한 부분은 status=200 요청도 느려졌다는 점입니다. HTTP 500이나 503이 늘어난 상황과 다르게, 성공 응답이 늦게 돌아오는 문제는 원인 추적이 더 까다롭습니다. 서버는 성공했다고 말하지만 사용자는 이미 느리다고 느끼기 때문입니다.
API latency 원인 추적은 DNS, Connect, TLS, TTFB로 쪼개야 합니다
API latency를 제대로 분석하려면 전체 시간을 하나로 보지 말고 단계별로 나누어야 합니다. HTTP 요청 하나는 대략 DNS 조회, TCP 연결, TLS 핸드셰이크, 요청 전송, 서버 처리 대기, 응답 수신 과정을 거칩니다.
이 구간을 나누지 않으면 “외부 API가 느립니다”라는 결론에서 멈추게 됩니다. 하지만 DNS가 느린 것과 TLS 핸드셰이크가 느린 것, 외부 서버의 첫 바이트 응답이 늦은 것은 대응 방법이 다릅니다.
curl -w "
dns_lookup: %{time_namelookup}s
tcp_connect: %{time_connect}s
tls_handshake: %{time_appconnect}s
ttfb: %{time_starttransfer}s
total: %{time_total}s
" -o /dev/null -s https://api.example.com/orders
이 명령은 단순하지만 원인 분리에 도움이 됩니다. 예를 들어 total이 5초인데 time_starttransfer도 5초에 가깝다면 외부 서버가 응답을 시작하기까지 오래 걸린 것입니다. 반대로 time_connect나 time_appconnect가 튄다면 네트워크 연결이나 TLS 구간을 의심해야 합니다.
구간별로 의심할 수 있는 원인
| 느린 구간 | 의심 원인 | 확인 방법 |
|---|---|---|
| DNS lookup | DNS 서버 지연, 캐시 미사용, 도메인 변경 | dig, nslookup, JVM DNS cache 설정 확인 |
| TCP connect | 네트워크 지연, 방화벽, 라우팅 문제, 포트 고갈 | connect timeout 로그, netstat, VPC Flow Logs 확인 |
| TLS handshake | 인증서 검증 지연, TLS 재협상, 커넥션 재사용 실패 | openssl, 클라이언트 connection reuse 여부 확인 |
| TTFB | 외부 API 서버 처리 지연, rate limit, 내부 큐 대기 | 외부 API 응답 시간, 상태 코드, vendor status 확인 |
| Response download | 응답 크기 증가, 압축 미적용, 네트워크 대역폭 문제 | Content-Length, Content-Encoding, payload 크기 확인 |
네트워크 문제가 아니라 HTTP 커넥션 풀 문제일 수도 있습니다
API 호출 latency가 갑자기 증가했을 때 의외로 많이 놓치는 부분이 HTTP connection pool입니다. 외부 API 호출이 많아졌는데 커넥션 풀 크기가 작거나, 응답을 제대로 닫지 않아 커넥션이 반환되지 않으면 요청은 네트워크로 나가기 전부터 대기하게 됩니다.
이 경우 외부 API 서버는 정상인데 우리 서버에서만 latency가 증가합니다. 로그상으로는 외부 API 호출 시간이 길어진 것처럼 보이지만, 실제로는 커넥션을 얻기 위해 기다린 시간이 포함되어 있을 수 있습니다.
커넥션 풀 부족 시 나타나는 패턴
max connections per route: 20
external API concurrent calls: 80
connection request timeout: not configured
결과:
- 요청 일부가 커넥션 대기
- p95, p99 latency 증가
- 외부 API 서버 로그에는 지연 흔적이 적음
여기서 connection timeout과 read timeout만 설정되어 있고 connection request timeout이 빠져 있으면 문제가 길어질 수 있습니다. TCP 연결을 맺는 시간이 아니라, 풀에서 커넥션을 빌리는 대기 시간이 제한 없이 늘어날 수 있기 때문입니다.
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(1000)
.setConnectionRequestTimeout(500)
.setResponseTimeout(3000)
.build();
실무에서는 timeout을 하나만 설정하면 충분하다고 생각하기 쉽습니다. 하지만 HTTP 클라이언트에서는 연결을 맺는 시간, 커넥션 풀에서 대기하는 시간, 응답을 기다리는 시간이 서로 다릅니다. 이 차이를 모르면 timeout을 설정했는데도 요청이 오래 붙잡히는 상황이 생깁니다.
외부 API latency 증가와 내부 서버 문제를 구분하는 기준
API latency가 증가하면 내부 서버부터 의심하는 경우가 많습니다. 물론 내부 DB, Redis, 애플리케이션 스레드 풀, GC 같은 요소도 확인해야 합니다. 다만 외부 API 호출이 포함된 요청이라면 내부 처리 시간과 외부 호출 시간을 분리해서 로그로 남겨야 합니다.
request_total_elapsed=5280ms
db_elapsed=32ms
business_logic_elapsed=18ms
external_api_elapsed=5167ms
response_build_elapsed=4ms
이런 형태로 로그가 남아 있으면 판단이 훨씬 쉬워집니다. 전체 요청은 5초가 넘었지만 내부 DB와 비즈니스 로직은 빠르게 끝났고, 대부분의 시간이 외부 API 호출에서 사용되었습니다. 이 경우 내부 로직 최적화보다 외부 호출 안정화가 먼저입니다.
내부 서버 문제에 가까운 신호
내부 서버 문제라면 외부 API를 호출하지 않는 엔드포인트에서도 latency가 함께 증가하는 경우가 많습니다. CPU 사용률, 스레드 풀 대기, DB connection pool 대기, GC pause, slow query 등이 동시에 관측될 수 있습니다.
반대로 특정 외부 API를 사용하는 요청에서만 지연이 커지고, 같은 서버의 다른 API는 정상이라면 외부 연동 구간을 먼저 보는 것이 좋습니다. 이때도 단정하면 안 되고, trace나 구간별 로그로 근거를 남겨야 합니다.
외부 API 문제에 가까운 신호
외부 API 문제에 가까운 신호는 특정 vendor, 특정 endpoint, 특정 시간대에 지연이 몰리는 것입니다. 예를 들어 결제 승인 API만 느려지고 조회 API는 정상이라면, 같은 외부 시스템 안에서도 특정 기능의 처리 지연일 수 있습니다.
/payment/approve p95=4.2s
/payment/cancel p95=310ms
/payment/status p95=280ms
이런 상황에서는 네트워크 전체가 느린 것이 아니라 특정 외부 API endpoint의 처리 시간이 늘어난 것으로 볼 수 있습니다. vendor status 페이지, 외부사 공지, 같은 시간대의 timeout 로그, 재시도 횟수 증가 여부를 함께 확인해야 합니다.
API latency 증가 원인으로 자주 나오는 케이스
HTTP, 네트워크, API latency 문제는 원인이 하나로만 떨어지지 않는 경우가 많습니다. 그래도 실무에서 자주 만나는 패턴은 어느 정도 정리할 수 있습니다. 아래 항목은 장애 보고서나 회고에서 반복적으로 등장하는 원인들입니다.
1. 외부 API 서버의 응답 지연
가장 단순한 원인입니다. 외부 API 서버가 내부적으로 느려져서 응답 시작 자체가 늦어지는 경우입니다. 이때 우리 서버의 TCP 연결이나 TLS 연결은 빠르게 끝나지만, 첫 응답 바이트가 늦게 도착합니다.
이 경우 재시도를 무작정 늘리면 상황이 더 나빠질 수 있습니다. 외부 서버가 이미 느린 상태인데 재시도 트래픽까지 늘어나면 대기열이 더 길어질 수 있기 때문입니다. 재시도는 횟수, 간격, 대상 에러를 제한해서 적용해야 합니다.
2. 커넥션 재사용 실패
HTTP Keep-Alive가 제대로 동작하지 않으면 요청마다 TCP 연결과 TLS 핸드셰이크를 새로 수행합니다. 요청량이 적을 때는 티가 잘 나지 않지만, 호출량이 늘어나면 연결 비용이 latency로 드러납니다.
좋지 않은 패턴:
- 요청마다 새 HttpClient 생성
- 응답 body를 끝까지 읽지 않음
- connection pool 미사용
- Keep-Alive timeout 불일치
개선 방향:
- HttpClient를 재사용
- pool size와 timeout 명시
- 응답 리소스 정리
- idle connection 관리
특히 요청마다 HTTP 클라이언트를 새로 만드는 코드는 테스트 환경에서는 문제를 잘 드러내지 않습니다. 운영 환경에서는 호출량과 동시성이 올라가면서 커넥션 생성 비용, 포트 사용량, TLS 비용이 함께 증가합니다.
3. DNS 조회 지연 또는 캐시 설정 문제
DNS 조회가 매번 오래 걸리면 API 호출 전체 latency가 증가합니다. Java 애플리케이션에서는 JVM DNS cache 설정, 컨테이너 환경의 resolv.conf, 내부 DNS 서버 상태를 같이 봐야 합니다.
도메인 기반으로 외부 API를 호출할 때 DNS TTL이 짧거나, 캐시가 기대와 다르게 동작하면 특정 시점에 조회 지연이 튈 수 있습니다. 이 문제는 애플리케이션 로그만으로는 잘 보이지 않기 때문에 구간별 측정이 필요합니다.
4. Timeout 설정이 너무 길거나 구분되어 있지 않음
timeout은 단순히 장애를 빨리 실패시키기 위한 설정이 아닙니다. 느린 외부 의존성이 내부 요청 스레드를 오래 붙잡지 못하게 막는 안전장치입니다. connect timeout, read timeout, connection request timeout을 구분해서 설정해야 합니다.
connect timeout: TCP 연결을 맺는 시간 제한
connection request timeout: pool에서 커넥션을 얻는 시간 제한
read timeout: 응답 데이터를 기다리는 시간 제한
모든 timeout을 길게 잡아두면 일시적인 지연에는 관대해질 수 있지만, 장애 전파에는 취약해집니다. 반대로 너무 짧게 잡으면 정상적인 느린 응답까지 실패 처리될 수 있습니다. 그래서 API의 성격에 따라 기준을 나눠야 합니다.
5. 재시도 정책이 latency를 더 키움
재시도는 실패율을 낮추는 데 도움이 되지만, 잘못 적용하면 latency를 크게 늘립니다. 특히 동기 API 요청 안에서 3번 재시도하고 각 요청마다 3초 timeout을 둔다면, 최악의 경우 한 요청이 9초 이상 붙잡힐 수 있습니다.
잘못된 예:
read timeout 3초
retry 3회
backoff 없음
결과:
사용자 요청 하나가 외부 API 지연에 오래 묶임
재시도를 넣을 때는 모든 실패에 재시도하지 않는 편이 낫습니다. connection reset, 503, 일시적인 네트워크 오류처럼 재시도 가치가 있는 케이스와, 400번대처럼 재시도해도 의미 없는 케이스를 구분해야 합니다.
API latency 문제를 해결할 때의 대안 비교
API latency가 증가했다고 해서 바로 캐시를 넣거나 서버를 늘리는 것은 좋은 접근이 아닙니다. 병목이 외부 API 응답 지연이라면 서버 증설은 효과가 제한적입니다. 커넥션 풀 대기라면 pool 설정과 호출 구조를 먼저 봐야 합니다.
| 대안 | 효과가 있는 경우 | 주의할 점 |
|---|---|---|
| Timeout 조정 | 외부 지연이 내부로 전파되는 경우 | 너무 짧으면 정상 요청도 실패할 수 있음 |
| Connection pool 조정 | 동시 호출 증가로 pool 대기가 생긴 경우 | 무작정 키우면 외부 API에 부담을 줄 수 있음 |
| Retry + Backoff | 일시적인 네트워크 오류가 있는 경우 | 동기 요청 latency를 늘릴 수 있음 |
| Circuit Breaker | 외부 API 장애가 반복되는 경우 | fallback 정책이 함께 설계되어야 함 |
| 비동기 처리 | 사용자 응답과 외부 처리를 분리할 수 있는 경우 | 정합성, 재처리, 상태 관리가 필요함 |
| 캐시 | 조회성 API이고 응답 재사용이 가능한 경우 | 데이터 신선도 기준이 필요함 |
저는 먼저 측정 가능한 작은 개선부터 적용하는 편입니다. timeout을 명확히 나누고, connection pool 대기 시간을 확인하고, retry 정책을 제한하는 것만으로도 불필요하게 긴 대기 시간을 줄일 수 있습니다. 구조 변경은 그다음에 검토해도 늦지 않습니다.
Spring Boot에서 HTTP API latency를 추적하는 로그 예시
Spring Boot에서 외부 API latency를 분석하려면 요청 전후 시간을 남기는 것이 기본입니다. 단순히 전체 elapsed만 남기기보다 API 이름, endpoint, status, timeout 여부, retry 횟수를 같이 남기면 나중에 원인 분석이 쉬워집니다.
long start = System.currentTimeMillis();
try {
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
String.class
);
long elapsed = System.currentTimeMillis() - start;
log.info("external_api name={} endpoint={} status={} elapsed={}ms",
"paymentVendor",
"/v1/payments",
response.getStatusCode().value(),
elapsed
);
return response.getBody();
} catch (Exception e) {
long elapsed = System.currentTimeMillis() - start;
log.warn("external_api_failed name={} endpoint={} elapsed={}ms error={}",
"paymentVendor",
"/v1/payments",
elapsed,
e.getClass().getSimpleName()
);
throw e;
}
이 정도 로그만 있어도 장애 상황에서 큰 도움이 됩니다. 다만 운영 환경에서는 URL 전체를 그대로 남기면 개인정보나 토큰이 섞일 수 있으므로 query string, header, body 로그는 조심해야 합니다. 필요한 값만 구조화해서 남기는 것이 안전합니다.
해결 방법은 원인별로 달라져야 합니다
API latency 문제의 해결책은 원인에 맞춰야 합니다. 외부 API 서버가 느린 상황에서 내부 서버만 늘리는 것은 효과가 작습니다. 반대로 커넥션 풀이 부족한 상황에서 외부사에 문의만 보내면 문제 해결이 늦어집니다.
외부 API 응답 지연이 원인일 때
외부 API의 TTFB가 증가했다면 timeout, retry, fallback을 먼저 정리해야 합니다. 사용자 요청을 끝까지 붙잡고 기다릴지, 빠르게 실패시키고 나중에 재처리할지 결정해야 합니다. 결제, 인증, 주문처럼 정합성이 중요한 기능에서는 무조건 빠른 실패가 답은 아닐 수 있습니다.
이때는 요청의 성격을 나눠야 합니다. 사용자가 즉시 결과를 알아야 하는 요청인지, 내부적으로 나중에 보정 가능한 요청인지에 따라 처리 전략이 달라집니다. 조회성 API라면 캐시나 fallback 응답을 검토할 수 있고, 상태 변경 API라면 중복 요청과 재처리 정책을 더 신중하게 봐야 합니다.
커넥션 풀 대기가 원인일 때
커넥션 풀 대기가 원인이라면 pool size를 늘리는 것만으로 끝내면 안 됩니다. 외부 API가 받아줄 수 있는 동시 요청 수, 우리 서버 인스턴스 수, endpoint별 호출량을 같이 봐야 합니다. 서버 10대에서 각자 200개씩 커넥션을 열면 외부 API 입장에서는 2,000개의 동시 연결이 될 수 있습니다.
따라서 pool size는 내부 처리량만 보고 정하지 말고 외부 API의 허용량과 함께 맞춰야 합니다. 필요하면 endpoint별로 클라이언트를 분리하거나, 중요도가 낮은 호출은 bulkhead 방식으로 격리하는 것도 고려할 수 있습니다.
네트워크 지연이 원인일 때
네트워크 지연이 의심된다면 애플리케이션 로그만으로 판단하기 어렵습니다. 특정 AZ, 특정 NAT Gateway, 특정 outbound 경로에서만 지연이 생기는지 확인해야 합니다. 같은 API를 다른 서버, 다른 네트워크 위치에서 호출해 비교하면 원인을 좁히는 데 도움이 됩니다.
확인 순서 예시:
1. 같은 서버에서 curl로 외부 API 호출
2. 다른 서버 또는 다른 AZ에서 동일 호출
3. DNS lookup 시간 비교
4. TCP connect 시간 비교
5. 애플리케이션 trace와 네트워크 지표 비교
이 과정에서 특정 서버에서만 문제가 재현된다면 애플리케이션 설정이나 해당 서버의 네트워크 상태를 봐야 합니다. 모든 서버에서 동일하게 느리다면 외부 API 또는 공통 네트워크 경로를 의심할 수 있습니다.
적용 후 확인해야 할 지표
API latency 개선 작업을 했다면 평균 응답 시간만 보면 부족합니다. p95, p99 latency가 실제로 낮아졌는지, timeout이 줄었는지, retry 횟수가 과도하게 늘지 않았는지 함께 봐야 합니다.
| 지표 | 확인 이유 |
|---|---|
| p95 / p99 latency | 일부 느린 요청이 줄었는지 확인 |
| timeout count | 실패가 증가했는지 확인 |
| retry count | 재시도가 과도하게 발생하는지 확인 |
| connection pool pending | 커넥션 대기가 줄었는지 확인 |
| external API status code | 429, 500, 503 같은 응답 증가 여부 확인 |
| 사용자 요청 성공률 | latency 개선이 실제 사용자 경험으로 이어졌는지 확인 |
개선 후 p99 latency는 낮아졌지만 timeout count가 늘었다면, 단순히 빨리 실패하도록 바뀐 것일 수 있습니다. 이 경우 사용자 입장에서는 응답이 빨라진 것이 아니라 실패가 빨라진 것입니다. 그래서 latency와 성공률은 같이 봐야 합니다.
API latency 장애를 줄이기 위한 운영 기준
HTTP API 호출은 언제든 느려질 수 있습니다. 외부 API를 사용하는 순간 우리 시스템의 안정성은 외부 시스템의 상태에도 영향을 받습니다. 그래서 단순히 호출 코드를 작성하는 것에서 끝내지 말고, 느려졌을 때 어떻게 제한하고 복구할지까지 설계해야 합니다.
제가 기준으로 삼는 것은 세 가지입니다. 먼저 모든 외부 API 호출에는 timeout을 명시합니다. 다음으로 외부 호출 시간을 내부 처리 시간과 분리해서 측정합니다. 마지막으로 재시도는 반드시 제한하고, 실패했을 때의 처리 방식을 기능별로 다르게 둡니다.
운영 기준 예시:
- connect timeout, read timeout, pool wait timeout을 구분한다
- 외부 API별 latency, timeout, status code를 기록한다
- retry는 대상 에러와 횟수를 제한한다
- p95, p99 latency 알림을 설정한다
- 외부 API 장애 시 fallback 또는 재처리 정책을 준비한다
이 기준이 있으면 장애가 발생했을 때도 대응이 단순해집니다. 막연히 서버를 재시작하거나 외부사에 문의하는 대신, 어느 구간에서 지연이 생겼는지 근거를 가지고 이야기할 수 있습니다. 협업할 때도 이 차이가 큽니다.
정리: API 호출 latency 증가는 원인보다 위치를 먼저 찾아야 합니다
API 호출 latency가 갑자기 증가했을 때 가장 위험한 대응은 원인을 너무 빨리 단정하는 것입니다. “외부 API가 느리다”, “네트워크 문제다”, “서버가 부족하다”는 말은 모두 가능성이지만, 측정 없이 결론이 될 수는 없습니다.
먼저 전체 요청 시간을 내부 처리 시간과 외부 호출 시간으로 나누고, 외부 호출은 DNS, TCP connect, TLS handshake, TTFB, response download로 쪼개야 합니다. 그 다음에 timeout, connection pool, retry, fallback, 비동기 처리 같은 대안을 원인에 맞게 선택하는 것이 좋습니다.
HTTP API latency 문제는 코드 한 줄로 끝나는 문제가 아닙니다. 하지만 구간별로 측정하고, timeout과 커넥션 풀을 명확히 관리하고, 재시도 정책을 조심스럽게 설계하면 장애 전파를 크게 줄일 수 있습니다. 운영에서 중요한 것은 모든 지연을 없애는 것이 아니라, 지연이 생겼을 때 어디서 발생했는지 빠르게 확인하고 서비스 전체로 번지지 않게 막는 것입니다.
'개발 > 기타' 카테고리의 다른 글
| API retry 로직 잘못 짜서 장애 난 사례: http 네트워크 오류를 안전하게 다루는 방법 (1) | 2026.06.07 |
|---|---|
| DNS 문제로 장애 난 경험: HTTP 장애처럼 보였지만 원인은 네트워크 이름 해석이었다 (0) | 2026.06.06 |
| HTTP Keep-Alive 설정 안 해서 생긴 성능 문제와 해결 과정 (0) | 2026.06.04 |
| 외부 API 호출 시 간헐적 실패 원인 분석: HTTP API 장애를 추적하는 실무 기준 (0) | 2026.06.02 |
| HTTP 429 Too Many Requests 대응 전략: 백엔드에서 Rate Limit을 다루는 실무 기준 (0) | 2026.06.01 |
