← 블로그 목록

Developer Knowledge

개발자가 알아야 할 지식: 평균 지연 시간에 속지 않는 법, 꼬리 지연과 퍼센타일

평균 응답 시간만 보면 놓치기 쉬운 꼬리 지연의 의미, p95/p99 지표 해석, 히스토그램 기반 관측, 실무 적용 체크리스트를 정리한다.

Google SRE
  • 개발자가 알아야 할 지식
  • Software Engineering
  • Performance
  • Observability
  • Reliability

왜 개발자가 알아야 하나

서비스가 느리다는 신고를 받았는데 대시보드의 평균 응답 시간은 멀쩡한 적이 있다. 이 상황은 꽤 흔하다. 평균은 전체 요청 시간을 하나의 숫자로 눌러 담기 때문에, 대부분의 요청이 빠르고 일부 요청만 매우 느린 상태를 잘 숨긴다. 사용자는 평균 사용자가 아니다. 결제 버튼을 누른 사람이 5초를 기다렸다면, 다른 사용자가 50밀리초에 응답받았다는 사실은 그 사람에게 아무 도움이 되지 않는다.

꼬리 지연(tail latency)은 응답 시간 분포의 느린 끝부분을 말한다. 보통 p95, p99, p99.9 같은 높은 퍼센타일로 본다. p99가 2초라면 요청의 99%는 2초 이하로 끝났고, 나머지 1%는 그보다 오래 걸렸다는 뜻이다. 1%라는 숫자는 작아 보이지만 하루 1천만 요청을 처리하는 서비스에서는 10만 건이다. 장애라고 부르기 애매한 성능 문제도 이 정도 규모가 되면 고객센터, 이탈률, 재시도 폭증, 운영 피로로 이어진다.

분산 시스템에서는 꼬리 지연이 더 중요해진다. 한 사용자 요청이 내부적으로 검색, 권한 확인, 재고 조회, 추천, 결제 상태 확인 등 여러 하위 호출로 나뉘면, 전체 응답 시간은 대개 가장 느린 호출의 영향을 받는다. 각 하위 서비스의 느린 요청 비율이 작아도 호출 수가 많아질수록 적어도 하나가 느릴 가능성은 커진다. 그래서 평균이 좋은 서비스들을 조합했는데도 최종 사용자 경험은 나빠질 수 있다.

개발자가 이 개념을 알아야 하는 이유는 성능 개선의 우선순위가 달라지기 때문이다. 평균을 줄이는 최적화와 p99를 줄이는 최적화는 같은 일이 아닐 때가 많다. 캐시 적중률을 높이는 일, 큐 대기 시간을 제한하는 일, DB의 특정 쿼리 플랜 변동을 잡는 일, 커넥션 풀 고갈을 막는 일, 과도한 동시성을 제어하는 일은 평균보다 꼬리에서 먼저 효과가 보인다. 반대로 평균만 보고 “충분히 빠르다”고 판단하면, 실제 사용자가 겪는 가장 답답한 순간을 놓친다.

핵심 개념

퍼센타일은 값들을 작은 순서대로 정렬했을 때 특정 위치에 있는 값을 뜻한다. p50은 중앙값이다. 요청 100개 중 절반은 이 값보다 빠르고 절반은 느리다. p95는 하위 95%가 이 값 이하라는 뜻이고, p99는 하위 99%가 이 값 이하라는 뜻이다. 평균은 모든 값을 더해 개수로 나눈 값이지만, 퍼센타일은 분포의 모양을 보여준다. 특히 지연 시간처럼 한쪽으로 긴 꼬리를 가진 데이터에서는 평균보다 퍼센타일이 훨씬 설명력이 좋다.

예를 들어 요청 10개가 각각 40, 42, 43, 45, 46, 47, 49, 50, 52, 2000밀리초에 끝났다고 하자. 평균은 약 281밀리초다. 평균만 보면 나쁘지 않아 보일 수 있지만 실제로는 한 요청이 2초 동안 멈췄다. p50은 46밀리초 근처이고, 높은 퍼센타일은 2초짜리 요청의 존재를 드러낸다. 운영자가 알아야 할 질문은 “평균적으로 빠른가?”뿐 아니라 “느린 요청은 얼마나 느린가?”, “느린 요청이 어느 경로에서 생기는가?”, “그 비율이 배포 이후 늘었는가?”다.

