한 요청의 구간 추적
90분 안팎
학습 목표
트레이스 ID와 span의 연결을 읽습니다.
개념
전체 지연을 작업 구간으로 나눕니다
오류율이 늘었다는 집계만으로 안내판 API의 파일 읽기가 실패했는지 프록시에서 요청이 막혔는지 알 수 없습니다. 트레이스는 요청 안에서 수행한 작업을 span으로 나누고 원인 관계를 보존합니다. 이 레슨에서는 API 처리와 store.fixture라는 저장소 주입 구간을 같은 trace_id로 묶습니다. 자식의 오류 상태와 소요 시간을 읽어 조사할 구간을 선택합니다. 실제 원격 데이터베이스를 진단했다고 말하지 않고 어느 경계까지 관측했는지 설명하는 것이 목표입니다.
trace와 span은 서로 다른 식별자입니다
trace_id는 한 흐름을 묶고 span_id는 그 안의 작업 하나를 구분합니다. parent_span_id는 바로 위 작업을 가리킵니다. 정상·오류·지연 요청은 각각 별도 trace이며 한 trace 안의 API와 store는 다른 span ID를 사용합니다. 같은 trace_id만 남기면 작업들이 같은 흐름이라는 사실은 알지만 어떤 작업이 누구를 호출했는지는 사라집니다. span_id를 전체 흐름에서 똑같이 쓰면 두 작업을 구별할 수 없습니다. ID의 모양을 외우는 대신 연결에 필요한 세 관계를 확인합니다.
요청 ID와 트레이스 ID를 연결합니다
요청 ID는 앞 레슨의 검색 키이며 trace ID와 같은 값일 필요가 없습니다. 실습은 span attributes에 request_id를 넣어 요청 로그와 조인합니다. 미션 Java 필터는 로그에 trace_id와 현재 API span_id를 기록합니다. 그러면 사용자 신고 ID로 로그를 찾고 trace ID로 두 구간을 선택할 수 있습니다. 같은 시각에 실행됐다는 이유만으로 다른 trace의 저장소 구간을 가져오지 않습니다. 여러 요청이 동시에 처리되는 환경에서 시간 가까움보다 명시적 맥락 전파가 중요한 이유입니다.
HTTP 경계에서는 맥락을 전달합니다
프로세스가 나뉘면 메모리의 현재 span이 자동으로 옮겨가지 않습니다. W3C traceparent는 버전·trace ID·상위 span ID·flags를 전달하는 표준 헤더입니다. 미션은 version 00의 제한된 입력을 검사하고 비정상 형식이나 모두 0인 ID는 새 맥락으로 대체합니다. 이것은 표준 전체를 구현한 propagator가 아닙니다. 일반 서비스에서는 OpenTelemetry 라이브러리로 추출과 주입을 처리합니다. 헤더는 인증 자격증명이 아니고 외부가 보낸 맥락도 신뢰할 수 없는 입력이라는 점을 유지합니다.
수집기의 범위를 정확히 부릅니다
허용 이미지 안에서 실습하기 위해 Python으로 OTLP/HTTP JSON의 일부를 받는 교육용 수집기를 제공합니다. 공식 OpenTelemetry Collector 바이너리나 Java SDK를 설치한 실습이 아닙니다. service.py는 수동 span 모델을 만들고 receiver.py는 허용한 필드를 JSONL로 저장합니다. resourceSpans 안의 scopeSpans와 spans 구조, /v1/traces POST를 관찰합니다. gzip·gRPC·배치·재시도·전체 프로토콜 호환성은 지원하지 않습니다. 표준의 핵심 경계를 재현하는 모델과 운영용 수집 제품을 혼동하지 않습니다.
OTLP JSON의 표현을 확인합니다
traceId는 32자리, spanId와 parentSpanId는 16자리의 16진 표현입니다. 이 실습은 소문자만 허용하는 더 좁은 계약입니다. 상태 enum은 정수 code이며 1은 OK, 2는 ERROR, 0은 UNSET입니다. 시작과 끝의 Unix 나노초는 문자열로 직렬화합니다. JSON 키는 lowerCamelCase를 씁니다. Python 수신기는 span 이름과 request_id를 검증하고 token 같은 불필요 속성을 저장하지 않습니다. 브라우저에 보일 이름을 자유 텍스트로 받으면 비밀을 옮길 수 있어 정한 이름 목록으로 제한합니다.
수동 계측이 시간을 재는 위치입니다
service.py의 fixture는 API 시작 시각을 찍고 저장소 주입 함수 앞뒤를 잰 뒤 API 종료 시각을 기록합니다. slow는 실제 150ms sleep을 실행하고 error는 저장소 결과를 실패로 표시합니다. 이는 파일이나 네트워크 지연을 실제로 재현한 데이터베이스가 아니라 정한 결과를 주입한 함수입니다. 미션의 /notices store.read는 실제 파일 생성 여부 확인과 읽기를 둘러쌉니다. /fixture/slow의 store.fixture는 실제 300ms 대기를 감쌉니다. 계측의 이름과 실제 수행한 작업을 일치시킵니다.
부모 시간과 자식 시간을 더하지 않습니다
API span은 자식 호출 시간을 이미 포함합니다. API 200ms, store 150ms라면 두 값을 더한 350ms가 요청 전체 시간은 아닙니다. 자식이 차지한 비중을 보고 남은 구간을 추가 조사합니다. 자식 둘이 병렬 실행되면 각 시간을 합한 값도 부모와 일치하지 않을 수 있습니다. 이 실습은 동기 중첩이라 부모 시작이 자식 시작보다 빠르고 자식 끝이 부모 끝을 넘지 않아야 합니다. 이 경계 검사는 비동기 모든 시스템의 절대 규칙이 아니라 제공한 호출 모델의 계약입니다.
오류 상태는 느림과 다릅니다
slow 요청은 오래 걸려도 서버 결과가 정상이라 span 상태가 OK입니다. error 요청은 짧게 끝나도 ERROR입니다. 클라이언트가 100ms에 포기하면 서버는 300ms 작업을 완료하고 OK를 남길 수 있습니다. 미션에서 실제 timeout 표본과 서버 span을 나란히 읽어 이 차이를 확인합니다. 클라이언트 timeout이면 모든 자식 span이 ERROR여야 한다고 기대하지 않습니다. 상태를 기록하는 주체와 시간 경계를 먼저 적고 사용자 관측과 내부 처리 결과의 차이를 장애 조사 근거로 사용합니다.
외부 검사와 모델 검사를 구분합니다
python3 -B check.py는 메모리 안에서 span 생성·허용 필드 선택·부모 연결·오류 상태·지연을 검사합니다. bash check.sh는 Docker 네트워크에서 API와 수집기를 실제 실행하여 HTTP 전송과 JSONL 저장까지 확인합니다. 자동 검증기의 PENDING은 후자의 실행이 남았다는 표시이며 통과 실측을 뜻하지 않습니다. starter는 수신 parentSpanId를 빈 값으로 저장하여 normal broken parent에서 실패합니다. 정상 trace가 빠져도 검사를 통과하도록 입력을 줄이지 않고 수신 연결을 보존합니다.
수집 누락은 작업 부재가 아닙니다
span이 없으면 해당 작업이 실행되지 않았다고 바로 결론내리지 않습니다. 계측이 빠졌거나 exporter가 실패했거나 sampling으로 보관하지 않았을 수 있습니다. 이 모델은 sampling 없이 요청마다 두 span을 요구하므로 missing spans는 수집 경로 또는 모델 결함을 찾게 합니다. collector connection refused는 대상 주소와 컨테이너 네트워크를 확인하고 broken parent는 JSON 필드의 보존을 확인합니다. 실제 환경에서는 수집 실패와 drop 수를 함께 관측해야 정상처럼 보이는 빈 트레이스를 구분할 수 있습니다.
수집된 정보에도 접근 범위가 필요합니다
트레이스 속성에 토큰·이메일·전체 SQL 문장·파일 내용이 들어가면 로그 제외 정책만으로 보호할 수 없습니다. receiver는 request_id만 속성에서 골라 저장하고 예외 메시지도 받지 않습니다. 이 수집기는 개인 Docker 네트워크에서만 사용하며 인증·TLS가 없습니다. 외부 공개 수집 주소로 적용하지 않습니다. 실습 종료 후 evidence를 보관하고 생성한 컨테이너의 이름·프로젝트 라벨을 확인하여 자기 자원만 정리합니다. 실제 실행 image ID는 evidence에 남기고 이미지 tag를 digest라고 부르지 않습니다.
실패 한 건을 설명하는 제출물입니다
오류 request_id에서 로그 한 줄과 같은 trace_id의 API·store span을 고릅니다. store의 parent_span_id가 API span_id와 같은지, 오류 상태가 어떤 구간에 남는지, 시작과 끝이 어떤 경계인지를 적습니다. 지연 fixture와 비교하여 실패와 느림이 다른 질문임도 설명합니다. 제출에는 실제 외부 실행 여부와 수집기의 부분 구현 범위를 남깁니다. 구간별 지연 예산의 일반 원리는 더 읽기로 이어가며 이번 결과는 제공된 안내판 함수와 저장소 fixture까지로 결론을 제한합니다.
표준 구조 참고: OpenTelemetry OTLP 명세, 맥락 전파 공식 문서. 실습은 제한된 교육 모델입니다.
따라하기
부모 연결과 중첩 시간 읽기
숫자는 구조 이해용 가상 값이며 실측 성능이 아닙니다. 같은 흐름이면서 store의 부모가 API인지 확인합니다.
api={"trace":"t1","span":"a1","start":0,"end":200}
store={"trace":"t1","span":"s1","parent":"a1","start":20,"end":170}
print("same trace",api["trace"]==store["trace"])
print("parent matches",store["parent"]==api["span"])
print("API ms",api["end"]-api["start"])
print("store ms",store["end"]-store["start"])실행 결과
same trace True parent matches True API ms 200 store ms 150
수신 연결 실패를 찾기
starter.zip 루트에서 모델 검사합니다. normal broken parent가 나오면 receiver.py의 parent_span_id 필드가 parentSpanId를 보존하는지 확인합니다. 이 명령은 Docker 전송을 확인하지 않습니다.
python3 -B check.py모델의 정상·오류·지연 확인
solution.zip 루트에서 다음 명령을 실행합니다. 다음 출력은 작성 환경에서 모델을 직접 실행한 결과입니다. 실제 HTTP 수집 결과와 구분합니다.
python3 -B check.py실행 결과
PASS normal API -> store PASS error API -> store PASS slow API -> store PASS allowed fields, no token or email PASS 4 trace checks PASS reject zero-trace PASS reject short-span PASS reject reverse-time PASS reject string-status PASS reject private-name PASS 5 trace input contracts
개인 Docker 네트워크에서 전송 검증
Docker가 가능한 별도 환경에서 프로젝트 이름을 자기 이름으로 바꿉니다. check.sh는 실제 API 응답·수집기 전송·spans.jsonl 저장·부모 연결을 확인합니다. evidence/image-id.txt와 생성 자원의 라벨을 확인합니다. external 실행 대기여서 출력은 제공하지 않습니다.
bash check.sh bc-yourname-trace확인 문제
실습
receiver.py에서 parentSpanId를 parent_span_id로 보존합니다. python3 -B check.py는 모델 확인입니다. Docker에서 bash check.sh bc-yourname-trace를 실행하여 두 컨테이너의 실제 전송과 저장을 검사합니다. API→store의 같은 trace ID·다른 span ID·부모 연결·중첩 시간·오류 상태·실제 sleep·민감 속성 제외가 기준입니다. Python OTLP/HTTP JSON 부분 수신기이며 공식 Collector나 SDK 자동 계측이 아닙니다. 실행 검증은 external 대기이고 README에 자기 자원 보존·정리 절차가 있습니다.
실행 명령
BOOTCAMP_AUTOCLEAN=1 bash check.sh
기대 결과
정상·오류·지연 실제 HTTP 요청의 수집 JSONL과 API/store 부모 연결·정보 제외를 확인합니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 지표·로그·트레이스로 확인하는 정보를 설명합니다.