OpenTelemetry로 LLM 요청 Trace 연결하기
메뉴
Moonshot Notes orbit notebook mark
Moonshot NotesAI 도구와 개발 워크플로우 기록하는 공간

AI Backend

OpenTelemetry로 LLM 요청 Trace 연결하기

LLM 서비스에서 OpenTelemetry를 사용해 API 요청, retrieval, LLM 호출, validation, DB 저장을 하나의 trace로 연결하고 지연과 실패 원인을 분석하는 방법을 정리합니다.

OpenTelemetry로 LLM 요청 Trace 연결하기 hero image
Markdown약 1914 tokens

API, RAG, 모델 호출, 응답 검증을 하나의 흐름으로 보기

이 글에서는 OpenTelemetry를 사용해 LLM 서비스의 요청 흐름을 trace로 연결하는 방법을 정리합니다.

LLM 서비스의 장애 분석은 일반 API보다 어렵습니다. 한 요청 안에 검색, 모델 호출, 응답 검증, 저장, 캐시, 외부 provider 호출이 섞여 있기 때문입니다. 사용자는 “답변이 느리다”고 말하지만, 실제 원인은 vector search일 수도 있고, 모델 호출일 수도 있고, schema validation 재시도일 수도 있습니다.

그래서 trace가 필요합니다.

분석 기준일: 2026-05-12
실습 기준 환경: FastAPI, OpenTelemetry, PostgreSQL, Redis, LLM Provider API
주요 참고자료: OpenTelemetry Docs, W3C Trace Context, Google SRE

핵심 요약

  • OpenTelemetry는 traces, metrics, logs를 생성·수집·내보내기 위한 관측성 프레임워크다.
  • LLM 요청은 API, retrieval, LLM call, validation, DB save를 span으로 나눠야 한다.
  • trace_id는 로그, metric, eval result와 연결되어야 한다.
  • prompt 원문과 문서 전문은 span attribute에 넣지 않는다.
  • p95/p99 latency, error rate, token usage를 trace와 함께 봐야 한다.

1. 왜 LLM 서비스에 trace가 필요한가

LLM 요청은 여러 하위 작업으로 나뉩니다.

# 예시입니다.POST /answers→ authenticate→ rate limit check→ retrieval→ prompt build→ llm call→ schema validation→ db save→ response

전체 응답 시간이 8초라고 할 때 어디서 시간이 걸렸는지 모르면 개선할 수 없습니다.

병목 위치가능한 원인
retrievalvector index, metadata filter, DB 부하
prompt buildcontext 과다, token 계산
llm callprovider 지연, rate limit, output 길이
validationschema 실패, 재시도
db saveconnection pool, transaction 지연

Trace는 이 흐름을 span 단위로 나눠 보여줍니다.

2. OpenTelemetry 기본 개념

개념설명
Trace하나의 요청 전체 흐름
Spantrace 안의 개별 작업 단위
Attributespan에 붙는 key-value metadata
Metric시간에 따른 수치 데이터
Log개별 이벤트 기록
Collectortelemetry 수집·가공·전송 컴포넌트

LLM 서비스에서는 trace와 token usage metric을 연결하는 것이 중요합니다.

3. LLM 요청 span 설계

추천 span 구조:

# 예시입니다.answer.create├── auth.check├── rate_limit.check├── retrieval.search│   └── vector.query├── prompt.build├── llm.call├── output.validate└── answer.save

각 span에는 duration과 error 여부가 기록됩니다.

4. Trace ID 전파

외부에서 들어온 traceparent header가 있으면 이어받고, 없으면 새 trace를 만듭니다.

# 예시입니다.traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

응답에도 request_id나 trace_id를 포함하면 장애 신고를 받을 때 추적이 쉬워집니다.

// 예시 JSON 구조입니다.{  "answer": "...",  "trace_id": "0af7651916cd43dd8448eb211c80319c"}

5. Span Attribute 설계

좋은 attribute는 분석에 도움이 되면서 민감정보를 포함하지 않아야 합니다.

Attribute예시주의
llm.provideropenaiOK
llm.modelconfigured-modelOK
llm.prompt_versionanswer.v3OK
llm.input_tokens3200OK
llm.output_tokens800OK
rag.top_k5OK
rag.document_scopeofficial_docsOK
user.question원문 질문넣지 않기
prompt.full전체 prompt넣지 않기
document.contentchunk 전문넣지 않기

6. 민감정보와 보안

보안 주의
OpenTelemetry attribute와 log에는 prompt 원문, 문서 전문, 개인정보, API key, access token을 넣지 않아야 합니다. 관측성 데이터도 외부 시스템으로 전송될 수 있으므로 보안 등급을 별도로 관리해야 합니다.

대신 hash나 길이, category를 기록합니다.

# 예시입니다.question_hashprompt_versioncontext_token_countretrieved_chunk_countdocument_scope

7. Metrics와 Logs 연결

Trace만으로는 전체 경향을 보기 어렵습니다. Metrics와 Logs도 함께 설계해야 합니다.

Signal역할
Trace한 요청의 상세 흐름
Metric전체 경향과 알림
Log이벤트와 디버깅 정보

예시 metric:

# 예시입니다.llm_request_duration_msllm_provider_latency_msllm_schema_validation_fail_totalrag_retrieval_latency_msrag_empty_result_totalllm_input_tokens_totalllm_output_tokens_total

로그에는 trace_id를 반드시 포함합니다.

// 예시 JSON 구조입니다.{  "level": "ERROR",  "message": "LLM schema validation failed",  "trace_id": "0af...",  "prompt_version": "answer.v3",  "error_code": "SCHEMA_VALIDATION_FAILED"}

