“우리 평균 응답 200ms 인데요”. 그런데 사용자 한 명Slack 으로 “왜 *오늘 결제 가 *10 초씩 걸리냐” 한다.

둘 다 사실. 평균 이 200ms 라는 것99 명 이 *50ms 인데 1 명 이 *15,000ms 라는 극단 적 분포수학적 으로 동일 할 수 있다.

백엔드 서버 개발자책임평균 을 보는 것아니라 *분포 를 보는 것. 그리고 *그 분포 의 *long tail왜 길어졌는지추적 가능 하게 만드는 것 — 그 도구 가 APM.

이 글은 9 년차 백엔드 개발자 의 시각 에서 APM 의 역할, 응답 시간 분포 의 수학, 분위수 통계 의 함정, 실전 도구 비교, Spring Boot 에 *바로 붙이는 코드, 내 실전 사고 사례밀도 있게 정리한다.

함께 보면 좋은 자매편 :

이 글은 그 위에 *분포 의 수학 + APM 의 도구 지형덧 댄다. 대시보드 위 의 *분위수 그래프왜 그렇게 생겼는지완전히 풀어 본다.


TL;DR — 한 줄 결론

평균백엔드 의 *진짜 SLA체계적으로 가린다. p95 / p99 / p99.9 만이 사용자 가 체감하는 시간정직한 표현. 그 분포 를 *정확히 측정 하려면 HDR Histogram / t-digest 같은 분위수 자료 구조 가 필요하고, 그 분포 를 *추적 가능 하게 만들려면 Trace ID + Span 의 *분산 트레이싱 (OpenTelemetry) 이 필요. *Spring Boot + Micrometer + OTel agent + Prometheus + Grafana / Jaeger오픈소스 스택지금 의 표준 답안. 상용 (Datadog/New Relic) 은 비용 vs 통합 시야 의 trade-off.


1. 왜 *평균거짓말 의 표준 형태 인가**

1.1 *평균 의 *수학적 정의 가 *백엔드 와 안 맞는다**

평균 = 모든 응답 시간 의 합 / 요청 수.

이 정의 의 조용한 가정모든 응답 시간 이 *대칭 적 정규 분포 를 따른다. 그래야 *평균 = 중앙값 = 최빈값.

현실 의 응답 시간 :

  • 대다수 (95~99%)수십 ms좁고 빠른 봉우리
  • 나머지 (1~5%)수백 ms ~ 수십 초긴 꼬리

수학적 으로 *log-normal 또는 Pareto 분포. 오른쪽 으로 *심하게 치우친 (right-skewed) *비대칭.

이런 분포 에서 산술 평균long tail 의 *극단 값무방비 로 끌려간다. 1 % 의 15 초 요청평균 을 *수백 ms 단위왜곡.

1.2 백엔드 의 *long tail 의 *발생원 6 가지**

9 년 의 경험 으로 꼬리 를 길게 만드는 *주범 * :

  1. GC pauseJava 의 *Stop-The-World. G1 의 Mixed GC 가 *수십 ~ 수백 ms. ZGC 도 *드물게 *수 ms.
  2. DB connection 대기HikariCP 풀 고갈대기 큐밀린 요청 의 *합산 지연.
  3. 외부 API 호출PG / 카카오톡 / SMS / OCR간헐 적 *수 초 지연. circuit breaker 미적용 시 *전체 풀 점거.
  4. N+1 쿼리평소 1 쿼리 인데 *특정 데이터 의 *400 쿼리 폭발.
  5. Cold startJIT 미온, *클래스 로딩, *connection 풀 prewarm 안 됨, Caffeine miss.
  6. Lock contentionDB 의 *row lock, Java 의 *synchronized, 분산 락 의 *재시도.

이 6 가지 중 어느 것평소 에는 *문제 없음. 문제 가 생기는 순간특정 요청수십 배 의 지연. 평균 으로 보면 *조용히 묻힘.

1.3 유명 한 *Amazon 의 *50ms 의 1 %

