LabHub
배우기 러닝패스 코스

OTCA — 오픈텔레메트리 인증 어소시에이트 · SDK 증거 추적: 사라진 스팬 찾기 · 이론

flush 성공과 수신 성공 사이

LabHub 에서 이어서 보기

한 줄 요약

계측은 호출 한 번이 아니라 여러 경계의 연속입니다. 예외를 기록했다는 것과 오류로
표시했다는 것, 스팬을 끝냈다는 것과 내보냈다는 것, 수신기가 받았다는 것과 저장소에서
조회할 수 있다는 것을 각각 확인해야 합니다.

왜 이게 필요했나

짧게 실행되는 주문 검증 작업이 있습니다. 작업은 오류를 잡아 응답으로 바꾸고,
끝에 force_flush를 호출한 뒤 성공 여부를 로그에 남깁니다. 로그에는 True가 찍혔지만
관측 화면에는 요청이 없습니다. 이 상황에서 “관측 화면이 늦다”라고 결론 내리면,
열린 스팬을 종료하지 않은 결함이나 전송이 거절된 사실을 놓칠 수 있습니다.

이 실습의 사전 실험에서는 실제 Python SDK 1.44.0과 공식 OTLP/HTTP 익스포터를
사용했습니다. 자체 수신기를 HTTP 200과 400으로 응답하게 만들어 비교했습니다.
익스포터의 결과는 SUCCESS와 FAILURE로 달랐지만 provider의 force_flush는 두 경우
모두 True였습니다. 아래 설명은 이 관측을 기반으로 하며 모든 언어·버전이 같은
반환값을 준다고 일반화하지 않습니다.

어떻게 동작하나

예외 이벤트와 오류 상태는 별개다

Python 코드에서 예외를 잡아 처리하면 그 예외는 with 블록 밖으로 전파되지 않을
수 있습니다. 이 경우 자동 문맥 관리가 무엇을 기록할 것이라고 추측하지 말고,
현재 스팬에 남긴 이벤트와 상태를 각각 확인하세요. 이번 실습은 start_span으로
만든 스팬에서 잡힌 예외를 처리하므로 자동 문맥 관리의 동작과 섞이지 않습니다.

from opentelemetry.trace import Status, StatusCodetry:    validate_order()except ValueError as error:    span.record_exception(error)    span.set_status(Status(StatusCode.ERROR, "order validation failed"))

record_exception은 조사할 이벤트를 남깁니다. ERROR는 이 작업을 어떤 상태로
분류할지 정합니다. 사전 실험에서 이벤트만 기록한 스팬은 UNSET 상태였고, 상태를
별도로 지정한 스팬만 ERROR였습니다. 이벤트 검색에서는 보이는데 오류 비율이나
상태 필터에서는 빠진다면 이 구분이 유용합니다.

모든 잡힌 예외가 반드시 작업 실패를 뜻하는 것은 아닙니다. 정상적인 대체 경로로
복구했다면 업무 의미에 맞는 상태를 선택해야 합니다. 이 과제에서는 주문 검증 실패를
최종 오류로 분류한다고 명시했기 때문에 ERROR를 요구합니다. SDK 호출 방법을
외우는 것과 어떤 업무 결과에 그 호출을 적용할지 판단하는 것은 다른 학습입니다.

span.end와 force_flush의 순서

배치 처리기는 끝난 스팬을 모아 내보냅니다. 아직 진행 중인 스팬의 정보를 마음대로
완결시켜 내보내면 실제 작업 시간이나 마지막 이벤트가 틀릴 수 있습니다. 따라서
열린 스팬을 둔 채 flush해도 그 스팬이 자동으로 종료되는 것은 아닙니다.

span.end()flush_result = provider.force_flush(timeout_millis=3000)

사전 실험에서는 열린 스팬을 둔 첫 flush가 True였지만 수신된 요청은 0개였습니다.
그 뒤 스팬을 끝내고 다시 flush하자 1개가 도착했습니다. 첫 반환값은 열린 작업을
완료했다는 증거가 아니었습니다. “내보낼 것이 없어서 끝남”과 “원하는 스팬을 보내고
끝남”을 구별하지 않으면 짧은 작업에서 데이터가 사라지는 원인을 놓칩니다.

force_flush는 평소 모든 스팬마다 무조건 부르는 함수로 외울 것이 아닙니다.
일반 서비스에서는 배치 처리를 활용하고, 짧은 실행이나 프로세스가 중단될 수 있는
경계에서 대기와 종료 정책을 설계합니다. 이 실습은 경계를 짧은 시간에 재현하려고
직접 flush를 사용합니다. 배치 처리의 성능 비교나 고부하 운영 지침을 검증한 것은
아닙니다.

