P95·P99 지연시간이란? 꼬리 지연시간 측정법

Rene, Kabir Goel 
P95·P99 지연시간이란? 꼬리 지연시간 측정법

P99 지연시간은 요청의 99%가 그 안에 끝나는 응답 시간입니다. API의 P99가 400밀리초라면 100번 중 99번은 400 ms 이내에 응답하고 1번은 더 오래 걸립니다. P95는 같은 개념을 95%로 본 것으로, 20번 중 1번은 더 느립니다. 둘 다 분포의 꼬리, 즉 평균이 묻어 버리는 느린 요청을 나타냅니다.

꼬리는 그 비율보다 훨씬 큰 영향을 줍니다. 요청을 한 번만 보내는 사용자는 거의 알아채지 못합니다. 음성 에이전트와 20턴을 대화하는 통화자는 아마 알아챕니다. 이 가이드는 백분위수의 원리, 계산 방법, 수치를 왜곡하는 실수, 그리고 실시간 음성 AI에서의 의미를 다룹니다.

P50, P95, P99 한눈에 보기

백분위수의미이보다 느린 요청
P50중앙값. 전형적인 요청2번 중 1번
P90느린 꼬리의 시작10번 중 1번
P95대시보드와 SLO에서 흔히 쓰는 목표20번 중 1번
P99드문 지연. 중단, 재시도, 대기열100번 중 1번
P99.9트래픽이 많을 때만 보이는 이상치1,000번 중 1번

높은 백분위수일수록 의미 있는 값이 되려면 샘플이 더 많이 필요합니다. 요청 100건으로 낸 P99는 단 1건에 달려 있습니다. P95에는 최소 수백 건, P99에는 수천 건의 샘플을 쓰고, 결과 옆에 샘플 수를 적으세요.

평균이 꼬리를 가리는 이유

요청 100건을 생각해 보세요. 94건은 200 ms에 끝나고 6건은 2초가 걸립니다. 평균은 308 ms로, 실제로 그만큼 걸린 요청은 하나도 없습니다. 중앙값은 200 ms로 아주 좋아 보입니다. P95와 P99는 모두 2,000 ms이고, 이것이 사용자 여섯 명이 실제로 겪은 시간입니다.

실제 지연시간 분포는 이렇게 치우쳐 있습니다. 대부분의 요청은 빠른 경로를 지나고, 일부는 느린 디스크, 콜드 캐시, 가비지 컬렉션 중단, 꽉 찬 대기열 뒤에서 기다립니다. 평균은 두 집단을 섞어 어느 쪽도 설명하지 못하는 숫자를 만듭니다. 중앙값은 느린 집단을 완전히 무시합니다. 백분위수는 느린 집단을 계속 보이게 합니다.

P95와 P99 계산하기

측정값을 빠른 순서로 정렬하고 95% 또는 99% 지점의 값을 읽습니다. 샘플이 200개라면 P95는 190번째, P99는 198번째 값입니다. 이것이 최근접 순위 방식입니다.

import math


def percentile(samples, p):
    """Nearest rank: the smallest sample with at least p% of samples at or below it."""
    ordered = sorted(samples)
    rank = math.ceil(p / 100 * len(ordered))
    return ordered[max(rank, 1) - 1]


latencies_ms = [200] * 94 + [2000] * 6
print(percentile(latencies_ms, 50))  # 200
print(percentile(latencies_ms, 95))  # 2000
print(percentile(latencies_ms, 99))  # 2000

NumPy는 기본적으로 보간하므로 numpy.percentile이 두 샘플 사이의 값을 돌려줄 수 있습니다. 최근접 순위를 쓰려면 method="inverted_cdf"를 넘기세요. 샘플이 적으면 두 방식의 결과가 꽤 다를 수 있으니, 이 점에서도 샘플 수를 함께 적어야 합니다.

모니터링 시스템은 보통 모든 샘플을 저장하지 않습니다. 요청을 지연시간 구간별로 세고 히스토그램에서 백분위수를 추정합니다. Prometheus의 histogram_quantile()이 그런 예입니다. 추정은 주변 구간만큼만 정확하므로, 관심 있는 지연시간 근처에 구간 경계를 두세요.

백분위수를 왜곡하는 세 가지 실수

백분위수를 평균한다. 한 서버는 900건을 모두 200 ms에 처리하고, 다른 서버는 100건을 처리하는데 그중 10건이 1초 걸린다고 해 봅시다. P95는 200 ms와 1,000 ms이고 평균은 600 ms입니다. 하지만 1,000건 전체의 실제 P95는 200 ms입니다. Prometheus 문서도 따로 계산한 분위수는 합칠 수 없다고 말합니다. 히스토그램이나 원시 샘플을 합친 다음 계산하세요.