Amazon 의 *2007 년 *기록 (Greg Linden) — 추가 100ms 지연 마다 *매출 1% 감소. 이 데이터 의 *원천분위수 측정 도구 였다. 평균 으로 봤다면 *그 인과 가 *영원히 안 보였다.

우리 의 백엔드 도 같다. p99 의 *200ms 차이체감 만족 의 *결정 적 차이. 평균 이 *말해주지 않는 진실.


2. *분위수 (Percentile) — *분포 의 *정직한 언어**

2.1 *정의 와 *한 줄 의미**

분위수 의미 백엔드 적 해석
p50 (중앙값) 요청 의 절반 이 이 시간 안 에 응답 전형 적 사용자 의 체감
p95 95 % 의 요청 이 이 시간 안 조금 늦은 사용자 의 체감
p99 99 % 가 이 시간 안. 100 명 중 1 명 불만 게시판 의 체감
p99.9 1,000 명 중 1 명. *long tail 의 *심장. 서비스 의 *진짜 SLA 기준
p99.99 10,000 명 중 1 명. *극단 적 사용자. 고가용성 서비스 의 SRE 영역

2.2 *p99 가 *제일 정직한 *백엔드 SLA**

9 년차 경험 으로 — 모든 백엔드 SLA 는 *p99 기준 으로 작성 해야 한다.

이유 :

  • p95long tail 의 *너무 많이 숨김. 5 % 의 *나쁜 경험그대로 묻힘.
  • p99균형 점. 99 % 가 행복 한 SLA = *대부분 사용자 행복. 그 1 % 는 *내가 *식별 가능 / 사과 가능.
  • p99.9극단 적 정확성. 측정 의 *통계 적 노이즈크다. 수십만 건 데이터 필요.

내 settlement 의 *결제 API SLA = *p99 < 500ms. p95 가 아니라 *p99. p95 로 두면 *5 % 의 *느린 결제 가 *조용히 누적 됨.

2.3 *분위수 의 *수학적 *조심 할 점**

분위수 는 *평균낼 수 없다

서비스 A 의 p99 = 200ms
서비스 B 의 p99 = 300ms
→ A+B 합쳐서 p99 = ???  ← 250ms 아님!!!

분위수 합산 의 진실원본 분포 가 필요. 합쳐서 *p99 가 *200~500ms 사이 어디 든 가능. Grafana 의 *평균 패널p99 를 평균 내면 통계적 으로 의미 없음. 반드시 *HDR Histogram 의 *원본 버킷 합 으로 재계산*.

분위수 는 *시간 별로 *나눌 수 없다

1 분간 p99 = 200ms
2 분간 p99 = 300ms
→ 2 분 합쳐서 p99 = ???  ← 250ms 아님!!!

같은 이유. 반드시 *히스토그램 의 합산 후 재계산*.

이게 Prometheus 의 histogram_quantile() 함수진짜 의미. 분위수 자체 가 아니라 *히스토그램 버킷 을 저장하고 조회 시 점에 *분위수 계산. 이렇게 해야 *합산 / 시간 윈도우 가능.

2.4 *Coordinated Omission — *측정 의 *조용한 거짓말**

Gil Tene 이 명명한 분위수 측정 의 *치명 적 함정.

전형 적 시나리오 :

  • 부하 테스트 가 *RPS 100 *유지 시도.
  • 서버 가 *느려져서 *응답 이 *10 초 늦어짐.
  • 부하 테스트 클라이언트 는 *그 10 초 동안 *새 요청 을 *덜 보냄 (왜냐하면 바쁨).
  • 결과 — *측정 안 된 *수백 개 의 *늦은 요청통계 에서 누락.

해결시작 시간 (intended start time) 을 *기준 으로 측정. 기다린 시간 까지 포함. 이게 *wrk2 / HdrHistogram / Gatling 의 *지원 이유.

JMeter 의 *기본 측정 이 *coordinated omission취약. p99 가 *실제보다 *극단 적으로 낙관 적. 부하 테스트 결과 신뢰 할 때 *반드시 확인.


