숏컷 코드
SELECT
percentile_cont(0.5) WITHIN GROUP (ORDER BY response_ms) AS p50,
percentile_cont(0.95) WITHIN GROUP (ORDER BY response_ms) AS p95,
percentile_disc(0.95) WITHIN GROUP (ORDER BY response_ms) AS p95_observed
FROM request_logs
WHERE created_at >= now() - interval '1 hour';SELECT
service,
percentile_cont(ARRAY[0.5, 0.9, 0.99])
WITHIN GROUP (ORDER BY response_ms) AS percentiles
FROM request_logs
GROUP BY service;문법
WITHIN GROUP은 정렬이 계산의 일부인 aggregate에 쓴다
일반 aggregate의 내부 ORDER BY는 선택 사항인 경우가 많지만, ordered-set aggregate는 정렬 자체가 계산의 핵심입니다. percentile_cont(0.95) WITHIN GROUP (ORDER BY response_ms)는 응답 시간을 정렬한 뒤 95번째 백분위 값을 계산합니다.
SELECT
percentile_cont(0.95) WITHIN GROUP (ORDER BY response_ms) AS p95
FROM request_logs;0.95는 각 행마다 달라지는 값이 아니라 aggregate 호출 한 번에 적용되는 직접 인자입니다. 그래서 GROUP BY service를 함께 쓰면 서비스별로 같은 백분위 기준을 적용해 각 그룹의 percentile을 계산합니다.
percentile_cont와 percentile_disc는 값 해석이 다르다
percentile_cont는 연속 percentile입니다. 필요한 위치가 실제 행 사이에 있으면 인접한 값 사이를 보간해 결과를 만듭니다. 수치형 지표나 시간 간격처럼 중간값이 의미 있는 데이터에 적합합니다.
SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY amount)
FROM payments;percentile_disc는 이산 percentile입니다. 정렬된 입력 값 중 실제 존재하는 값을 반환합니다. 등급, 상태 코드, 가격 단계처럼 실제 값 하나를 골라야 할 때 더 적합합니다.
SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY plan_tier)
FROM subscriptions;두 함수 모두 percentile fraction은 0과 1 사이여야 합니다. 입력 값의 NULL은 ordered-set aggregate별 규칙에 따라 무시되거나 정렬 규칙을 따르므로, NULL이 의미 있는 상태라면 계산 전에 별도 분리 집계를 두는 편이 안전합니다.
배열 fraction은 여러 percentile을 한 번에 반환한다
여러 percentile을 같은 기준으로 보고 싶으면 배열을 직접 인자로 넘길 수 있습니다. 결과도 배열로 반환되므로 표시 계층에서는 각 위치가 어떤 percentile인지 이름을 붙여 해석해야 합니다.
SELECT
endpoint,
percentile_cont(ARRAY[0.5, 0.9, 0.95, 0.99])
WITHIN GROUP (ORDER BY response_ms) AS latency_percentiles
FROM request_logs
GROUP BY endpoint;동일한 필터와 동일한 정렬 컬럼으로 여러 percentile을 계산할 때는 배열 형태가 중복을 줄입니다. 다만 결과를 JSON API로 내보낼 때는 p50, p90, p95, p99처럼 명시적인 컬럼으로 풀어 주는 편이 읽기 쉽습니다.
mode는 가장 자주 나온 값을 고른다
mode() WITHIN GROUP (ORDER BY value)는 가장 빈도가 높은 값을 반환합니다. 동률이면 정렬 순서상 먼저 나오는 값이 선택될 수 있으므로, 동률 자체가 중요한 분석에서는 GROUP BY value ORDER BY count(*) DESC로 빈도표를 직접 보는 편이 낫습니다.
SELECT mode() WITHIN GROUP (ORDER BY status) AS common_status
FROM orders;선택 기준
| 상황 | 적합한 선택 |
|---|---|
| 중간값, p95, p99 같은 성능 지표 | percentile_cont |
| 실제 관측값 중 하나를 반환해야 함 | percentile_disc |
| 여러 percentile을 같은 기준으로 계산 | 배열 fraction |
| 가장 흔한 값을 보고 싶음 | mode() |
| 동률과 분포 자체가 중요함 | 별도 빈도표 또는 histogram |
응답 시간 분석에서는 평균보다 percentile이 더 실무적인 경우가 많습니다. 평균은 소수의 긴 요청에 흔들리고, p95나 p99는 사용자가 체감하는 꼬리 지연을 더 잘 드러냅니다.
주의할 점
percentile은 정렬과 집계가 필요하므로 큰 테이블에서 즉시 계산하면 비용이 큽니다. 자주 보는 대시보드라면 시간 범위를 제한하거나, 사전 집계 테이블과 materialized view를 함께 검토해야 합니다.
-- 비용이 큰 전체 테이블 percentile
SELECT percentile_cont(0.99) WITHIN GROUP (ORDER BY response_ms)
FROM request_logs;
-- 시간 범위와 그룹을 제한
SELECT
date_trunc('minute', created_at) AS minute,
percentile_cont(0.99) WITHIN GROUP (ORDER BY response_ms) AS p99
FROM request_logs
WHERE created_at >= now() - interval '1 hour'
GROUP BY minute
ORDER BY minute;percentile은 표본 수가 적으면 해석이 흔들립니다. 그룹별 p99를 보여 줄 때는 count(*)를 함께 표시해 해당 percentile이 충분한 행 수에서 나온 값인지 확인할 수 있게 해야 합니다.
참고 링크
2 sources