TECH 으로 돌아가기
TECH HACKER NEWS 오늘 8분 읽기 32 READS

평균은 아무것도 말해주지 않는다: 시각화로 지연시간 문제를 파헤치기

성능 개선 작업을 하다 보면 벤치마크에서는 분명히 좋아졌는데 실제 운영 대시보드에서는 개선이 보이지 않는 답답한 상황을 만나곤 한다. 한 엔지니어는 링커(lld) 관련 성능 개선을 검증하던 중 정확히 이 문제에 부딪혔다. 웹 서비스 경험이 많았던 그는 몇 개의 백분위(percentile)를 얹은 시계열 대시보드를 보며 눈에 띄는 변화를 기대했지만, 데이터는 결론을 내리기엔 너무 노이즈가 심했다. 빌드 속도 개선을 평가하던 동료 역시 같은 벽에 부딪혔다. 콜드 캐시, 증분 빌드, 로컬과 원격 등 빌드 시간에 영향을 주는 변수가 너무 많아 값이 크게 출렁였기 때문이다. 그 동료가 찾아낸 돌파구가 바로 누적분포함수(CDF)를 이용한 시각화였고, 이 글은 하나의 합성 데이터셋을 여러 방식으로 그려보며 '단일 숫자 하나로는 데이터의 전체 이야기를 담을 수 없다'는 점을 보여준다.

같은 데이터, 정반대의 결론

시나리오는 이렇다. 어떤 웹 서비스가 요청 지연시간을 줄이려고 새 캐싱 계층을 일주일에 걸쳐 배포했다. 배포 완료 후 평균 지연시간 대시보드를 보니 112ms에서 122ms로 오히려 올라갔다. 여기서 흔한 반응은 장애(SEV)를 선언하고 변경을 롤백한 뒤 사후 분석을 쓰는 것이다. 그런데 꼬리 분포를 보기 위해 백분위를 나눠 보면 상황이 이상해진다. 평균은 소폭 악화라고 말하지만, 중앙값(p50)은 '대박 개선'이라고, 전형적인 요청이 거의 두 배 빨라졌다고 말한다. 반대로 p99는 최악의 요청이 두 배 이상 느려진 심각한 장애라고 말한다. 똑같은 숫자들에서 계산된 평균과 중앙값이 정반대 방향을 가리키는 것이다.

엔지니어는 데이터 기반으로 판단하라고 배우지만, 현실에서는 자기 주장에 맞는 통계를 골라 쓰기가 너무 쉽다. 이 모순의 원인은 분포의 모양을 그려보면 드러난다. '변경 전' 분포는 봉우리가 하나인 깔끔한 형태지만 '변경 후'는 봉우리가 둘인 이봉(bimodal) 분포다. 다만 밀도 그래프는 스무딩 파라미터에 따라 모양이 달라지고, 두 분포가 겹치는 구간에서 서로를 흐리며, 특정 백분위 값을 눈으로 읽기가 어렵다는 한계가 있다. 두 개의 집단이 있다는 건 보이지만, 중앙값이 어디로 갔는지는 잘 보이지 않는다.

CDF: 한 장으로 모든 백분위를 읽다

여기서 CDF가 빛을 발한다. CDF는 'x밀리초 이하로 끝난 요청이 전체의 몇 퍼센트인가'라는 질문에 모든 백분위에 대해 한꺼번에 답한다. 변경 전과 후의 CDF를 같은 차트에 겹쳐 그리면 어느 백분위에서 어떻게 이동했는지가 한눈에 보인다. 이 사례에서 '변경 후' 곡선은 140ms 아래 구간에서는 왼쪽으로 이동했다. 더 많은 요청이 이전보다 빨라졌다는 뜻이다. 반대로 140ms 오른쪽에서는 '변경 후' 곡선이 더 높아, 더 많은 요청이 느려졌음을 보여준다. 두 곡선은 약 140ms에서 교차하는데, 이 지점이 개선이 손해로 뒤바뀌는 전환점이다. 두 CDF가 교차한다는 것은 어떤 단일 백분위로도 요약할 수 없는 변화라는 명백한 신호다. 효과의 부호 자체가 어느 백분위를 보느냐에 따라 달라지기 때문이다. 여기에 각 백분위에서 '변경 후 지연시간 빼기 변경 전 지연시간'을 그리는 시프트 함수를 더하면, 지점마다 변화의 크기까지 정량적으로 읽을 수 있다.