3. 분포 를 *제대로 저장 하는 자료 구조*

“매 요청 의 응답 시간 을 다 저장”디스크 폭발. 수십억 건 요청 의 *수십 GB 데이터.

그래서 *근사 압축 자료 구조 가 필요하다. 4 가지 후보 :

3.1 *naive 평균 + 표준편차 — *제일 흔하고 *제일 쓸모 없는**

장점 : 메모리 상수. 단점 : 분위수 추론 불가. 분포 모양 모름. 대부분 의 *Prometheus 기본 메트릭 이 이렇게 잘못 됐다.

3.2 Histogram (고정 버킷)

[0~10ms]   [10~50ms]   [50~100ms]   ...   [10s~]
   count       count       count               count

장점 :

  • 합산 가능 (버킷 단위 더하기).
  • 시간 윈도우 합산 가능.
  • Prometheus 의 histogram_quantile() 가 *이걸 가정.

단점 :

  • 버킷 경계 가 *고정p99 의 정확도 가 *낮음. 적은 버킷 = 큰 오차.
  • 너무 많은 버킷 = 메모리 폭발.

3.3 HDR Histogram (High Dynamic Range)

Gil Tene 의 *고급 자료 구조. 지수 적 버킷 + 정밀도 보장.

1 µs ~ 1 분 의 *7 자릿수 범위* 에서 *0.1 % 의 *상대 오차 보장*.
메모리 = 수십 KB.

장점 :

  • 극단 적 정확.
  • 합산 가능.
  • Java 에 *공식 라이브러리. Micrometer 도 *옵션 적 지원.

단점 :

  • 합산 시 *값 범위 가 다르면 *주의.
  • 시각화 도구 의 *직접 지원 적음 (보통 분위수로 변환 후 송신).

3.4 t-digest

Ted Dunning 의 *분위수 추정 알고리즘.

장점 :

  • 극단 분위수 (p99.9, p99.99) 의 *상대 적 정확도 *매우 높음.
  • 합산 가능.
  • Datadog / Elasticsearch / Cassandra 등 다수 채택.

단점 :

  • 알고리즘 의 *수학적 복잡도.
  • Java 의 *공식 라이브러리 가 *상대 적으로 적음.

3.5 선택 지침

상황 권장
Prometheus + Grafana 기본 Histogram (Timer, histogram_quantile)
정밀한 p99.9비즈니스 SLA HDR Histogram
Datadog / Elastic APM t-digest (도구 가 자동)
그냥 대시보드 의 *p99 라인 Histogram 충분

4. APM 의 *3 가지 축 — *Metric / Trace / Log**

APM 은 추적 가능 한 관측우산 용어. 3 가지 *데이터 형 의 통합 :

4.1 *Metric — *집계 된 *시계열**

  • 수십억 요청수치 합산 으로 압축. 분위수 / 처리량 / 에러율 / GC time.
  • 시각화 — *Grafana, Datadog 대시보드.
  • 질문 — *“지난 1 시간 의 p99 가 어땠지?”

4.2 *Trace — *한 요청 의 *Hop 별 *시간 분해**

  • HTTP 요청 1 건내부 5 개 마이크로서비스 + 3 개 DB + 2 개 외부 API돌아 다닐 때, 각 Hop 의 시간한 묶음 으로 추적.
  • 시각화 — *Jaeger UI 의 *waterfall, Datadog APM 의 *flame graph.
  • 질문 — *“이 요청 이 *왜 *3 초 걸렸나?”

4.3 *Log — *문맥 적 *정확한 사건**

  • 비즈니스 로직 의 *흔적. 예외 의 *스택 트레이스.
  • 시각화 — *Kibana / Loki / Splunk.
  • 질문 — *“왜 *이 사용자 의 *이 요청 이 *실패 했나?”

3 가지 의 *통합 의 핵심Trace ID 의 일관성. Metric 의 *p99 가 튄 시점 에서 그 시간대 의 *느린 trace 한 건 을 찾고, 그 trace 의 *각 span로그 를 *문맥 적으로 추적.