8. Dashboard에서 볼 것

초기 dashboard는 복잡할 필요가 없습니다.

# 예시입니다.[ ] 요청 수[ ] p50/p95/p99 latency[ ] error rate[ ] provider latency[ ] schema validation failure rate[ ] retrieval latency[ ] token usage[ ] cache hit rate[ ] eval pass rate

Google SRE의 four golden signals인 latency, traffic, errors, saturation을 LLM 서비스에 맞게 확장하면 됩니다.

9. FastAPI 예제 구조

# 예시 코드입니다.from opentelemetry import trace tracer = trace.get_tracer(__name__) # 이 선언은 예시 흐름을 보여줍니다.async def create_answer(req):    with tracer.start_as_current_span("answer.create") as span:        span.set_attribute("llm.prompt_version", "answer.v3")         with tracer.start_as_current_span("retrieval.search") as s:            chunks = await retrieve(req.question)            s.set_attribute("rag.top_k", len(chunks))         with tracer.start_as_current_span("llm.call") as s:            result = await call_llm(req, chunks)            s.set_attribute("llm.input_tokens", result.usage.input_tokens)            s.set_attribute("llm.output_tokens", result.usage.output_tokens)         with tracer.start_as_current_span("output.validate"):            validated = validate_output(result)         return validated

이 예시는 구조를 보여주기 위한 코드입니다. 실제 서비스에서는 middleware, exporter, collector 설정이 필요합니다.

10. 실무 체크리스트

# 예시입니다.[ ] 요청 전체를 하나의 trace로 볼 수 있는가?[ ] retrieval, llm call, validation이 별도 span인가?[ ] trace_id가 API 응답과 로그에 포함되는가?[ ] span attribute에 민감정보가 없는가?[ ] token usage가 metric으로 기록되는가?[ ] schema validation failure를 추적하는가?[ ] p95/p99 latency를 dashboard에서 보는가?[ ] eval result와 prompt_version을 연결할 수 있는가?

실패 사례: 로그는 많은데 병목을 찾지 못하는 경우

LLM 서비스에서 request started, retrieval done, model done 같은 로그를 많이 남겨도 장애 분석이 어려울 때가 있습니다. 각 로그가 같은 request에 속한다는 것은 알지만, 어느 단계가 p95 latency를 밀어 올리는지, retry가 몇 번 일어났는지, validation 실패가 model 호출 전인지 후인지 한눈에 보기 어렵기 때문입니다. 로그는 사건 목록이고, trace는 사건 사이의 부모-자식 관계와 시간을 보여줍니다.

특히 RAG 요청은 병목이 매번 바뀝니다. 어떤 요청은 vector search가 느리고, 어떤 요청은 provider queue가 길고, 어떤 요청은 schema validation retry 때문에 두 번째 모델 호출이 생깁니다. 하나의 trace 안에 api.request, retrieval.search, llm.call, output.validate, answer.persist span이 있으면 문제 위치를 단계별로 분리할 수 있습니다.

구현 예시: LLM 요청 span 구조

trace llm_answer_request  span api.request    span auth.check    span retrieval.search    span llm.call    span output.validate    span answer.persist

각 span attribute에는 원문이 아니라 운영 가능한 요약값을 넣습니다.

llm.model = "runtime-selected-model"llm.prompt_version = "rag_answer.v4"llm.input_tokens = 4200llm.output_tokens = 620retrieval.top_k = 8retrieval.index = "document_chunks_hnsw_v2"validation.schema = "answer_with_citations.v2"

prompt 전문, 문서 전문, 사용자 개인정보를 attribute에 넣지 않는 것이 중요합니다. 대신 hash, token count, version, chunk id처럼 재현과 분석에 필요한 최소값을 남깁니다.

체크리스트 적용 결과

확인 항목좋은 신호위험 신호
trace 연결API, retrieval, LLM, validation span이 한 trace에 있음단계별 로그만 흩어져 있음
민감정보prompt hash와 token count만 저장원문 prompt와 chunk 전문 저장
비용 분석token usage가 trace와 metric에 연결비용 리포트와 장애 trace가 분리
재시도retry span이 원 호출의 child로 보임재시도가 새 request처럼 보임

이 구조가 있으면 "LLM이 느리다"는 모호한 신고를 "retrieval p95가 증가했다" 또는 "schema validation retry가 늘었다"로 바꿀 수 있습니다.

11. Q&A

Q1. 로그만 잘 남기면 trace가 없어도 되나요?

작은 서비스에서는 로그로 시작할 수 있습니다. 하지만 요청이 여러 단계로 나뉘면 trace가 훨씬 유리합니다. 특히 LLM provider 호출과 retrieval 병목을 구분하려면 span이 필요합니다.

Q2. 모든 요청을 trace하면 비용이 크지 않나요?

샘플링을 사용할 수 있습니다. 다만 오류 요청, 긴 지연 요청, eval 실패 요청은 우선적으로 보존하는 정책이 좋습니다.

Q3. prompt 내용을 trace에 넣어도 되나요?

권장하지 않습니다. prompt와 문서 chunk에는 민감정보가 포함될 수 있습니다. hash, token count, version 등으로 대체합니다.

12. 참고자료와 불확실성

참고자료

불확실성

  • OpenTelemetry SDK 설정은 언어와 프레임워크에 따라 달라집니다.
  • 샘플링 정책과 데이터 보관 기간은 조직의 비용·보안 정책에 따라 결정해야 합니다.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP