분산 트레이싱이 끊기는 자리 · 지표와 추적이 같은 질문에 답하게 · 실습
지표와 추적이 같은 질문에 답하게 만든다
목표
스팬 덤프만으로 요청 수·오류 수·지연 분포를 세어 보고, 지표 쪽 노출 형식 파일과 견주어 이름과 값이 어긋난 자리를 찾아 계측을 맞춥니다. 표본 추출기를 바꿔 가며 같은 자료에서 서로 다른 오류율이 나오는 것을 직접 만들고 표본 확률의 역수로 보정한 뒤, 구간마다 대표 트레이스를 뽑는 다리를 만들고 규칙을 파일로 굳혀 두 번째 서비스에 적용합니다.
왜 중요한가
지표와 추적은 대개 따로 만들어진다. 지표 쪽은 프레임워크가 경로 틀을 넣어 주고 추적 쪽은 손으로 주소를 넣다 보니, 계열 아홉 개짜리 지표와 서른 개 넘는 스팬 묶음이 마주 보게 된다. 그 상태에서 '이 봉우리의 느린 요청 하나만 열어 보자' 고 하면 아무도 답하지 못한다. 더 나쁜 것은 스팬으로 비율을 세는 일이다 — 오류는 전부 남기고 성공은 다섯 건에 하나만 남기는 흔한 표본 정책 아래에서 스팬으로 센 오류율은 실제의 네 배 가까이 부풀고, 표본 확률을 스팬에 적어 두지 않았다면 되돌릴 방법조차 없다. 두 신호를 같은 이름과 같은 값으로 붙여 두고 확률을 함께 적어 두면, 개수와 비율은 지표에서 읽고 추적은 예를 찾는 데 쓰는 제 자리로 돌아간다. 지표 SDK 의 누적과 델타는 다른 모듈의 몫이고, 여기서 만드는 것은 속성 규칙 파일과 두 신호를 대조하는 검사기다.
단계
1. /root/tp-metrics/collect.py 를 만드세요(기본 덤프 경로 /root/tp-metrics/01-spans.jsonl). 재료 tracelab.tp_metrics.traffic 의 SHOP 을 차례로 처리하면서 요청마다 루트 스팬 GET <주소> 를 만들고, 스팬을 시작할 때 속성 다섯 개를 넘깁니다 — request.id·request.index·http.target(요청의 path)·http.response.status_code(요청의 status)·user.id(요청의 user). 스팬 안에서 traffic.work(tracer, req) 를 부르세요. 그다음 /root/tp-metrics/01-from-spans.tsv 에 탭으로 나눈 두 칸 네 줄을 적습니다 — requests<탭><루트 스팬 수>, errors<탭><상태 코드 500 이상인 루트 수>, p50_ms<탭><값>, p95_ms<탭><값>. 분위수는 루트 스팬의 duration_ms 를 오름차순으로 놓고 1부터 세어 올림(비율 × 개수) 번째 값을 고르며, 소수 셋째 자리까지 적습니다.
2. 지표 파이프라인이 내놓은 파일이 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 에 있습니다(Prometheus 노출 형식). /root/tp-metrics/02-mapping.tsv 에 탭으로 나눈 세 칸 세 줄을 적으세요 — 첫 칸은 지표 라벨로 순서대로 handler·code·svc, 둘째 칸은 1단계 덤프에서 그 라벨에 대응하려는 자리(http.target·http.response.status_code·service.name), 셋째 칸은 그 둘의 값이 그대로 맞는지 yes 또는 no 입니다. 그리고 /root/tp-metrics/02-gap.txt 에 두 줄을 적습니다 — metric_series= 뒤에 그 파일의 http_requests_total 계열 수, span_groups= 뒤에 1단계 덤프의 루트 스팬을 http.target 값으로 묶었을 때 나오는 묶음 수.
3. /root/tp-metrics/aligned.py 를 만드세요(기본 덤프 경로 /root/tp-metrics/03-aligned.jsonl). 1단계와 같은 트래픽을 처리하되 루트 스팬에 http.route(요청의 route, 곧 경로 틀)를 더해 넘기고, 스팬 이름도 GET <경로 틀> 로 둡니다. 조사에 쓰는 http.target 은 그대로 둡니다. 표본 추출기는 samplers.keep_all() 을 씁니다. 돌리고 나면 /root/tp-metrics/03-joined.tsv 에 탭으로 나눈 네 칸을 계열마다 한 줄씩 적으세요 — <handler><탭><code><탭><지표 값><탭><그 짝의 루트 스팬 수> 이고 handler 오름차순, 같으면 code 오름차순입니다. 두 숫자가 계열마다 같아야 합니다.
4. /root/tp-metrics/sampled.py 를 만드세요. 명령줄 인자로 nth5 또는 errbias 를 받아 각각 samplers.every_nth(5) 와 samplers.errors_and_nth(5) 를 표본 추출기로 쓰고, 나머지는 3단계와 똑같이 계측합니다. 기본 덤프 경로는 /root/tp-metrics/04-<인자>.jsonl 입니다. 두 번 돌려 /root/tp-metrics/04-nth5.jsonl 과 /root/tp-metrics/04-errbias.jsonl 을 만든 뒤, 3단계의 덤프까지 셋을 놓고 /root/tp-metrics/04-rates.tsv 에 탭으로 나눈 네 칸 세 줄을 적으세요 — 첫 칸은 순서대로 full·nth5·errbias(full 은 3단계 덤프입니다), 그다음은 <오류 루트 수><탭><전체 루트 수><탭><비율> 이고 비율은 소수 넷째 자리까지입니다.
5. 두 표본 덤프의 루트 스팬에는 sampling.probability 가 적혀 있습니다. 스팬 하나가 대표하는 건수는 그 값의 역수입니다. /root/tp-metrics/05-adjusted.tsv 에 탭으로 나눈 네 칸 두 줄을 적으세요 — 첫 칸은 순서대로 nth5·errbias, 그다음은 <보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율> 이고 앞 두 칸은 소수 넷째 자리까지, 비율도 소수 넷째 자리까지입니다. 그리고 /root/tp-metrics/05-limits.txt 에 두 줄을 적습니다 — limit1= 과 limit2= 뒤에 보정으로도 되돌릴 수 없는 것을 각각 40자 이상으로. 4단계의 full 비율과 견주어 보정이 어디까지 맞춰 주는지 확인하세요.
6. /root/tp-metrics/bridge.py 를 만드세요(기본 덤프 경로 /root/tp-metrics/06-bridge.jsonl). 3단계와 같이 계측해 덤프를 남긴 뒤, 그 덤프를 다시 읽어 계열마다 가장 느린 루트 스팬 한 건을 골라 표로 씁니다. 표의 경로는 덤프 경로의 .jsonl 을 .tsv 로 바꾼 자리입니다(기본이면 /root/tp-metrics/06-bridge.tsv). 한 줄은 탭으로 나눈 네 칸 <http.route><탭><상태 코드><탭><duration_ms(소수 셋째 자리)><탭><trace_id> 이고, http.route 오름차순·같으면 상태 코드 오름차순입니다. 그리고 /root/tp-metrics/06-limits.txt 에 limit1=·limit2= 두 줄로 이 다리가 답해 줄 수 없는 것을 각각 40자 이상 적으세요.
7. /root/tp-metrics/07-contract.tsv 에 탭으로 나눈 세 칸 일곱 줄을 적으세요. 첫 칸은 스팬 속성 이름으로 순서대로 http.route·http.response.status_code·service.name·http.target·request.id·user.id·sampling.probability 입니다. 둘째 칸은 그 속성이 지표에서 쓰는 라벨 이름이고, 지표에 두지 않는 것은 - 입니다. 셋째 칸은 both(두 신호에 같은 값으로 둔다) 또는 trace-only(추적에만 둔다) 입니다. 이 표는 3단계 덤프와 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 을 견주어 실제로 맞아야 합니다 — both 인 속성은 3단계 덤프의 루트 스팬에 모두 있어야 하고, trace-only 인 속성 이름은 지표 파일의 라벨로 나타나서는 안 됩니다.
8. /root/tp-metrics/pay.py 를 만드세요(기본 덤프 경로 /root/tp-metrics/08-pay.jsonl). 재료의 traffic.PAY 를 서비스 이름 pay-api 로 계측하되 7단계 규칙의 속성을 그대로 남기고, 루트 스팬 이름은 POST <경로 틀>, 표본 추출기는 samplers.keep_all() 입니다. 그리고 /root/tp-metrics/agree.py 를 만드세요 — python3 agree.py <스팬덤프> <노출형식파일> 로 돌리면 계열마다 지표 값과 루트 스팬 수를 견주어, 다른 계열마다 mismatch<탭><handler><탭><code><탭><지표 값><탭><스팬 수> 를 한 줄씩 찍고 종료 코드 1 로 끝나며, 전부 같으면 ok<탭><계열 수> 한 줄을 찍고 0 으로 끝납니다. 검사기를 /opt/app/tracelab/tp_metrics/metrics/pay-api.prom 에 돌린 출력을 /root/tp-metrics/08-agree.txt 에 저장하세요.
참고
- 작업 디렉터리는
/root/tp-metrics입니다. 없으면 먼저 만드세요. - 계측 프로그램은 반드시
/opt/otel-lab/bin/python <파일>로 돌립니다. 시스템python3에는 OpenTelemetry SDK 가 없습니다. 반대로 덤프와 지표 파일만 읽는 프로그램은 시스템python3로 돌리세요. - 재료는 되돌려 틀 트래픽
/opt/app/tracelab/tp_metrics/traffic.py(SHOP120건·PAY80건), 표본 추출기/opt/app/tracelab/tp_metrics/samplers.py, 지표 쪽 노출 형식 파일/opt/app/tracelab/tp_metrics/metrics/shop-api.prom·pay-api.prom·pay-api-broken.prom입니다. 그 파일들을 만든 생성기는/opt/app/tracelab/tp_metrics/make_metrics.py이고, 공용 배선은/opt/app/tracelab/dump.py, 덤프 읽기 도우미는/opt/lab/checks/_tplib.py입니다. - 이 이미지에는 Prometheus 가 없습니다. 지표는 노출 형식 텍스트 파일로 다루고 그것을 읽어 견주는 데까지가 이 실습의 범위이며, exemplar 를 실제로 저장하거나 조회하는 일은 여기서 할 수 없습니다.
- 흔한 실수: 표본 판정에 쓰이는 속성을
set_attribute로 나중에 붙이는 것. 표본 결정은 스팬이 시작될 때 나므로 그때 넘긴 속성만 추출기가 봅니다. - [HTTP 스팬 시맨틱 컨벤션](https://opentelemetry.io/docs/specs/semconv/http/http-spans/) · [HTTP 지표 시맨틱 컨벤션](https://opentelemetry.io/docs/specs/semconv/http/http-metrics/) · [확률 표본 추출과 adjusted count](https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/) · [Prometheus 노출 형식](https://prometheus.io/docs/instrumenting/exposition_formats/) · [Prometheus 이름과 라벨 짓기](https://prometheus.io/docs/practices/naming/)
단계 8개
- 스팬 덤프만으로 요청 수와 지연 분포를 세어 본다
- 지표 쪽 이름·값과 스팬 속성이 어긋난 자리를 찾는다
- 같은 이름·같은 값으로 두 신호를 붙인다
- 표본을 바꾸면 같은 자료에서 다른 오류율이 나온다
- 표본 확률의 역수로 보정해 다시 세고, 못 고치는 것을 적는다
- 구간마다 대표 트레이스를 뽑아 지표에서 건너갈 다리를 만든다
- 어떤 속성을 두 신호에 공통으로 둘지 규칙 파일로 굳힌다
- 두 번째 서비스에 규칙을 적용하고 검사기로 대조한다