이게 Datadog / New Relic 의 *비싼 값진짜 정체셋 의 *자동 연결.


5. *APM 도구 지형 — *6 가지 옵션 비교**

5.1 *국내 산 — *Pinpoint / Scouter**

  Pinpoint Scouter
출신 Naver (오픈소스) LG CNS → 오픈소스
데이터 저장 HBase 자체 file storage
강점 호출 흐름 의 *깊은 시각화, 대용량 분산 경량, 빠른 셋업, JVM 지표 풍부
약점 셋업 복잡 (HBase + Zookeeper) 시각화 노후
적합 대형 SI 의 수십 ~ 수백 인스턴스 중소 규모 의 *빠른 도입

5.2 *글로벌 상용 — *New Relic / Datadog / Dynatrace**

  New Relic Datadog Dynatrace
가격 호스트 / 사용량 기반 호스트 + 데이터 양 (악명 ↑) 프리미엄 (가장 비쌈)
강점 통합 UX, 분산 트레이싱 의 *원조 클라우드 통합, 알림 의 풍부함 AI 자동 인사이트 (Davis)
Spring Boot 통합 에이전트 첨부 → 즉시 에이전트 + Micrometer 변환 OneAgent 자동 계측

5.3 *오픈소스 — *OpenTelemetry 기반**

2026 년 의 *대세. 벤더 락인 탈피 + 표준 SDK.

Spring Boot 앱
  ↓ (Micrometer / OTel SDK)
OTel Collector  ← 통합 변환 / 샘플링 / 라우팅
  ↓
+----------------+------------------+-------------+
|                |                  |             |
Prometheus     Tempo / Jaeger     Loki         (선택적으로 Datadog 등)
(메트릭)         (트레이스)         (로그)
  ↓                ↓                ↓
              Grafana — 통합 시각화

장점Prometheus + Grafana + Loki + TempoGrafana 스택 으로 3 종 통합. 벤더 락인 없음.

단점셋업 의 부하 (특히 *retention / 샘플링 / *알람 의 *직접 구성). *Datadog 의 *바로 됨 에 비하면 학습 곡선.

내 K3s 홈랩 패턴Prometheus + Grafana + Loki + Tempo + OTel Collector. 완전 무료 *셀프 호스팅. 셋업 *수십 시간 이지만 연 *수백만 원 절약.

5.4 Elastic APM

ELK 스택 의 일부. Elasticsearch 가 이미 있다면 *합리적. 고유 한 *trace + log 의 *동일 데이터 베이스 결합 이 강점.


6. *Spring Boot 에 *APM 붙이기 — *실제 코드**

6.1 Micrometer 의 *Timer

@RestController
@RequiredArgsConstructor
class PaymentController {
    private final MeterRegistry meterRegistry;
    private final PaymentService paymentService;
    private final Timer paymentTimer;

    public PaymentController(MeterRegistry registry, PaymentService svc) {
        this.meterRegistry = registry;
        this.paymentService = svc;
        this.paymentTimer = Timer.builder("payment.request")
            .description("Payment API latency")
            .publishPercentiles(0.5, 0.95, 0.99, 0.999)   // ← 분위수 명시
            .publishPercentileHistogram()                   // ← Prometheus 용 버킷
            .serviceLevelObjectives(
                Duration.ofMillis(100),
                Duration.ofMillis(500),
                Duration.ofMillis(1000)
            )                                               // ← SLO 추적
            .register(registry);
    }

    @PostMapping("/pay")
    public PaymentResult pay(@RequestBody PaymentRequest req) {
        return paymentTimer.record(() -> paymentService.execute(req));
    }
}

핵심 :

  • publishPercentilesMicrometer 내장 *분위수 계산.
  • publishPercentileHistogramPrometheus 호환 *히스토그램 버킷 생성 (histogram_quantile() 사용 가능).
  • serviceLevelObjectivesSLO 추적 용 *boolean 카운터. Prometheus 알람 의 *기반.

6.2 *application.yml 의 *전역 설정**