단계별 백분위수를 더한다. 음성 에이전트는 음성 인식, 언어 모델, 음성 합성을 차례로 실행합니다. 음성 인식은 항상 300 ms, LLM은 보통 500 ms지만 10번 중 1번은 1,500 ms, TTS는 보통 150 ms지만 10번 중 1번은 600 ms가 걸린다고 해 봅시다. 단계별 P95를 더하면 2,400 ms입니다. 하지만 턴 전체의 실제 P95는 1,950 ms입니다. 두 느린 경우가 같은 턴에 겹치는 일은 드물기 때문입니다. 부분에서 추정하지 말고 종단 간 시간을 직접 측정하세요.

응답을 기다리는 클라이언트로 측정한다. 이전 요청이 돌아와야 다음 요청을 보내는 부하 생성기는 서버가 느릴 때 오히려 요청을 덜 보내므로 느린 구간이 적게 잡힙니다. Gil Tene는 이를 coordinated omission이라고 부르며, 그의 부하 생성기 wrk2는 일정한 속도로 요청을 보내 이 문제를 피합니다. 운영 수치는 테스트 클라이언트가 아니라 실제 트래픽으로 측정하세요.

꼬리가 음성 에이전트의 인상을 결정하는 이유

대화는 촘촘한 타이밍으로 돌아갑니다. Levinson과 Torreira의 턴 테이킹 연구 리뷰에 따르면 턴 사이의 공백은 약 200 ms 수준입니다. Kendrick과 Torreira의 전화 통화 연구에서는 약 700 ms가 넘는 공백 뒤의 응답 대부분이 거절 같은 비선호 응답이었고, 약 300 ms만 넘어도 단순한 수락이 줄었습니다. 듣는 사람은 공백에서 의미를 읽기 때문에, 900 ms 동안 말이 없는 에이전트는 통화자에게 느린 것을 넘어 내키지 않는 것처럼 들릴 수 있습니다.

이제 턴 수를 곱해 보세요. 각 턴이 독립적으로 5% 확률로 P95를 넘는다면, 20턴 통화에서 최소 한 번 넘을 확률은 64%입니다(1 − 0.95²⁰). P99조차 20턴 통화의 약 18%에서 나타납니다. 중앙값은 전형적인 턴을 설명하고, 꼬리는 전형적인 통화를 설명합니다. Google의 Jeff Dean과 Luiz André Barroso도 대규모 서비스에 대해 The Tail at Scale에서 같은 점을 지적했습니다. 사용자 동작 하나가 많은 요청으로 나뉘면, 드문 느린 응답이 흔한 일이 됩니다.

음성 파이프라인에서 무엇을 측정할까

통화자가 듣는 것을 측정하세요. 음성 에이전트라면 통화자가 말을 마친 시점부터 응답의 첫 번째 들리는 샘플까지를 통화자 쪽에서 기록합니다. 그다음 단계별로 나눠 꼬리가 어디서 생기는지 확인합니다.

단계측정 구간꼬리의 흔한 원인
턴 감지발화 끝부터 턴 종료 판단까지머뭇거림, 배경 소음, 긴 공백
음성 인식턴 종료부터 최종 전사까지긴 턴, 네트워크 지터
LLM과 도구전사부터 첫 토큰까지(도구 호출 포함)느린 도구, 긴 프롬프트, 제공업체 대기열
음성 합성첫 텍스트 전송부터 첫 들리는 오디오까지새 연결, 긴 첫 문장
네트워크와 전화서버를 떠난 오디오가 통화자에게 닿기까지통신사 라우팅, 지터 버퍼

주목할 음성 합성 지표는 첫 오디오까지의 시간(TTFA)입니다. 스트리밍 모델은 문장이 완성되기 전에 재생을 시작하기 때문입니다. 앞부분의 무음도 포함됩니다. 300 ms 무음으로 시작하는 청크는 통화자에게 300 ms의 추가 대기입니다. 아래 스크립트는 첫 바이트까지를 측정하므로, 오디오 앞부분의 무음은 따로 확인하세요.

