让指标和追踪回答同一个问题
한국어 원문으로 표시합니다.
목표
스팬 덤프만으로 요청 수·오류 수·지연 분포를 세어 보고, 지표 쪽 노출 형식 파일과 견주어 이름과 값이 어긋난 자리를 찾아 계측을 맞춥니다. 표본 추출기를 바꿔 가며 같은 자료에서 서로 다른 오류율이 나오는 것을 직접 만들고 표본 확률의 역수로 보정한 뒤, 구간마다 대표 트레이스를 뽑는 다리를 만들고 규칙을 파일로 굳혀 두 번째 서비스에 적용합니다.
왜 중요한가
지표와 추적은 대개 따로 만들어진다. 지표 쪽은 프레임워크가 경로 틀을 넣어 주고 추적 쪽은 손으로 주소를 넣다 보니, 계열 아홉 개짜리 지표와 서른 개 넘는 스팬 묶음이 마주 보게 된다. 그 상태에서 '이 봉우리의 느린 요청 하나만 열어 보자' 고 하면 아무도 답하지 못한다. 더 나쁜 것은 스팬으로 비율을 세는 일이다 — 오류는 전부 남기고 성공은 다섯 건에 하나만 남기는 흔한 표본 정책 아래에서 스팬으로 센 오류율은 실제의 네 배 가까이 부풀고, 표본 확률을 스팬에 적어 두지 않았다면 되돌릴 방법조차 없다. 두 신호를 같은 이름과 같은 값으로 붙여 두고 확률을 함께 적어 두면, 개수와 비율은 지표에서 읽고 추적은 예를 찾는 데 쓰는 제 자리로 돌아간다. 지표 SDK 의 누적과 델타는 다른 모듈의 몫이고, 여기서 만드는 것은 속성 규칙 파일과 두 신호를 대조하는 검사기다.
단계
/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부터 세어올림(비율 × 개수)번째 값을 고르며, 소수 셋째 자리까지 적습니다.- 지표 파이프라인이 내놓은 파일이
/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값으로 묶었을 때 나오는 묶음 수. /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 오름차순입니다. 두 숫자가 계열마다 같아야 합니다./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단계 덤프입니다), 그다음은<오류 루트 수><탭><전체 루트 수><탭><비율>이고 비율은 소수 넷째 자리까지입니다.- 두 표본 덤프의 루트 스팬에는
sampling.probability가 적혀 있습니다. 스팬 하나가 대표하는 건수는 그 값의 역수입니다./root/tp-metrics/05-adjusted.tsv에 탭으로 나눈 네 칸 두 줄을 적으세요 — 첫 칸은 순서대로nth5·errbias, 그다음은<보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율>이고 앞 두 칸은 소수 넷째 자리까지, 비율도 소수 넷째 자리까지입니다. 그리고/root/tp-metrics/05-limits.txt에 두 줄을 적습니다 —limit1=과limit2=뒤에 보정으로도 되돌릴 수 없는 것을 각각 40자 이상으로. 4단계의full비율과 견주어 보정이 어디까지 맞춰 주는지 확인하세요. /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자 이상 적으세요./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인 속성 이름은 지표 파일의 라벨로 나타나서는 안 됩니다./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 스팬 시맨틱 컨벤션 · HTTP 지표 시맨틱 컨벤션 · 확률 표본 추출과 adjusted count · Prometheus 노출 형식 · Prometheus 이름과 라벨 짓기
스팬 덤프만으로 요청 수와 지연 분포를 세어 본다
/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부터 세어 올림(비율 × 개수) 번째 값을 고르며, 소수 셋째 자리까지 적습니다.
자식 스팬(db.query)도 덤프에 들어가므로 요청을 셀 때는 parent_id 가 없는 줄만 세야 합니다. 분위수는 math.ceil(0.95 * n) - 1 번째 칸(0부터 세는 파이썬 색인)입니다. 걸린 시간은 기계에 따라 조금씩 다르므로 채점기는 여러분의 덤프를 다시 읽어 같은 방법으로 계산해 견줍니다 — 절대 수치를 맞히는 것이 아닙니다. 덤프를 다시 만들기 전에 파일을 지우세요.
지표 쪽 이름·값과 스팬 속성이 어긋난 자리를 찾는다
지표 파이프라인이 내놓은 파일이 /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 값으로 묶었을 때 나오는 묶음 수.
노출 형식의 한 줄은 이름{라벨="값",...} 값 이고 # 으로 시작하는 줄은 설명입니다. 두 숫자를 나란히 놓고 보면 왜 짝지을 수 없는지가 한눈에 보입니다 — 한쪽은 손으로 셀 수 있는 수이고 다른 쪽은 주소마다 하나씩 불어납니다. service.name 은 스팬의 attributes 가 아니라 resource 에 들어 있습니다.
같은 이름·같은 값으로 두 신호를 붙인다
/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 오름차순입니다. 두 숫자가 계열마다 같아야 합니다.
지표는 라벨 handler 와 code 로 계열을 가르고, 스팬은 속성 http.route 와 http.response.status_code 로 묶입니다. 상태 코드는 지표에서 문자열이고 스팬에서 정수이므로 짝지을 때 한쪽으로 맞춰야 합니다. keep_all() 을 쓰면 모든 루트 스팬에 sampling.probability 가 1.0 으로 적히는데, 5단계에서 그 값을 쓰게 됩니다.
표본을 바꾸면 같은 자료에서 다른 오류율이 나온다
/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단계 덤프입니다), 그다음은 <오류 루트 수><탭><전체 루트 수><탭><비율> 이고 비율은 소수 넷째 자리까지입니다.
표본 추출기는 request.index 와 http.response.status_code 를 보고 결정하므로, 그 두 속성을 스팬을 시작할 때 넘기지 않으면 추출기가 아무것도 보지 못합니다. 세 비율 가운데 하나만 크게 튈 것입니다 — 어느 것이 왜 튀는지 생각해 보세요. 덤프는 이어 붙으므로 돌리기 전에 지우세요.
표본 확률의 역수로 보정해 다시 세고, 못 고치는 것을 적는다
두 표본 덤프의 루트 스팬에는 sampling.probability 가 적혀 있습니다. 스팬 하나가 대표하는 건수는 그 값의 역수입니다. /root/tp-metrics/05-adjusted.tsv 에 탭으로 나눈 네 칸 두 줄을 적으세요 — 첫 칸은 순서대로 nth5·errbias, 그다음은 <보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율> 이고 앞 두 칸은 소수 넷째 자리까지, 비율도 소수 넷째 자리까지입니다. 그리고 /root/tp-metrics/05-limits.txt 에 두 줄을 적습니다 — limit1= 과 limit2= 뒤에 보정으로도 되돌릴 수 없는 것을 각각 40자 이상으로. 4단계의 full 비율과 견주어 보정이 어디까지 맞춰 주는지 확인하세요.
보정한 수는 루트 스팬마다 1 / sampling.probability 를 더한 값입니다. 자식 스팬에는 확률이 적혀 있지 않으니 자연히 루트만 세게 됩니다. errbias 쪽은 보정하면 4단계의 full 비율에 아주 가까워지고 nth5 쪽은 원래도 그리 멀지 않았을 것입니다. 되돌릴 수 없는 것을 생각할 때는 '표본에 한 건도 안 들어온 조합' 과 '분위수' 를 떠올려 보세요.
구간마다 대표 트레이스를 뽑아 지표에서 건너갈 다리를 만든다
/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자 이상 적으세요.
표의 경로를 덤프 경로에서 끌어내는 이유가 있습니다 — 채점기가 여러분의 프로그램을 자기 임시 디렉터리에서 한 번 더 돌리는데, 표를 고정된 경로에 쓰면 여러분이 낸 파일을 덮어쓰게 됩니다. 대표를 고르는 일 자체는 표준에서 exemplar 라고 부르지만, 이 환경에는 Prometheus 가 없어 실제로 저장하거나 조회할 수는 없습니다. 한계를 적을 때는 '보통의 요청' 과 '표본에서 빠진 요청' 을 떠올려 보세요.
어떤 속성을 두 신호에 공통으로 둘지 규칙 파일로 굳힌다
/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 인 속성 이름은 지표 파일의 라벨로 나타나서는 안 됩니다.
갈라내는 기준은 카디널리티입니다. 지표의 라벨은 값의 가짓수가 계열 수를 그대로 곱하므로 주소나 사용자 식별자를 넣으면 계열이 폭발합니다. 반대로 추적은 요청 하나를 찾아내는 일이 목적이라 그런 값이 거기 있어야 합니다. service.name 은 스팬의 resource 에 들어 있는데, 그래도 both 입니다 — 두 신호를 서비스 단위로 묶는 열쇠이기 때문입니다.
두 번째 서비스에 규칙을 적용하고 검사기로 대조한다
/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 에 저장하세요.
검사기는 otel 이 필요 없으므로 시스템 python3 로 돌아가게 쓰세요. 지표 쪽에만 있는 계열과 스팬 쪽에만 있는 계열이 모두 어긋남이므로 두 열쇠 집합의 합집합을 돌아야 합니다. 채점기는 여러분의 검사기를 일부러 틀리게 적어 둔 /opt/app/tracelab/tp_metrics/metrics/pay-api-broken.prom 에도 돌려 보므로, 파일 이름이나 특정 값으로 판정하면 안 됩니다.