management:
  endpoints:
    web:
      exposure:
        include: prometheus,health,metrics
  metrics:
    distribution:
      percentiles-histogram:
        http.server.requests: true
      percentiles:
        http.server.requests: 0.5,0.95,0.99,0.999
      slo:
        http.server.requests: 100ms,500ms,1s
    tags:
      application: ${spring.application.name}
      env: ${ENV:dev}

이 한 블록 으로 모든 컨트롤러 메서드 의 *분위수 + 히스토그램 + SLO 자동 생성.

6.3 *OpenTelemetry Java Agent — *코드 0 줄 의 *분산 트레이싱**

# 다운로드
$ curl -L -o opentelemetry-javaagent.jar \
  https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

# 실행
$ java -javaagent:./opentelemetry-javaagent.jar \
       -Dotel.service.name=payment-service \
       -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
       -jar payment.jar

결과Spring MVC / WebClient / JDBC / Redis / Kafka 의 *호출전부 자동 으로 *span 으로 캡처. 코드 한 줄 안 바꿈. 마법 의 이름 은 *bytecode 변환.

6.4 *Prometheus 쿼리 — *p99 의 *시각화**

# HTTP 요청 의 p99 (5 분 윈도우)
histogram_quantile(0.99,
  sum by (le, uri) (
    rate(http_server_requests_seconds_bucket{
      application="payment-service",
      uri!~".*/actuator.*"
    }[5m])
  )
)

# SLO 위반율 — 500ms 초과 비율
1 - (
  sum(rate(http_server_requests_seconds_bucket{le="0.5"}[5m]))
  /
  sum(rate(http_server_requests_seconds_count[5m]))
)

이 두 쿼리 만으로 Grafana 의 *핵심 두 패널p99 라인 + SLO 위반율 — 완성.


7. 내 *9 년 경험분포 가 *진짜 사고 를 잡은 5 가지 사례*

7.1 *settlement 의 *p99 가 *야간 에 만 *3 초**

평균 응답 180ms 평소. 야간 (02~04 시)p99 만 *3 초로 *튐.

평균 만 봤다면전혀 안 보였다. p99 차트 가 *시간대 별 패턴드러냄.

원인 — 야간 의 *배치 잡 의 *DB 락 점유. 결제 트랜잭션 이 *밀려서 *대기. batch 의 *작은 트랜잭션 단위 화 로 해결.

7.2 *sparta-msa 의 *p99.9 만 *튀는 GC 사고**

p50 15ms, p95 40ms, p99 80ms건강 해 보임. p99.9 2,300ms수상.

GC 로그 와 시간 대 일치. G1 의 *Mixed GC 가 *주기 적 2 초 pause.

해결 — ZGC 로 변경 + Heap 크기 조정. p99.9 가 *200ms 이하 로 *극적 감소.

p99 까지 만 봤다면 *영원히 못 잡았던 사고. p99.9 의 가치.

7.3 *외부 PG API 의 *간헐 적 *10 초 지연**

p99 400ms. p99.9 11,000ms.

trace 추적 — *대부분 의 *long tail 이 *PG 의 *카드 인증 API 응답 대기.

해결 — circuit breaker (Resilience4j) + fallback. 10 초 이상 시 *바로 *retry-later 응답.

7.4 *N+1 의 *p99 폭주**

신규 정산 화면p99 가 *주말 에 만 *15 초.

trace 본 결과한 요청 이 *DB 470 회 호출. 전형 적 N+1.

해결 — JPA fetch join + DTO projection. p99 가 *200ms 로 떨어짐.

trace 의 *flame graph 가 *없었다면 *코드 어디서 N+1 인지 영원히 추측. APM 의 *진짜 가치 는 *이 시각화.

7.5 *cold start 의 *startup probe 실패**

배포 직후 5 분 동안 *p99 가 *4 초. 5 분 뒤 *정상.

원인 — JIT 미 워밍업 + connection 풀 init + Caffeine miss.