꼬리 지연을 제대로 보려면 히스토그램이 필요하다. 히스토그램은 관측값을 여러 구간(bucket)에 나눠 세고, 그 분포를 기반으로 퍼센타일을 계산할 수 있게 한다. OpenTelemetry의 메트릭 데이터 모델도 히스토그램을 측정값의 집합을 압축해 표현하는 방식으로 설명한다. 단일 평균이나 합계만 저장하면 나중에 p95나 p99를 복원하기 어렵다. 그래서 HTTP 요청 시간, DB 쿼리 시간, 큐 대기 시간처럼 분포가 중요한 값은 처음부터 히스토그램으로 계측하는 편이 좋다.

주의할 점은 퍼센타일도 완벽한 숫자가 아니라는 것이다. 히스토그램 버킷이 너무 거칠면 p99 추정이 부정확할 수 있고, 태그나 라벨을 너무 잘게 쪼개면 각 시계열의 표본 수가 줄어 높은 퍼센타일이 흔들린다. 1분 동안 요청이 20개뿐인 엔드포인트에서 p99를 크게 해석하는 것도 위험하다. 퍼센타일은 표본 수, 집계 기간, 버킷 설계, 라벨 카디널리티와 함께 봐야 한다.

또 하나 중요한 개념은 “사용자 관점의 지연 시간”과 “서버 내부 지연 시간”의 차이다. 서버 핸들러가 100밀리초에 끝났더라도 클라이언트는 DNS, TLS, 네트워크 왕복, 프록시, CDN, 다운로드, 렌더링 때문에 더 오래 기다릴 수 있다. 반대로 서버 내부 대시보드만 보고 사용자 경험을 결론내리면 실제 병목이 엉뚱한 곳에 있을 수 있다. 좋은 관측은 서버, 의존성, 네트워크, 클라이언트의 지표를 서로 연결해서 본다.

작은 예시 또는 체크리스트

API 서버의 /checkout 엔드포인트가 있다고 하자. 최근 배포 후 평균 응답 시간은 180밀리초에서 200밀리초로 조금 올랐다. 얼핏 보면 큰 문제가 아니다. 그런데 p95는 600밀리초에서 1.8초로, p99는 1.2초에서 6초로 올랐다. 이 경우 평균 증가는 작은 증상이지만, 실제 문제는 느린 요청의 꼬리가 길어진 것이다.

조사 과정은 다음처럼 잡을 수 있다.

  • 평균, p50, p95, p99를 같은 그래프에서 보고 배포 시점과 맞춘다.
  • /checkout 전체 지연 시간을 내부 단계별로 나눈다: 인증, 재고 조회, 쿠폰 계산, 결제 API 호출, DB 저장.
  • 각 단계의 히스토그램을 확인해 꼬리가 어느 구간에서 길어졌는지 찾는다.
  • 느린 요청의 trace를 샘플링해 특정 DB 쿼리, 외부 API, 커넥션 풀 대기, 락 경합이 있는지 본다.
  • 요청량 증가, 캐시 미스 증가, 특정 고객군이나 지역, 특정 상품군 같은 차원을 비교한다.
  • 임시 대응으로 타임아웃, 큐 길이 제한, 캐시 fallback, 비핵심 호출 제거가 가능한지 검토한다.

이 체크리스트의 핵심은 평균을 쪼개는 것이다. 전체 평균을 계속 바라보는 대신 분포, 경로, 의존성, 시점, 사용자군으로 나눠야 원인이 보인다. 꼬리 지연은 대개 “모든 요청이 조금 느려졌다”보다 “특정 조건의 요청이 아주 느려졌다”에 가깝다.

