히스토그램의 점 하나에서 트레이스로 건너간다
목표
응답 시간 히스토그램에 붙은 exemplar 를 따라 느린 버킷에서 그 요청의 트레이스로 건너가고, 같은 부하를 명시적 버킷과 지수(exponential) 버킷으로 각각 받아 p99 를 얼마나 정확히 말할 수 있는지 견줍니다.
왜 중요한가
메트릭은 싸고 오래 남지만 요청 하나를 기억하지 않습니다. 「p99 가 튀었다」는 알려 주지만 「어느 요청이」는 모릅니다. exemplar 는 히스토그램의 버킷마다 실제 요청 하나의 trace_id 를 붙여 두는 장치입니다. 그래서 대시보드의 튄 점을 누르면 그 요청의 트레이스가 열립니다. 이 연결은 조용히 끊기기도 합니다. exemplar 필터를 끄거나, 활성 스팬 밖에서 값을 기록하면 메트릭은 멀쩡한데 트레이스로 가는 길만 사라집니다.
버킷 경계도 문제입니다. 명시적 버킷은 경계를 사람이 미리 정하므로, 실제 지연이 경계 사이 넓은 칸에 몰리면 p99 를 그 칸의 위 경계로밖에 말할 수 없습니다. 지수 히스토그램은 경계를 데이터에 맞춰 스스로 조정하고, Prometheus 의 native histogram 과 같은 경계 체계를 씁니다.
단계
- api 를
OTEL_DIR=/root/prof/otel-m2로 다시 띄우고-c 4 -d 12s -seed 2부하를 준 뒤 6초 기다리세요. 결과는/root/prof/runs/m2-explicit.json입니다. /root/prof/otel-m2/metrics.jsonl에서 값이 가장 큰 exemplar 를 찾아 값·trace_id(16진수)·들어가는 버킷의 위 경계를/root/prof/answers/exemplar.json에 적으세요.- 그 trace_id 의
GET /checkout스팬을otel-m2/spans.jsonl에서 찾아 길이와 쿠폰 여부를/root/prof/answers/exemplar-trace.json에 적으세요. OTEL_METRICS_EXEMPLAR_FILTER=always_off·OTEL_DIR=/root/prof/otel-off로 api 를 다시 띄우고-d 6s부하(/root/prof/runs/m2-off.json) 뒤 exemplar 개수를/root/prof/answers/exemplar-off.txt에 적으세요.CHECKOUT_HISTOGRAM=exponential·OTEL_DIR=/root/prof/otel-exp로 다시 띄우고 1단계와 같은 조건의 부하 결과를/root/prof/runs/m2-exp.json에 남기세요.- 두 히스토그램에서 p99 가 들어가는 버킷의 위 경계와,
otel-exp/spans.jsonl로 잰 실제 p99, 그리고 어느 쪽이 더 가까운지를/root/prof/answers/p99.json에 적으세요. - 지수 히스토그램을 Prometheus native histogram 으로 옮긴다면 schema 와 첫 버킷 번호가 무엇인지
/root/prof/answers/native.json에 적으세요.
참고
- 작업 자리는
/root/prof입니다.prof-workspace가 소스(src)를 복사하고 두 프로그램(bin/pricing·bin/loadgen)을 빌드합니다. 여러 번 불러도 이미 있는 것은 덮지 않습니다. - 서비스는
prof-svc up pricing·prof-svc up api로 띄우고prof-svc status로 봅니다.up은 떠 있던 것을 내리고 다시 띄우므로, 앞에 적은 환경 변수가 그때 반영됩니다. - 부하는
loadgen입니다.-c(동시성)·-d(시간)·-coupon(쿠폰 비율)·-big(큰 주문 비율)·-seed를 받고,-out파일에 조건과 결과를 함께 적습니다. - 이 파드는 네트워크가 막혀 있습니다. 필요한 것은 전부 이미지에 들어 있습니다.
- 메트릭 파일은 5초마다 한 줄씩 늘어납니다. 값은 누적(cumulative)이라 가장 많이 센 줄이 전체입니다.
- SDK 의 JSON 은 exemplar 의 trace_id 를 10진 정수로 적습니다.
format(정수, '032x')로 16진수로 바꿔야 스팬과 맞습니다. - 지수 버킷 i 의 경계는
base = 2 ** (2 ** -scale)일 때(base ** (offset+i), base ** (offset+i+1)]입니다.
명시적 버킷으로 받는다
pricing 이 떠 있는 상태에서 OTEL_DIR=/root/prof/otel-m2 prof-svc up api 로 api 를 다시 띄우고 loadgen -url http://127.0.0.1:8080/checkout -c 4 -d 12s -seed 2 -out /root/prof/runs/m2-explicit.json 뒤 6초 기다리세요.
메트릭은 5초마다 내보냅니다. 부하가 끝나자마자 보면 마지막 몇 초가 빠져 있습니다. 기다리는 일도 측정의 일부입니다.
가장 높은 버킷의 exemplar
otel-m2/metrics.jsonl 의 checkout.duration 데이터 포인트에 붙은 exemplar 가운데 값이 가장 큰 것을 골라 value_ms·trace_id(16진수 32자리)·bucket_le(그 값이 들어가는 버킷의 위 경계)를 /root/prof/answers/exemplar.json 에 적으세요.
exemplar 는 데이터 포인트의 exemplars 배열에 있습니다. trace_id 가 10진 정수로 적혀 있으니 16진수로 바꿉니다. 위 경계는 explicit_bounds 가운데 값 이상인 첫 경계입니다.
exemplar 를 따라 트레이스로
그 trace_id 의 GET /checkout 스팬을 otel-m2/spans.jsonl 에서 찾아 trace_id·server_ms(스팬 길이)·coupon(속성 checkout.coupon, true/false)을 /root/prof/answers/exemplar-trace.json 에 적으세요.
스팬 파일의 trace_id 는 0x 가 붙은 16진수입니다. 메트릭의 점과 트레이스는 이 ID 하나로만 이어집니다.
연결이 조용히 끊기는 경우
OTEL_METRICS_EXEMPLAR_FILTER=always_off OTEL_DIR=/root/prof/otel-off prof-svc up api 로 다시 띄우고 -d 6s 부하를 /root/prof/runs/m2-off.json 에 남긴 뒤 6초 기다려, otel-off/metrics.jsonl 의 exemplar 개수를 /root/prof/answers/exemplar-off.txt 에 숫자로 적으세요.
필터는 SDK 표준 환경 변수입니다. 기본값 trace_based 는 「샘플링된 스팬 안에서 기록한 값」에만 exemplar 를 붙입니다. 끄면 히스토그램은 그대로 쌓입니다.
지수 히스토그램으로 같은 부하를 받는다
CHECKOUT_HISTOGRAM=exponential OTEL_DIR=/root/prof/otel-exp prof-svc up api 로 다시 띄우고 1단계와 같은 -c 4 -d 12s -seed 2 부하의 결과를 /root/prof/runs/m2-exp.json 에 남긴 뒤 6초 기다리세요.
두 히스토그램을 견주려면 조건이 같아야 합니다. 채점기는 두 결과 파일의 동시성·시간·비율·시드가 같은지 봅니다. 앞 단계의 필터 변수는 이번 명령에 적지 않습니다.
p99 를 얼마나 정확히 말할 수 있나
두 메트릭 파일에서 각각 가장 많이 센 200 응답 데이터 포인트로 p99 가 들어가는 버킷의 위 경계(explicit_le·exponential_le)를, otel-exp/spans.jsonl 의 GET /checkout 길이로 실제 p99(true_ms)를 구하고, 실제에 더 가까운 쪽(closer: explicit 또는 exponential)과 함께 /root/prof/answers/p99.json 에 적으세요.
p99 의 순위는 ceil(0.99 × count) 입니다. 버킷을 앞에서부터 누적해 그 순위에 닿는 버킷의 위 경계를 씁니다. 지수 히스토그램은 zero_count 를 먼저 더하고 positive 의 offset 과 scale 로 경계를 계산합니다.
Prometheus native histogram 으로 옮긴다면
6단계에서 쓴 지수 데이터 포인트를 Prometheus native histogram 으로 옮긴다고 할 때 schema 와 첫 양수 버킷의 Prometheus 번호(first_index)를 /root/prof/answers/native.json 에 적으세요.
Prometheus 문서는 schema 가 OTel 의 scale 과 같고, 같은 버킷을 Prometheus 가 OTel 보다 번호 하나 크게 센다고 적습니다. 경계 체계는 같고 번호 매김만 하나 어긋납니다.