해결 :

  • Spring Boot 의 *@WarmUp 작업. application context init 시 *주요 쿼리 *프리 콜.
  • Kubernetes 의 *startupProbe + readinessProbe 의 *충분한 *initialDelaySeconds.
  • AOT 컴파일 (GraalVM) 검토.

p99 의 시간 패턴 (배포 직후 만 튐)원인 진단 의 *결정 적 단서.


8. *알람 의 *철학 — *분포 기반 의 *5 가지 규칙**

평균 기반 알람 의 3 가지 흔한 오류 :

  1. 평균 응답 > 500ms 알람long tail 이 *평균 을 *왜곡. 오발 + 누락 양쪽.
  2. 에러율 > 1 % 알람백 엔드 의 *5xx 만 봄. 4xx 의 사용자 불만 + 200 OK 의 *느린 응답 누락*.
  3. 동시 접속 > N 알람부하 의 *상한 만 봄. 분포 의 *나쁜 꼬리 가 *원인 일 때 *무관.

대신 분포 기반 *5 가지 규칙 :

  1. SLO 위반율 > 0.5 %p99 SLO (예: 500ms) 초과 비율 의 *long-window burn rate. Google SRE 의 *공식 패턴.
  2. p99.9 의 급격한 변화전 주 동일 시간 대비 *2 배 이상 튐 → 알람. 베이스라인 비교.
  3. 에러율 의 복합 정의5xx + 4xx 의 일부 (예: 429, 408) + 200 의 *임계 초과 응답** 의 합 = *체감 에러.
  4. 분위수 간 갭 의 갑작스러운 확장p50 은 정상 인데 *p99 가 튀면 *분포 가 *비대칭 화. long tail 의 *새 원인 발생.
  5. trace 의 느린 span 의 *원인 분포지난 1 시간 의 *p99 trace 들 의 *공통 원인 (DB / 외부 API / GC). 주범 의 변화 감지.

이 5 가지 가 분포 기반 SRE 의 *근간.


9. *결론 — *백엔드 개발자 의 *3 가지 의식 변화**

9.1 “평균 응답” 이라는 단어 를 입에서 지우자

매주 보고 / Slack / 회의 — “평균 응답 200ms 입니다” 라는 말 자체 를 p99 응답 200ms 입니다 로 바꾸자.

조직 의 *언어 가 바뀌면 *사고 의 *기준 이 바뀐다. *기준 이 바뀌면 *알람 / 대시보드 / SLO전부 따라 옴.

9.2 *대시보드 의 *기본 그래프 4 개**

Grafana 첫 페이지고정 패널:

  1. RPS (요청 률)처리량 의 베이스
  2. p50 / p95 / p99 라인 (한 차트 에 3 줄)분포 의 변화
  3. 에러율 (5xx + 임계 초과)사용자 의 *진짜 *실패 율
  4. SLO 위반율 의 *burn rate** — *알람 의 기반

이 4 개 만 벽 에 띄워 두면 *서비스 의 *건강 의 *80 % 가 보임.

9.3 *trace 의 *습관 화**

p99 튀면 *바로 *그 시간대 의 *느린 trace 한 건Jaeger 에서 열어 *flame graph 를 본다.

5 분 의 *습관디버깅 시간 의 *수 시간 단축. trace 가 *추측 을 *증거 로 바꾼다.


다음 으로 *권 하는 읽기**

  • Google SRE Book — *Chapter 4 (Service Level Objectives). 분포 기반 SLO 의 *교과서.
  • Gil Tene 의 *“How NOT to Measure Latency” 강의. *Coordinated Omission 의 *진수.
  • OpenTelemetry 공식 문서Java 의 *자동 + 수동 계측 의 *모든 패턴.
  • Brendan Gregg 의 *“Systems Performance” 2 nd ed.. *USE / RED 의 *원작자.
  • 자매편 — 내 이전 글 Prometheus + Grafana 메트릭 시각화Backend Latency 추적.

다음 글분산 트레이싱 의 *내부 — *Trace ID 의 전파 / 샘플링 의 수학 / OpenTelemetry Collector 의 라우팅3 부 시리즈 — 곧.