실무에서 자주 생기는 오해

  • 평균 응답 시간이 낮으면 사용자 경험도 좋다고 믿기 쉽다. 평균은 대표값일 뿐이고, 느린 요청의 빈도와 심각도를 숨길 수 있다. 특히 요청량이 많은 서비스에서는 작은 꼬리 비율도 큰 사용자 수가 된다.

  • p99는 항상 p95보다 중요하다고 생각하는 것도 오해다. p99는 느린 끝을 더 민감하게 보여주지만 표본 수가 적거나 집계 구간이 짧으면 흔들린다. 트래픽이 적은 API는 p95, 최대값, 개별 trace, 에러율을 함께 보는 편이 더 현실적일 수 있다.

  • 퍼센타일 알림을 너무 촘촘하게 걸면 알림 피로가 생긴다. p99가 순간적으로 튀었다고 매번 깨우는 대신, 사용자 영향이 있는 기간, 요청량, 에러율, SLO burn rate와 연결해서 알림 조건을 설계해야 한다.

  • 모든 꼬리 지연을 코드 최적화로 해결하려는 접근도 위험하다. 원인은 GC, 디스크 I/O, 네트워크 혼잡, DB 락, 커넥션 풀, 큐 적체, 외부 의존성, 배치 작업, noisy neighbor처럼 다양하다. 코드 한 줄보다 자원 격리나 동시성 제한이 더 효과적일 때가 많다.

  • 히스토그램 버킷을 기본값 그대로 두면 충분하다고 생각하기 쉽다. 서비스의 목표 지연 시간이 100밀리초인지 3초인지에 따라 필요한 버킷 경계가 다르다. 중요한 SLO 경계 주변에는 해상도가 있어야 한다.

  • 최대값(max)을 p99 대신 쓰면 된다는 생각도 조심해야 한다. 최대값은 이상치를 보여주는 데 유용하지만, 단 한 건의 특이 사례에 지나치게 흔들린다. 운영 판단에는 분포와 비율이 필요하다.

오늘 바로 적용해보기

먼저 주요 사용자 흐름 3개를 고르자. 예를 들어 로그인, 검색, 결제, 글 작성, 파일 업로드처럼 사용자가 직접 기다리는 흐름이다. 각 흐름에 대해 평균만 있는지, p50/p95/p99가 있는지, 히스토그램으로 계측되고 있는지 확인한다. 평균만 있다면 “성능을 보고 있다”기보다 “성능의 일부만 보고 있다”에 가깝다.

다음으로 대시보드의 그래프 구성을 바꿔보자. 평균 응답 시간 그래프 하나 대신 요청량, 에러율, p50, p95, p99를 한 화면에 배치한다. 가능하다면 배포 마커와 인프라 이벤트도 함께 표시한다. 성능 문제는 숫자 하나보다 여러 신호의 관계에서 더 잘 보인다.

코드 리뷰에서는 새 외부 호출이나 새 DB 쿼리가 들어올 때 “평균적으로 얼마나 빠른가?”보다 “느릴 때 얼마나 오래 붙잡을 수 있는가?”를 물어보자. 타임아웃이 있는지, 큐가 무한히 쌓이지 않는지, 커넥션 풀이 고갈될 때 다른 기능까지 막히지 않는지, 비핵심 의존성은 fallback이 가능한지 확인한다.

운영 중인 서비스라면 느린 요청 20개를 직접 열어보는 것도 좋다. trace나 로그에서 공통점을 찾다 보면 평균 그래프가 말하지 못한 이야기가 나온다. 특정 지역, 특정 테넌트, 특정 쿼리 파라미터, 특정 캐시 키, 특정 배치 시간대가 반복될 수 있다. 꼬리 지연 분석은 통계와 디버깅이 만나는 지점이다.

마지막으로 SLO를 평균이 아니라 사용자 경험에 가까운 기준으로 표현해보자. 예를 들어 “요청의 95%가 300밀리초 이하, 99%가 1초 이하”처럼 말하면 팀이 더 구체적인 판단을 할 수 있다. 이 기준은 무조건 엄격할 필요는 없다. 중요한 것은 사용자가 체감하는 느림을 팀이 같은 언어로 다루는 것이다.

더 알아보기

오늘의 takeaway

평균 지연 시간은 서비스의 기분을 말해주지만, p95와 p99는 사용자가 실제로 어디에서 기다리는지 알려준다.