서비스가 커질수록 성능 문제의 본질은 평균 응답시간이 아니라 꼬리 지연(Tail Latency)으로 이동합니다. 대시보드에 평균 120ms가 찍혀도 P99가 1.8초면, 사용자 100명 중 1명은 매 요청에서 거의 “멈춘 것 같은” 경험을 하게 됩니다. 문제는 여기서 끝나지 않습니다. 느린 요청이 스레드·커넥션·락을 오래 점유하면서 큐를 길게 만들고, 결국 전체 시스템의 처리율까지 떨어뜨립니다.
그래서 실무에서는 “빠른 코드"보다 “느린 구간이 시스템 전체를 망치지 않게 하는 설계"가 더 중요합니다. 이 글은 바로 그 설계를 위한 운영형 기준을 다룹니다.
이 글에서 얻는 것
- 평균 중심 성능 점검에서 벗어나 P95/P99 중심 운영 체계로 전환하는 방법을 이해할 수 있습니다.
- 타임아웃을 감으로 넣는 대신, 서비스 홉별로 Timeout Budget을 숫자로 배분하는 기준을 가져갈 수 있습니다.
- 큐 규율, 동시성 제한, 헤지드 요청을 어떤 순서와 조건으로 적용해야 하는지 의사결정 우선순위를 잡을 수 있습니다.
핵심 개념/이슈
1) Tail Latency는 “느린 요청” 문제가 아니라 “포화 전이” 문제다
많은 팀이 성능 이슈를 “특정 쿼리가 느리다"로만 봅니다. 하지만 실제 장애는 보통 다음 순서로 번집니다.
- 일부 요청이 느려짐(P99 상승)
- 동시 처리 슬롯(스레드/풀/커넥션) 점유시간 증가
- 대기열 길이 증가
- 타임아웃/재시도 급증
- 전체 지연시간과 에러율 동반 악화
즉 Tail Latency는 개별 요청의 문제가 아니라 시스템 포화로 넘어가는 초기 신호입니다. 이 관점은 용량 계획과 포화도 관리를 먼저 이해하면 훨씬 명확해집니다.
2) 타임아웃은 “하나의 숫자"가 아니라 예산(Budget)이다
“클라이언트 타임아웃 3초"처럼 단일 숫자만 정하면, 내부 홉이 3개인 서비스에서 각 홉이 모두 2.5초씩 버티는 상황이 쉽게 생깁니다. 그러면 상위 서비스는 실패했고, 하위 서비스는 불필요한 일을 계속 수행해 리소스만 낭비합니다.
권장 방식은 요청 전체 시간 예산을 홉별로 분할하는 것입니다.
- 전체 사용자 SLA: 1200ms
- API Gateway: 100ms
- 비즈니스 서비스: 700ms
- DB/외부 API: 300ms
- 여유(직렬화/네트워크 변동): 100ms
여기서 중요한 건 “합이 SLA를 넘지 않게” 강제하는 것입니다. 타임아웃/재시도 기본 원칙은 Timeout·Retry·Backoff와 맞물려 운영해야 합니다.
3) 큐 규율이 없으면 P99 개선은 거의 불가능하다
P99를 줄이려면 CPU 최적화보다 먼저 대기열 제어를 해야 합니다. 대표적인 실전 규칙은 아래와 같습니다.
- 큐 길이 상한:
queue_length <= worker * 2 - 대기시간 상한:
queue_wait_p95 <= 50ms - 초과 시 정책: 신규 요청 무제한 대기 금지, 즉시 429/503 또는 낮은 우선순위 드롭
특히 “중요 요청과 비중요 요청을 같은 큐"에 넣으면 꼬리 지연이 폭발합니다. 결제/인증/주문 확정 같은 핵심 트래픽은 별도 큐와 별도 동시성 풀로 분리해야 합니다. 이 부분은 Admission Control과 동시성 제한을 함께 적용해야 효과가 납니다.
4) 헤지드 요청(Hedged Request)은 강력하지만, 조건이 맞을 때만 써야 한다
헤지드 요청은 특정 임계시간을 넘긴 느린 요청에 대해 같은 요청을 다른 복제본에 한 번 더 보내, 먼저 도착한 응답을 채택하는 방식입니다. 분산 저장소/검색 계층에서 Tail Latency를 깎는 데 매우 효과적입니다.
다만 아래 조건을 만족해야 안전합니다.
- 읽기 요청 또는 멱등 보장 요청
- 백엔드 여유 용량이 존재(평균 CPU 50~60% 이하 권장)
- 중복 요청으로 인한 비용 증가를 허용 가능
실무 규칙 예시:
- 헤지 시작 시점:
p95_latency * 1.2 - 헤지 비율 상한: 전체 요청의 3~5%
- 활성화 조건: 최근 10분 P99가 SLO의 1.3배 이상
조건 없이 상시 헤지를 켜면 오히려 트래픽이 증폭되어 장애를 키울 수 있습니다.
5) Tail Latency의 주범은 종종 “코드"보다 “공유 자원"이다
P99를 올리는 주요 원인은 알고리즘 복잡도보다 공유 자원 경합인 경우가 많습니다.
- DB 커넥션 풀 포화
- 락 경합(핫 로우/핫 키)
- 캐시 미스 폭증 후 동시 재계산
- 느린 외부 API 의존성
그래서 튜닝 우선순위는 보통 다음이 맞습니다.
- 경합 자원 식별(풀/락/큐)
- 동시성 제한과 대기 상한 적용
- 요청 병합/캐시 전략 적용
- 마지막으로 코드 미세 최적화
요청 병합 전략은 Request Coalescing을 참고하면 바로 적용할 수 있습니다.
실무 적용
1) 2주 도입 플랜(현업 서비스 기준)
1~2일차: 기준선 측정
- 엔드포인트별 P50/P95/P99, 타임아웃률, 큐 대기시간 수집
- 평균이 아닌 P99 기준 Top 5 병목 경로 선정
3~5일차: 보호 장치 추가
- 핵심 의존성별 timeout budget 재배분
- 동시성 제한, 큐 길이 상한, 즉시 실패 정책 적용
- 재시도 횟수 상한(예: 최대 2회) 및 지터 백오프 적용
6~10일차: 선택적 최적화
- 캐시 stampede 구간에 요청 병합 적용
- 읽기 경로에만 제한적 헤지드 요청 실험
- 카나리로 5% 트래픽부터 단계적 전환
이 순서를 지키면 “최적화는 했는데 장애는 더 늘어난” 상황을 피하기 쉽습니다.
2) 의사결정 기준(숫자·조건·우선순위)
우선순위는 사용자 체감 안정성 > 시스템 보호 > 평균 성능 수치로 둡니다.
- 즉시 조치 조건
- 10분 이동창에서
P99 > SLO x 1.5 - 타임아웃률 > 2%
- 큐 대기 P95 > 80ms
- 10분 이동창에서
- 보호 장치 기본값
- 핵심 API 동시 실행 상한: CPU 코어 수 x 2~4 범위에서 시작
- 재시도: 읽기 1~2회, 비멱등 쓰기 0회
- 큐 초과 시: low-priority 요청 먼저 거절
- 헤지드 요청 활성화 조건
- 백엔드 포화도 70% 미만
- 중복 비용 허용 도메인(검색/추천/조회)
- 비활성화 가드: 에러율 급등 또는 트래픽 급증 시 즉시 OFF
3) 관측/알람 설계 포인트
P99만 보면 늦습니다. 아래 지표를 같이 봐야 원인에 도달합니다.
request_latency_p99(결과 지표)queue_wait_p95(대기 병목)concurrency_inflight(포화 신호)timeout_rate,retry_rate(보호 장치 부작용)downstream_error_rate(의존성 전파)
알람은 “CPU 90%” 같은 인프라 중심보다 사용자 영향 지표 우선으로 구성하는 게 안전합니다. 세부 기준은 관측 알람 설계를 참고하세요.
4) 장애 시 운영 런북 최소 항목
- 어떤 경로의 P99가 먼저 튀었는지(엔드포인트/테넌트/리전)
- 동시성 상한 조정 전후 지표 비교(대기시간·에러율)
- 재시도 폭증 여부와 비활성화 판단 기준
- 임시 완화책(저우선 트래픽 제한, 캐시 TTL 단기 상향)
- 사후 액션(타임아웃 버짓 문서화, 큐 정책 상수화)
런북은 “누가 봐도 10분 안에 실행 가능한 문장"으로 써야 실제 온콜에서 동작합니다.
트레이드오프/주의점
보호 장치는 단기적으로 에러율을 올릴 수 있다
큐를 무한히 받던 시스템에 상한을 넣으면 초기엔 429/503이 보입니다. 하지만 이건 전체 장애를 막기 위한 통제된 실패입니다.재시도와 헤지는 잘못 쓰면 증폭기다
장애 상황에서 자동 재시도/헤지가 동시에 켜지면 트래픽이 2~3배로 불어나 원래 장애보다 더 크게 번질 수 있습니다.평균 지표 개선이 반드시 사용자 경험 개선은 아니다
P50이 20ms 빨라져도 P99가 나빠지면 체감은 더 악화됩니다. 최적화 목표를 반드시 퍼센타일로 정의해야 합니다.도메인별 예외를 남발하면 운영 일관성이 무너진다
“이 서비스만 특별히"가 많아질수록 런북과 자동화가 깨집니다. 공통 규칙 80%, 도메인 특화 20% 비율이 유지돼야 합니다.
체크리스트 또는 연습
체크리스트
- 서비스별 SLO가 평균이 아닌 P95/P99로 정의돼 있다.
- 요청 전체 타임아웃과 홉별 timeout budget 합이 일치한다.
- 큐 길이/대기시간 상한과 초과 시 정책(거절·강등)이 문서화돼 있다.
- 재시도와 헤지드 요청이 멱등성/포화도 조건을 만족할 때만 동작한다.
- 장애 런북에 “즉시 제한할 트래픽 우선순위"가 명시돼 있다.
연습 과제
- 현재 서비스의 상위 3개 API에 대해
SLO,timeout budget,retry policy를 한 표로 정리해보세요. - 1개 조회 API에 한정해 헤지드 요청 실험안을 작성하고, “활성화/비활성화 조건"을 숫자로 정해보세요.
- P99가 튀는 시간대 로그를 기준으로 큐 대기시간과 동시성 수치의 상관관계를 계산해보세요.
💬 댓글