TT Lab
블로그 시작하기
배우기 러닝패스 코스

프로파일링: 느린 요청 하나를 함수까지

메트릭의 점에서 트레이스로: exemplar 와 지수 히스토그램

TT Lab 에서 이어서 보기

한 줄 요약

exemplar 는 히스토그램 버킷에 실제 요청 하나의 trace_id 를 붙여 두는 장치이고, 지수(exponential) 히스토그램은 버킷 경계를 데이터에 맞춰 스스로 정하는 히스토그램입니다. 앞의 것은 메트릭에서 트레이스로 건너가는 다리이고, 뒤의 것은 p99 를 얼마나 정확히 말할 수 있는지를 정합니다.

왜 이게 필요했나

메트릭은 요청을 잊어버리도록 설계되어 있습니다. 백만 건의 응답 시간을 버킷 열댓 개의 숫자로 접기 때문에 싸고 오래 보관할 수 있습니다. 그 대신 「p99 가 튄 그 순간의 요청이 무엇이었나」에는 답하지 못합니다. 예전에는 같은 시각의 로그를 grep 하거나, 트레이스 저장소에서 시간 범위로 느린 것을 찾아 짐작했습니다. 운이 좋으면 맞고 아니면 엉뚱한 요청을 붙잡습니다.

버킷 경계도 문제였습니다. 명시적(explicit) 버킷은 사람이 경계를 미리 정합니다. OpenTelemetry SDK 의 기본 경계는 0, 5, 10, 25, 50, 75, 100, 250, 500 처럼 뒤로 갈수록 넓어집니다. 지연이 100 과 250 사이에 몰리면 p99 를 「250 이하」로밖에 말할 수 없습니다. 실제 p99 가 110 이어도 그렇습니다.

어떻게 동작하나

exemplar. SDK 는 히스토그램에 값을 기록하는 순간 활성 스팬이 있고 그 스팬이 샘플링되었으면, 그 값과 trace_id·span_id 를 버킷 옆에 하나 붙여 둡니다(OpenTelemetry Python SDK 의 기본 필터 trace_based). 그래서 값을 활성 스팬 안에서 기록해야 합니다. 스팬을 닫은 뒤에 기록하면 메트릭은 그대로 쌓이는데 exemplar 만 사라집니다. 환경 변수 OTEL_METRICS_EXEMPLAR_FILTER 를 always_off 로 두어도 같습니다. 어느 쪽이든 오류는 없습니다.

지수 히스토그램. 경계를 base = 2 ** (2 ** -scale) 의 거듭제곱으로 둡니다. scale 이 3 이면 이웃 경계의 비가 2 의 8제곱근(약 1.09)이라, 어느 값이든 9% 안의 칸에 들어갑니다. SDK 는 버킷 수 상한(기본 160)을 넘지 않는 가장 큰 scale 을 스스로 고릅니다. 데이터의 범위가 넓으면 scale 을 낮춰 칸을 넓힙니다.

Prometheus 의 native histogram 은 같은 경계 체계를 씁니다. Prometheus 문서는 schema 가 OTel 의 scale 과 같고, 같은 버킷을 Prometheus 가 번호 하나 크게 센다고 적습니다(OTel 의 n 번 = Prometheus 의 n+1 번). native histogram 은 Prometheus 3.8.0 에서 안정 기능이 되었고, 3.9 부터는 스크랩 설정 scrape_native_histograms 로 켭니다.

현장에서 만나는 모습

Grafana 에서 히스토그램 패널에 exemplar 를 켜면 그래프 위에 점이 찍히고, 점을 누르면 트레이스가 열립니다. 이 기능이 「어느 날부터 점이 안 보인다」는 문의로 돌아오는 일이 많습니다. 원인은 대개 셋 중 하나입니다. 기록 위치가 스팬 밖으로 옮겨졌거나, 필터 설정이 바뀌었거나, 수집 경로 어딘가에서 exemplar 를 버리고 있습니다(Prometheus 는 OpenMetrics 형식으로 긁어야 exemplar 를 받습니다).

흔히 틀리는 읽기

「exemplar 가 가리키는 요청이 곧 그 버킷의 대표다」라고 읽기 쉽습니다. SDK 의 기본 저장소는 버킷마다 exemplar 를 하나만, 대개 그 내보내기 주기에 들어온 것 가운데 하나를 남깁니다. 그러니 그 요청은 「그 칸에 들어간 요청의 한 예」이지 평균적인 요청이 아닙니다. 원인을 말하려면 그 예에서 출발해 트레이스와 프로파일로 같은 종류의 요청을 더 모아야 합니다.

히스토그램 파일을 읽을 때도 함정이 있습니다. 누적(cumulative) 모드에서는 매 내보내기가 처음부터의 합계라, 줄마다 개수를 더하면 같은 요청을 여러 번 세게 됩니다. 이 실습에서 「가장 많이 센 줄」을 쓰는 이유입니다. 그리고 exemplar 는 줄마다 그 주기의 것만 실리므로, 부하가 끝난 뒤의 마지막 줄에는 exemplar 가 비어 있을 수 있습니다.

다음 실습에서 할 것

같은 부하를 명시적 버킷과 지수 버킷으로 각각 받고, 가장 높은 버킷의 exemplar 를 따라 그 요청의 트레이스를 엽니다. 필터를 꺼서 연결이 조용히 사라지는 것을 보고, 두 히스토그램의 p99 위 경계를 실제 p99 와 견줍니다.

English corner