PySpark 대규모 분위수·통계 — p95 지연시간을 정확히, 빠르게
수십억 행에서 중앙값·백분위(p95/p99)를 구하는 일이 왜 어려운지, 정확한 분위수의 비용과 approxQuantile(근사)의 동작, 그룹별 분위수, 이상치 탐지(IQR), 상관·공분산까지 대규모 통계 계산 패턴을 정리합니다.
"API 응답시간 p95 를 구해줘." 간단해 보이는 이 요청이 수십억 행에서는 까다롭습니다. 백분위(percentile)와 중앙값(median)은 본질적으로 전체를 정렬해야 정확히 구할 수 있고, 분산 환경에서 전역 정렬은 비싼 셔플입니다. 평균은 쉽지만 분위수는 어렵습니다.
이 글은 대규모 분위수가 왜 비싼지, 근사 분위수(approxQuantile)가 어떻게 이를 해결하는지, 그리고 그룹별 분위수·이상치 탐지·상관분석 같은 통계 계산 패턴을 정리합니다.
1. 왜 분위수는 평균보다 어려운가
평균(avg) : sum / count → 부분합을 더하면 됨 (가산적, 셔플 적음)
중앙값/p95 : 정렬된 위치의 값 → 전체 순서를 알아야 함 (전역 정렬 필요)평균·합·최댓값은 결합법칙이 성립해 각 파티션에서 부분 결과를 구해 합치면 됩니다. 하지만 "정확히 95번째 백분위"는 모든 값의 순서를 알아야 하므로, 정확한 계산은 전역 정렬(또는 그에 준하는 셔플)을 요구합니다.
| 통계 | 비용 |
|---|---|
| count, sum, avg, min, max | 싸다(가산적) |
| stddev, var | 싸다(가산적) |
| median, percentile | 비싸다(정렬) |
| distinct count | 비싸다(별도 글 "고카디널리티 집계") |
2. 정확한 분위수 — percentile
정확한 값이 꼭 필요하면 percentile 을 씁니다(비쌈).
from pyspark.sql import functions as F
# 정확한 분위수 (정렬 비용)
df.agg(
F.percentile("latency", 0.5).alias("median"),
F.percentile("latency", 0.95).alias("p95"),
F.percentile("latency", 0.99).alias("p99"))
# percentile_approx 의 정확 버전 또는 expr
df.selectExpr("percentile(latency, array(0.5, 0.95, 0.99)) as pcts")percentile 은 모든 값을 고려해 정확하지만, 대규모에서 메모리·시간이 큽니다.
3. 근사 분위수 — approxQuantile (권장)
대부분의 모니터링·분석 지표는 약간의 오차가 허용됩니다. p95 가 230ms 든 232ms 든 판단은 같습니다. approxQuantile 은 고정 메모리의 스케치로 분위수를 근사해, 정렬 없이 빠르게 계산합니다.
# DataFrame.approxQuantile(col, probabilities, relativeError)
quantiles = df.approxQuantile(
"latency",
[0.5, 0.95, 0.99],
0.001) # 상대 오차 (작을수록 정확·비용↑)
# → [median, p95, p99]
# SQL 표현식 버전 (그룹·집계에 사용)
df.groupBy("endpoint").agg(
F.percentile_approx("latency", 0.95, 10000).alias("p95"))| percentile(정확) | approxQuantile(근사) | |
|---|---|---|
| 정확도 | 100% | relativeError 내 |
| 메모리 | 큼 | 고정(작음) |
| 속도 | 느림 | 빠름 |
| 적합 | 정산·규정 | 모니터링·분석 |
relativeError 트레이드오프: 0.0 이면 정확하지만 비싸고, 0.01(1%)이면 매우 빠릅니다. SLA 모니터링은 보통 0.001~0.01 로 충분합니다.
판단 기준: 분위수도 distinct 처럼 "정확함이 정말 필요한가"를 먼저 물으세요. p95 지연 같은 지표는 근사로 충분하고, 잡이 수배 빨라집니다.
4. 그룹별 분위수
엔드포인트별·국가별 p95 처럼 그룹별 분위수가 흔합니다. percentile_approx 를 집계로 씁니다.
result = (df.groupBy("endpoint")
.agg(
F.percentile_approx("latency", 0.5, 10000).alias("p50"),
F.percentile_approx("latency", 0.95, 10000).alias("p95"),
F.percentile_approx("latency", 0.99, 10000).alias("p99"),
F.avg("latency").alias("avg")))세 번째 인자(accuracy)가 클수록 정확하고 메모리를 더 씁니다. 그룹이 많고 스큐하면 비용이 커지니(별도 글 "데이터 스큐"), 스큐 그룹을 점검하세요.
5. 이상치 탐지 — IQR
분위수의 대표 응용이 이상치 탐지입니다. IQR(사분위 범위) 로 정상 범위를 벗어난 값을 찾습니다.
# Q1, Q3 근사 계산
q1, q3 = df.approxQuantile("amount", [0.25, 0.75], 0.001)
iqr = q3 - q1
lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
# 이상치 플래그
flagged = df.withColumn("is_outlier",
(F.col("amount") < lower) | (F.col("amount") > upper))
outliers = flagged.filter("is_outlier")| 방법 | 기준 |
|---|---|
| IQR | Q1-1.5·IQR ~ Q3+1.5·IQR 벗어남 |
| Z-score | 평균에서 N 표준편차 밖 |
| 백분위 컷 | 상하위 1% 제거 |
Z-score 는 가산적 통계(평균·표준편차)만 쓰므로 더 싸지만, 분포가 정규가 아니면 IQR 이 견고합니다.
6. 상관·공분산, 요약 통계
여러 변수 간 관계나 전반적 분포를 빠르게 봅니다.
# 상관계수 / 공분산
df.stat.corr("price", "demand")
df.stat.cov("price", "demand")
# 요약 통계 (count, mean, stddev, min, max, 분위수)
df.summary("count", "mean", "stddev", "min", "25%", "50%", "75%", "max").show()
# 빈도 교차표 / 근사 빈발항목
df.stat.crosstab("device", "status")
df.stat.freqItems(["product_id"], support=0.01)summary() 는 EDA(탐색적 분석)에 유용하지만 분위수를 포함하므로 큰 데이터에서는 비쌀 수 있습니다 — 샘플에 적용하는 것도 방법입니다.
7. 샘플링으로 통계 비용 줄이기
전체가 아닌 대표 표본으로도 충분한 통계가 많습니다.
# 단순 무작위 표본
sample = df.sample(fraction=0.01, seed=42)
# 층화 표본 (그룹 비율 유지)
stratified = df.sampleBy("country",
fractions={"KR": 0.1, "US": 0.05, "JP": 0.1}, seed=42)
# 표본으로 분위수 추정
sample.approxQuantile("latency", [0.95], 0.001)분포 추정·EDA 단계에서는 1% 표본으로도 충분한 경우가 많아, 비용을 크게 줄입니다.
8. 정리
| 통계 | 도구 | 비용 |
|---|---|---|
| 평균·합·표준편차 | avg, sum, stddev | 싸다 |
| 정확 분위수 | percentile | 비싸다 |
| 근사 분위수 | approxQuantile/percentile_approx | 싸다(권장) |
| 그룹별 p95 | percentile_approx in agg | 중간 |
| 이상치 | IQR(approxQuantile) / Z-score | 중간/싸다 |
| 관계 | stat.corr, summary | 중간 |
대규모 통계 계산의 핵심 통찰은 "가산적 통계는 싸고, 순서가 필요한 통계(분위수)는 비싸다"는 구분입니다. p95·중앙값이 필요하면 정확한 percentile 대신 approxQuantile 을 기본으로 삼으세요 — 고정 메모리로 정렬 없이, 허용 오차 안에서 빠르게 답을 줍니다. 여기에 IQR 이상치 탐지와 샘플링을 더하면, 수십억 행의 통계 분석도 일정한 비용으로 처리할 수 있습니다.
이 글은 Spark 3.5 기준으로 작성되었습니다. 대규모 지표·통계 분석이나 이상치 탐지 파이프라인 설계가 필요하시면 언제든 문의해 주세요.
— Data Dynamics 엔지니어링 팀