ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

응답지연

도입

“느린 코드”보다 대기(wait)가 누적되는 구조에서 더 자주 발생합니다.

백엔드에서 응답 지연을 다루는 순간부터 관점이 바뀝니다. “어떤 함수가 느리냐”보다, 어디에서 기다림이 쌓이는지를 찾아야 합니다. DB 쿼리가 느릴 수도 있지만, 스레드 풀 고갈, 커넥션 풀 고갈, 락 경합, 외부 API 지연, 디스크 I/O 대기 같은 “시스템 대기”가 원인인 경우가 많습니다.

이 글은 “응답이 느려졌을 때” 신입도 따라갈 수 있도록 진단 순서(트리아지)대표 패턴재발 방지 흐름으로 정리합니다.

핵심 메시지

“Latency는 연산이 아니라 대기가 만든다.
대기는 보통 공유 자원의존성에서 시작된다.”

- 운영 관점의 기본 원칙 -

정의

응답 지연은 요청 수신 → 처리 → 응답 전송까지 걸리는 시간이며, 특히 P95/P99가 사용자 체감을 결정합니다.

평균(latency avg)은 “대부분의 요청이 빠를 때” 문제를 가립니다. 사용자가 느끼는 불만은 대체로 일부 요청이 비정상적으로 느려지는 꼬리 지연(Tail Latency)에서 시작합니다. 그래서 성능을 “좋게” 만든다는 건 평균을 10ms 줄이는 것보다, P99를 5초에서 500ms로 낮추는 것에 가깝습니다.

지표 관점

평균보다 P50/P95/P99로 문제를 “정의”해야 원인이 보입니다.
지표 예시 해석
P50 120ms 대부분의 사용자 경험(중앙값)
P95 800ms 불만/이탈이 시작되는 구간(서비스 성격에 따라 다름)
P99 5s 대기 누적의 결과(풀 고갈, 락, I/O, 재시도 폭발)

💡 TIP / “평균은 건강, P99는 면역력”

평균이 괜찮아도 P99가 흔들리면 운영은 불안정합니다. P99는 “예외 상황에서 시스템이 버티는 힘(면역력)”을 보여줍니다. 따라서 응답 지연 대응은 평균 최적화보다 꼬리 지연을 줄이는 구조 개선이 중심이 됩니다.

 

진단 순서(트리아지)

“어디서 기다리는지”를 찾기 위해 CPU → I/O → 풀/큐 → 의존성 순서로 좁혀갑니다.

1
CPU 바운드인가? CPU 80~90%↑면 연산/락/GC를 우선 의심합니다.
2
I/O wait가 높은가? 디스크/네트워크 대기(iowait↑)는 “기다림”의 대표 신호입니다.
3
풀/큐가 고갈됐는가? 스레드 풀, 커넥션 풀, 요청 큐가 max 근처면 꼬리 지연이 폭발합니다.
4
의존성(다운스트림)이 느린가? 외부 API/DB/캐시가 느려지면 타임아웃·재시도가 지연을 증폭시킵니다.

대표 패턴

응답 지연은 보통 풀 고갈, 락 경합, I/O 대기, 재시도 폭발 중 하나로 귀결됩니다.

패턴 1

스레드 풀 고갈(Thread Pool Exhaustion)

요청 처리 스레드가 외부 호출(HTTP/DB)을 블로킹으로 기다리는 동안 점유되면, 동시에 들어오는 요청이 늘수록 스레드가 잠식됩니다. 스레드 풀은 결국 max에 도달하고, 이후 요청은 큐에서 대기하거나 타임아웃으로 떨어지며 P99가 급격히 증가합니다.

체크 포인트

  • active thread가 max에 근접하는가?
  • 요청 큐(queue)가 증가하는가?
  • 특정 외부 호출/쿼리 시간이 튀는가(APM trace)?
  • 타임아웃 설정이 과도하게 긴가?

👍 GOOD

  • 다운스트림 타임아웃을 짧고 명확하게 설정(예: 1~2s)
  • 서킷 브레이커/벌크헤드로 전염 차단
  • 스레드풀/큐 사용률 모니터링 및 알람

👎 BAD

  • 타임아웃 10~30초 + 공격적 재시도(지연 증폭)
  • 요청마다 블로킹 작업을 무제한 수행
  • 풀 고갈 시 “느려지다 멈춤” 형태로 장애 확산

패턴 2

I/O wait 급등(디스크/네트워크 대기)

CPU는 여유인데 응답이 느리다면, “연산”이 아니라 “대기”가 원인일 확률이 큽니다. 그 대표 지표가 iowait입니다. 흔한 케이스는 디스크에 로그를 과도하게 쓰거나, 동기 flush/fync가 잦아져 쓰기 대기가 늘어나는 상황입니다.

💡 TIP / 로그는 운영에서 “성능 비용”이 있다

요청/응답 전문 로깅, 대용량 JSON 문자열 생성은 디스크 I/O뿐 아니라 GC 압박까지 함께 유발합니다. 운영 환경에서는 비동기 로깅, 샘플링, 레벨 정책이 곧 성능 설계입니다.

 

실전 예시(운영 패턴 재구성)

“코드는 그대로인데 느려졌다”는 말이 나오면, 보통 의존성 지연 → 대기 누적 → 풀 고갈 흐름을 의심합니다.

상황: 특정 시간대에 P99가 6~8초로 급상승. 평균은 큰 변화 없음. CPU는 40% 수준으로 여유.
원인: 외부 API 지연이 300ms → 2~3초로 늘었고, 타임아웃이 10초로 길어서 요청 스레드가 오래 점유됨.
결과: 대기가 쌓이면서 스레드 풀이 잠식되고, 이후 요청은 큐에서 대기 → Tail Latency 폭발.

해결 전략

응답 지연을 줄이는 핵심은 대기의 전염을 끊고, 자원을 분리하며, 타임아웃/재시도를 통제하는 것입니다.

✅ 실무에서 바로 효과 나는 5가지

  • ✔️ 다운스트림 타임아웃을 짧고 명확하게 설정(예: 1~2s)
  • ✔️ 리트라이 정책을 제한(무한/공격적 재시도는 지연을 증폭)
  • ✔️ 서킷 브레이커로 느린 의존성의 전염 차단
  • ✔️ 벌크헤드(풀 분리)로 핵심 API 보호
  • ✔️ 스레드풀/커넥션풀/큐 사용률 알람(예: 80%↑)

 

정리

응답 지연은 대기 누적을 줄이는 싸움이며, 평균보다 P95/P99를 기준으로 구조를 개선해야 합니다.

신입일 때는 “어느 코드가 느리냐”에 집중하기 쉽지만, 운영에서는 “어디서 기다림이 쌓이냐”가 먼저입니다. 응답 지연을 제대로 다루려면 풀(스레드/커넥션), 타임아웃, 리트라이, 의존성 지연을 한 세트로 봐야 합니다. 이 관점을 갖추면, 같은 장애를 더 빨리 진단하고 재발까지 줄일 수 있습니다.

728x90