평균은 괜찮아 보이는데 사용자는 느리다고 해요. 사실은 느린 요청들이 끝쪽 꼬리에 숨어 있을 수 있어요.End-to-End Request Debugging에서는 느린 요청 하나를 DNS, 연결, TLS, 프록시, 캐시, 오리진으로 나눠 읽었어요. 그리고 느린 upstream과 느린 render에서는 오리진 안쪽 시간이 남을 기다린 시간인지, 직접 응답을 만든 시간인지 나눠 봤죠. 근데요, 실제 운영에서는 요청 하나만 느린 게 아니라 이런 말이 자주 나와요.
142 ms예요. 꽤 괜찮아 보이죠. 그런데 p99는 2380 ms예요.
이제 질문이 달라져요.
“평균은 괜찮은데, 왜 일부 사용자는 이렇게 느리게 느낄까요?”오늘은 그 끝쪽 느린 구간, 즉 tail latency를 읽어볼게요.
여기서는 통계 공식을 깊게 증명하기보다, 운영 화면에서 평균, p50, p95, p99, max를 어떤 순서로 읽어야 하는지에 집중해요. 지표 이름과 계산 방식은 모니터링 도구마다 조금 다를 수 있으니, 같은 도구 안에서 같은 조건으로 비교하는 습관이 중요해요.
식당 평균 대기 시간은 괜찮은데 줄 끝은 화가 나 있어요
점심시간 식당을 떠올려볼게요. 손님 100명 중 90명은 5분 안에 음식을 받았어요. 그런데 10명은 재료 확인, 결제 오류, 주방 실수 때문에 20분 넘게 기다렸어요. 식당이 이렇게 말할 수도 있어요.“평균 대기 시간은 6분밖에 안 돼요.”틀린 말은 아니에요. 하지만 줄 끝에서 20분 기다린 손님에게는 별로 위로가 안 되죠. 웹 요청도 비슷해요.
그래서 tail latency는 대부분이 아니라 끝쪽 사용자가 겪는 지연을 보는 방식이에요.
이 그림에서 중요한 건
꼬리 지연예요. 평균은 빠른 요청들의 요청이 많으면 낮게 보일 수 있지만, 사용자가 기억하는 건 꼬리 지연의 멈춤일 때가 많아요.
p50, p95, p99는 줄을 세워서 보는 숫자예요
percentile은 요청 시간을 작은 값부터 큰 값까지 줄 세웠을 때, 어느 위치까지를 보는지에 가까워요. 예를 들어 요청 100개를 빠른 순서대로 세웠다고 해볼게요.
그러니까 p99는 “99%가 이 시간보다 느렸다”가 아니에요. 반대로 99%는 이 값 이하였고, 가장 느린 1%는 이 값보다 더 느릴 수도 있다는 뜻이에요.
이 그림에서 p99와 max를 구분해야 해요. p99는 꼬리 쪽을 보지만, max 하나에만 끌려가는 숫자는 아니에요. 반대로 max는 한 번의 특이한 요청에도 크게 흔들릴 수 있어요.
평균이 괜찮아도 p99가 나빠질 수 있어요
요청 10개만 단순하게 놓고 볼게요.2500ms예요.
이때 평균은 마지막 값 때문에 올라가지만, 여전히 “대부분은 괜찮다”는 느낌을 줄 수 있어요.
운영 화면에서는 이런 식으로 보일 수 있죠.
평균은 전체 분위기를 볼 때 유용해요. 하지만 사용자가 “가끔 멈춘다”고 말할 때는 평균보다 p95, p99가 더 빠르게 방향을 잡아줘요.
꼬리 지연은 보통 한 가지 원인으로만 생기지 않아요
p99가 커졌다고 해서 바로 “DB가 느리다”로 끝내면 위험해요. 꼬리 지연은 여러 작은 확률이 겹칠 때 자주 커져요. 그래서 꼬리 지연을 볼 때는 “전체가 느린가요?”보다 먼저 이렇게 물어보는 게 좋아요.- 특정 route만 느린가요?
- 특정 region이나 ISP에서만 느린가요?
- 캐시
MISS일 때만 느린가요? - 로그인 사용자나 큰 계정에서만 느린가요?
- retry가 붙은 요청만 꼬리에 모이나요?
- timeout 설정값 근처에 요청이 몰리나요?
히스토그램이나 버킷으로 모양을 먼저 봐요
percentile 숫자만 보면 꼬리의 모양이 잘 안 보여요. 그래서 가능하면 latency histogram이나 bucket도 같이 봐요. 예를 들어 이런 집계가 있다고 해볼게요.
이때
1-3s, 3-10s 요청의 request id를 뽑아서 trace를 보면 훨씬 좋아요. 평균을 더 오래 쳐다보는 것보다, 꼬리 버킷의 실제 요청 몇 개를 잡는 게 빠를 때가 많거든요.
p99를 볼 때는 같은 조건끼리 나눠야 해요
전체 서비스 p99 하나만 보면 너무 넓어요.
꼬리 지연 디버깅은 “p99가 높다”에서 끝나는 일이 아니에요.
어떤 요청들의 p99가 높은지를 좁히는 일이에요.
request id로 꼬리 요청 몇 개를 실제로 잡아요
숫자가 방향을 알려줬다면, 이제 실제 요청을 봐야 해요.
여기서 Server-Timing과 Request ID가 중요해져요. p99 숫자는 어디가 아픈지 알려주는 알림이고, request id와 trace는 그 느린 요청의 실제 경로를 보여주는 기록이에요.
timeout 근처에 몰리면 retry와 queue를 같이 봐요
꼬리 지연에서 자주 보이는 모양이 있어요.3000ms 근처에 몰려 있으면 우연이 아닐 수 있어요. 설정된 timeout까지 기다렸다가 실패하거나, 한 번 실패한 뒤 retry를 하면서 꼬리가 길어질 수 있어요.
사용자에게는 성공한 요청이에요. 하지만 체감은 느려요. 그래서 성공률만 보면 놓칠 수 있어요. p99와 retry count, upstream duration, timeout 설정을 같이 봐야 해요.
잘못 읽기 쉬운 함정
평균이 좋아졌으니 사용자 경험도 좋아졌다고 보기
평균은 좋아졌는데 p99가 나빠질 수 있어요. 예를 들어 빠른 요청을 더 빠르게 만들었지만, 느린 요청의 원인은 그대로라면 꼬리 사용자는 여전히 불편해요.p99를 max처럼 읽기
p99는 가장 느린 단 하나의 요청이 아니에요. “상위 1% 근처”를 보는 값이에요. 극단값 하나를 보려면 max나 slow sample을 따로 봐야 해요.전체 서비스 p99 하나로 원인을 정하기
전체 p99는 여러 route, 지역, 사용자 조건, cache status가 섞인 값이에요. route, status, region, dependency로 나누지 않으면 원인이 흐려져요.p99 숫자만 보고 실제 요청을 안 보기
percentile은 방향을 알려주지만, 원인 자체는 아니에요. 느린 요청의 request id, trace, 로그, upstream timing을 실제로 봐야 해요.요청 수가 적은 구간의 p99를 과하게 믿기
샘플 수가 작으면 p99가 매우 흔들릴 수 있어요. 20개 요청의 p99와 20만 개 요청의 p99는 신뢰감이 달라요. traffic volume과 집계 기간을 같이 봐야 해요.예시로 같이 읽어볼게요
1. 평균은 괜찮지만 p99가 높은 경우
p99 구간의 request id를 뽑고, 검색 upstream, 캐시 miss, 큰 query 조건을 같이 봐요.
2. 특정 route만 꼬리가 긴 경우
3. p99와 timeout이 붙어 있는 경우
3초쯤 걸리는 느린 성공과 3초 뒤 실패가 섞였을 수 있어요. retry, upstream timeout, connection pool 대기, queue depth를 확인해요.
4. p99는 높은데 앱 로그는 짧은 경우
자, 정리해볼까요?
- tail latency는 대부분의 빠른 요청 뒤쪽에 숨어 있는 느린 요청들의 꼬리예요.
- 평균은 전체 분위기를 보여주지만, “가끔 멈춘다”는 사용자 경험은 p95, p99에서 더 잘 드러날 수 있어요.
- p50, p95, p99는 요청 시간을 줄 세웠을 때의 위치를 보는 값이에요. p99는 max와 같지 않아요.
- p99가 높으면 route, status, region, cache status, user type, upstream dependency로 나눠야 해요.
- 숫자만 보지 말고 p99 구간의 request id, trace, Server-Timing, 로그를 실제로 잡아야 해요.
- timeout 근처에 꼬리가 몰리면 retry, queue, connection pool, upstream timeout을 같이 봐야 해요.
이어서 보면 좋은 글
- Server-Timing과 Request ID는 왜 같이 봐야 할까요? — p99 구간의 느린 요청을 로그와 trace로 이어 붙이는 방법을 볼 수 있어요.
- 느린 upstream과 느린 render는 어떻게 구분할까요? — p99 요청 안쪽에서 남을 기다린 시간과 직접 응답을 만든 시간을 나눠봐요.
- TTFB와 Content Download는 어떻게 다르게 읽을까요? — 꼬리가 첫 바이트 전인지, 첫 바이트 뒤 다운로드인지 먼저 나눠볼 수 있어요.
- Connection reuse, Keep-Alive, Pooling은 왜 같이 봐야 할까요? — 앱 로그는 짧은데 브라우저 쪽 꼬리가 길 때 앞단 연결 재사용과 pool 대기를 이어서 볼 수 있어요.