다음 스크립트는 Cartesia의 음성 합성 Sonic의 TTFA 백분위수를 측정합니다. 0.5초 간격으로 요청 205건을 예약하고 처음 5건은 버린 뒤, 예약된 시각부터 첫 오디오 바이트까지를 잽니다. 전송 시각이 아니라 예약 시각부터 재기 때문에 느린 응답이 다음 요청을 늦춘 만큼도 수치에 드러나고, 위에서 설명한 coordinated omission을 피할 수 있습니다.

import math
import os
import time

from cartesia import Cartesia


def percentile(samples, p):
    """Nearest rank: the smallest sample with at least p% of samples at or below it."""
    ordered = sorted(samples)
    rank = math.ceil(p / 100 * len(ordered))
    return ordered[max(rank, 1) - 1]


# max_retries=0: a retried request would hide a failure inside a slow sample.
client = Cartesia(api_key=os.environ["CARTESIA_API_KEY"], max_retries=0)
interval = 0.5  # seconds between scheduled requests
ttfa_ms = []
failures = 0
first = time.perf_counter() + interval

for i in range(205):
    # Each request has a fixed start time. If a slow response makes us late,
    # the wait counts against latency instead of quietly thinning the sample.
    scheduled = first + i * interval
    time.sleep(max(0.0, scheduled - time.perf_counter()))
    got_audio = False
    try:
        with client.tts.with_streaming_response.generate(
            model_id="sonic-3.6",
            transcript="Your order shipped this morning and should arrive on Thursday.",
            voice="6ccbfb76-1fc6-48f7-b71d-91ac6298247b",
            output_format={"container": "raw", "encoding": "pcm_s16le", "sample_rate": 16000},
            language="en",
        ) as response:
            for chunk in response.iter_bytes():
                if chunk:
                    got_audio = True
                    if i >= 5:  # the first requests pay for connection setup
                        ttfa_ms.append((time.perf_counter() - scheduled) * 1000)
                    break
    except Exception:
        pass
    if not got_audio:
        failures += 1

print(f"{len(ttfa_ms)} timed requests, {failures} failed or returned no audio")
if ttfa_ms:
    for p in (50, 90, 95, 99):
        print(f"p{p}: {percentile(ttfa_ms, p):.0f} ms")
    print(f"max: {max(ttfa_ms):.0f} ms")

pip install cartesia로 SDK를 설치하고 대시보드에서 받은 CARTESIA_API_KEY를 설정하세요. 서버가 실행되는 리전에서, 트래픽이 가장 많은 시간대에 실행하세요. 집 Wi-Fi에 연결된 노트북으로 재면 Wi-Fi를 재게 됩니다. SDK는 기본적으로 실패한 요청을 두 번 재시도하는데, 그러면 실패와 재시도가 느린 샘플 하나로 섞입니다. 그래서 스크립트는 재시도를 끄고, 오디오가 없는 응답을 포함한 실패를 따로 셉니다. 보고서에서 실패를 빼지 마세요. 응답하지 않은 요청이야말로 최악의 지연시간입니다. 실제 에이전트라면 턴 사이에 연결을 유지하는, 실제로 쓰는 WebSocket 경로를 측정하세요.

공개 벤치마크가 보여 주는 것

Coval은 음성 합성, 음성 인식, 음성 대 음성에 대한 독립 벤치마크를 공개 API를 대상으로 30분마다 실행하고 중앙값, 백분위수, 샘플 수를 공개합니다. 방법론에 따르면 TTFA에 앞부분 무음을 포함하고 연결 핸드셰이크는 제외합니다.

2026년 10월 15일에 확인한 30일 음성 합성 순위에서, 각각 샘플이 260건 이상인 33개 모델 사이에서 중앙값과 P95의 차이는 크게 달랐습니다. 전형적인 모델의 P95는 중앙값의 1.38배였습니다. 양 극단을 보면 한 모델의 P95는 중앙값의 1.1배였고, 다른 모델은 7배가 넘었습니다. 전형적으로는 624 ms, 20번 중 1번은 4,751 ms였습니다.

교훈은 이 가이드 첫머리와 같습니다. 중앙값이 비슷한 두 모델도 통화에서는 아주 다르게 느껴질 수 있습니다. P95를 샘플 수와 함께, 사용자가 경험하는 방식 그대로 측정해 비교하세요.