다섯 개의 서로 다른 성공

| 관측 지점 | 그 사실로 말할 수 있는 것 | 아직 말할 수 없는 것 |
| --- | --- | --- |
| span.end 이후 종료 관측 | 스팬이 끝났음 | 전송됐음 |
| 익스포터 호출 | 내보내기를 시도했음 | 수신기가 수락했음 |
| 익스포터 SUCCESS | 해당 익스포터가 성공으로 처리했음 | 영속 저장·최종 조회 가능 |
| 수신기의 수락 기록 | 실험 수신기가 요청을 수락했음 | 다른 백엔드로 전달·저장됐음 |
| 대상 저장소에서 ID로 조회 | 그 저장소에서 해당 자료가 보임 | 모든 스팬이 빠짐없이 보존됐음 |

HTTP 400 실험에서는 요청 본문이 수신기까지 도착했습니다. 그래서 received의
스팬 목록만 세면 1개입니다. 하지만 수신기는 요청을 거절했으므로 accepted_spans는
0이고 익스포터 결과는 FAILURE입니다. 본문 도착을 성공으로 정의해 버리면 바로
이 반례를 놓칩니다. 실습에서 delivered 함수는 수신기의 수락을 판정하도록
요구하며, 영속 저장의 보증으로 사용하지 않습니다.

이 구분은 로그·메트릭·메시지 큐에도 적용되는 생각 방법입니다. 함수 반환,
로컬 큐 삽입, 네트워크 송신, 상대의 수락, 최종 처리 완료 중 어느 지점을 보고
성공이라고 했는지 먼저 적으세요. 시스템마다 반환값의 계약이 다르므로 단어가
같다는 이유만으로 성공의 범위를 옮겨 붙여서는 안 됩니다.

service.name은 어디에 넣나

스팬의 일반 속성에 service.name을 적는 것과 Resource의 service.name을 설정하는
것을 구별하세요. Resource는 그 신호를 만든 서비스를 설명합니다. 실습에서는
서로 다른 서비스 이름 두 개로 호출하므로, 예제의 문자열 하나를 코드에 고정하면
한 경우만 맞고 다른 경우는 틀립니다.

수신한 protobuf에서 service.name뿐 아니라 trace ID도 함께 대조합니다. 이름이
맞는 다른 요청을 현재 요청의 성공으로 오인하지 않기 위해서입니다. “화면에 뭔가
생겼다”는 확인을 “내가 방금 보낸 요청이 이 경계를 통과했다”는 확인으로 좁히는
연습입니다. 실제 서비스에서는 이때 쓰는 식별자의 보관·접근 권한도 관리해야 합니다.

현장에서 만나는 모습

배치 작업의 종료 직전에 관측 데이터가 사라지는 장애를 조사한다면 세 질문부터
시작합니다. 모든 스팬을 끝냈는가, 종료 경계에서 exporter가 실행될 기회가 있었는가,
그 시도의 결과와 수신 측의 결과가 무엇인가입니다. 단순히 기다리는 시간을 늘리는
것만으로 끝내지 말고 어느 경계의 자료가 달라지는지 비교해야 합니다.

또 하나의 함정은 시험 종료 정리입니다. 테스트의 finally에서 provider.shutdown을
부르면 누락됐던 데이터가 뒤늦게 전송될 수 있습니다. 그 자료를 학생 코드의 성공에
합쳐 세면, flush를 빠뜨린 코드도 통과합니다. 이번 채점기는 학생 함수 직후의 관측을
복사하고 그 뒤에 자원을 정리합니다. 정리 과정은 필요하지만 정답을 대신하면 안 됩니다.

다음 실습에서 할 것

전용 환경에 SDK와 익스포터가 미리 설치돼 있습니다. 외부 서비스나 API 키는
필요하지 않습니다. 여덟 단계에서 연결, 샘플링, 부모 정책, 예외, 종료, 수신 판정,
서비스 식별, 종합 보고서를 차례로 고칩니다. 각 파일을 run으로 실행하면 실제
관측과 실패 조건을 함께 볼 수 있습니다. 채점은 코드 사본으로 실행하고 현재
파일을 바꾸지 않으며, 앞 단계 준비도 이미 존재하는 부분 답안을 덮어쓰지 않습니다.

공식 기준: [Python 계측](https://opentelemetry.io/docs/languages/python/instrumentation/),
[OTLP 익스포터](https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html).