시간과 조건으로 데이터를 쪼개기

지금까지는 배포 전후의 두 스냅숏만 봤지만, 실제 배포는 순간에 일어나지 않는다. 이 캐싱 계층은 일주일에 걸쳐 트래픽 0%에서 100%로 점진 확대됐다. 하루치 분포를 세로로 쌓는 리지라인(ridgeline) 그래프를 그리면, 배포가 진행될수록 빠른 요청의 주 봉우리가 왼쪽으로 미끄러지고 오른쪽에 느린 요청의 두 번째 봉우리가 서서히 솟아오르는 과정이 보인다. 중앙값은 내려가지만 느린 요청은 조용히 수와 지연시간을 키워가고 있었던 것이다. 참고로 지연시간은 대략 로그정규 분포를 따르므로 x축을 로그 스케일로 두어야 두 봉우리가 모두 봉우리로 읽힌다. 같은 데이터를 하루 한 열, 지연시간별 트래픽 양을 색으로 표현한 히트맵으로도 압축할 수 있다. 한 주 전체를 뭉뚱그린 어떤 집계값도 서로 다른 7일치를 하나의 탁한 숫자로 뭉개 이 추세를 완전히 숨겨버렸을 것이다.

무슨 일이 일어났는지 확인했다면 다음 질문은 '왜'다. 새 계층은 요청을 캐시에서 처리(히트)하거나, 추가 홉을 거쳐 백엔드로 넘긴다(미스). '변경 후' 요청을 이 속성으로 나눠 각각 CDF를 그리면 두 집단 모두 다시 단봉 분포로 돌아온다. 캐시 히트는 기존 기준선보다 왼쪽으로 이동해 더 빠르고, 캐시 미스는 추가 홉 비용을 치르며 훨씬 오른쪽에 자리한다. 다만 '일부 요청이 캐시를 놓친다'는 것은 메커니즘일 뿐 아직 원인은 아니다. 어떤 요청이, 왜 미스를 내는가.

근본 원인까지: 응답 크기라는 마지막 축

각 요청에는 아직 쓰지 않은 필드가 하나 더 있다. 바로 응답 크기다. 캐시는 작고 자주 쓰이는 객체를 담고, 큰 객체는 밀려나거나 애초에 들어가지 못한다. 지연시간을 응답 크기에 대해 산점도로 그리고 각 점을 캐시 히트/미스로 색칠하며 양쪽 축에 밀도를 덧붙인 조인트플롯(jointplot)을 그리면, 작고 빠른 집단(히트)과 크고 느린 집단(미스)이라는 두 개의 깔끔한 군집이 드러난다. 지연시간 분포의 이봉성은 응답 크기 분포의 이봉성에서 비롯된 것이다. 이제 캐시의 최대 객체 크기를 키우거나 큰 응답을 분할한다는, 실행 가능한 대응책이 나온다. 실제로 이 접근은 필자가 업무에서 바이너리 크기(예: 50MiB 초과)로 데이터를 잘라 지연시간 분포의 이봉성을 관찰했던 방식과 그대로 닮아 있다.

실무자에게 이 이야기가 주는 교훈은 분명하다. 하나의 패널이나 그래프는 전체 이야기를 담기엔 너무 작고, 최악의 경우 오해를 부른다. 특히 평균 하나만 보는 대시보드는 서로 다른 집단이 섞인 상황에서 위험하다. 성능 회귀를 판단할 때는 평균과 중앙값, 여러 백분위를 함께 놓고, CDF로 분포 전체를 겹쳐 비교하며, 시간축과 조건(캐시 히트/미스, 응답 크기 같은 요청 속성)으로 데이터를 쪼개 봐야 한다. 다만 이 글의 그림들은 고정 시드로 만든 하나의 합성 데이터셋에서 나온 것이고 데이터와 차트 생성에 AI가 활용됐다는 점은 감안할 필요가 있다. 그럼에도 핵심 메시지는 실제 운영 데이터에 그대로 적용된다. 요약된 숫자 하나를 믿기 전에 데이터를 직접 여러 각도에서 들여다보라는 것이다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://fzakaria.com/2026/07/27/the-mean-means-nothing
SHARE
처리 중...