꼬리 지연시간 줄이기

  • 연결을 열어 두세요. 새 HTTPS 요청마다 TCP와 TLS 설정 비용을 다시 치르며, Cartesia 문서에 따르면 이 비용은 첫 오디오 바이트까지의 시간과 비슷한 수준인 경우가 많습니다. 통화 내내 열려 있는 WebSocket은 이 비용을 한 번만 냅니다.
  • 반복되는 것은 캐시하세요. 인사말, 대기 안내, 마무리 인사는 통화마다 새로 생성할 필요가 없습니다. 원시 오디오로 캐시해 실시간 스트림에 끼워 넣으세요.
  • 더 일찍 말하기 시작하세요. 문단 전체를 기다리지 말고 LLM 토큰이 도착하는 대로 음성 합성에 보내세요.
  • 도구에 기한을 두세요. 2초 걸리는 도구 호출은 매번 턴에 2초를 더합니다. 타임아웃을 정하고, 기다리는 동안 에이전트가 무엇을 하고 있는지 말하게 하세요.
  • 느린 요청을 헤지하세요. The Tail at Scale은 첫 요청이 평소 P95를 넘으면 예비 요청을 보내고 먼저 돌아온 쪽을 쓰는 방법을 설명합니다. P95까지 기다리면 추가 부하가 약 5%로 제한됩니다. 같은 오디오를 다시 생성하는 것처럼 두 번 보내도 안전한 요청에만 쓰고, 데이터를 기록하는 도구 호출에는 절대 쓰지 마세요.
  • 모델을 통화 가까이에서 실행하세요. 네트워크 거리나 공유 인프라가 꼬리의 원인이라면, 온프레미스 배포로 모델을 전화 시스템 바로 옆에 둘 수 있습니다.

파이프라인을 직접 조립하고 조정하고 싶지 않다면, Cartesia Managed Agents가 턴 감지, Ink 음성 인식, LLM, Sonic을 전화번호 위에서 실행합니다. 플레이그라운드에서 만들어 직접 전화를 걸어 볼 수도 있습니다. 직접 만든 파이프라인이 느리다면, 해결책은 대개 측정 결과가 가리키는 단계에 있고, 위의 스크립트가 그 출발점입니다.

관련 가이드

자주 묻는 질문

P99 지연시간이란 무엇인가요?

P99 지연시간은 응답 시간의 99번째 백분위수입니다. 요청의 99%가 그 시간 안에 끝나고, 가장 느린 1%는 더 오래 걸립니다. API의 P99가 400 ms라면 100번 중 1번은 400 ms를 넘깁니다. 평균이 가리는 분포의 느린 쪽을 보여 주는 지표입니다.

P95 지연시간이란 무엇인가요?

P95 지연시간은 요청의 95%가 달성하는 응답 시간입니다. 20번 중 1번은 더 느립니다. 실제 문제에는 반응하면서도 소수의 예외에 크게 흔들리지 않기 때문에 대부분의 팀이 대시보드와 서비스 수준 목표에 사용합니다.

P95와 P99의 차이는 무엇인가요?

P95는 가장 느린 5%, P99는 가장 느린 1%의 요청을 나타냅니다. P95는 평소 부하에서 시스템이 어떻게 동작하는지 보여 주고, P99는 가비지 컬렉션 중단, 콜드 캐시, 재시도, 대기열 같은 개별 사건에 좌우되는 경우가 많습니다. 중앙값과 함께 둘 다 보고하고 샘플 수도 밝히세요.

꼬리 지연시간이란 무엇인가요?

꼬리 지연시간은 지연시간 분포의 느린 쪽, 보통 P95 이상을 말합니다. 히스토그램 오른쪽으로 길게 늘어진 꼬리에서 나온 이름입니다. 한 번의 사용자 동작에 요청을 여러 번 보내거나 대화 턴이 많은 시스템에서는 대부분의 사용자가 결국 꼬리를 만나게 됩니다.

P95 값을 평균해도 되나요?

안 됩니다. 합친 요청 전체의 백분위수는 각 부분 백분위수의 평균이 아니고, 파이프라인의 P95도 단계별 P95의 합이 아닙니다. 원시 샘플이나 히스토그램을 보관해 합친 뒤, 합친 데이터에서 백분위수를 계산하세요.

음성 에이전트에는 어느 정도의 P95 지연시간이 필요한가요?

통화자가 말을 마친 순간부터 처음 소리를 듣는 순간까지를 측정하세요. 대화 연구에 따르면 사람들은 약 200 ms 만에 말할 차례를 넘기고, 약 300 ms가 넘는 공백은 단순한 수락을 줄이며, 약 700 ms가 넘는 공백은 거절 같은 비선호 응답과 연관됩니다. P95가 1초에 가까운 음성 에이전트는 20턴 중 1턴에서 머뭇거리는 것처럼 들립니다.