미니멀 .NET LLM Observability: 15분 안에 타임아웃 재현 및 트라이애지
Source: Dev.to
LLM 엔드포인트가 타임아웃되면, 대시보드만으로는 거의 도움이 되지 않습니다. 필요한 것은 증상에서 원인으로 빠르게 연결되는 경로입니다.
이 글에서는 제어된 504 오류를 강제로 발생시키고 metrics → trace → logs 워크플로우를 반복적으로 수행하면서 디버깅할 수 있는 작은 .NET 실험실을 소개합니다. 스택은 ASP.NET Core, Blazor, .NET Aspire, Ollama, OpenTelemetry이며, 목표는 실용적입니다: 배포 전에 진단 시간을 단축하는 것입니다.
핵심 아이디어는 다음과 같습니다: 관측 가능성은 대시보드가 아니라 진단 시간입니다.
저는 로그를 보면서 로그, 트레이스, 메트릭을 신뢰할 수 있게 연관시키는 방법이 없어 이미 너무 많은 시간을 낭비했기 때문에 이 프로젝트를 만들었습니다. 이 글에서 “LLM 워크로드”란 모델 호출과 프롬프트 또는 툴 변경으로 인해 꼬리 지연 시간과 오류가 발생하는 엔드포인트를 의미하며, 단순히 HTTP 핸들러만을 의미하지는 않습니다.
이 글은 레포지토리 우선이며, 동반 레포지토리를 직접 사용합니다:
Repo:
- 정상, 지연, 타임아웃, 실제 모델 호출 시나리오를 트리거할 수 있는 Blazor UI가 포함되어 있습니다.
The Stack in One Minute
| Component | Description |
|---|---|
| ASP.NET Core API | 노이즈 없이 엔드‑투‑엔드로 계측할 수 있는 작은 요청 표면입니다. |
| Blazor Web UI | 원클릭으로 정상, 지연, 타임아웃, 실제 모델 호출 시나리오를 확인할 수 있습니다. |
| .NET Aspire AppHost | 로컬 오케스트레이션과 빠른 전환을 위한 Aspire 대시보드를 제공합니다. |
Ollama (ollama/ollama:0.16.3) | 클라우드 토큰 비용 없이 실제 로컬 모델 호출 동작을 제공합니다. |
| OpenTelemetry | 로그는 무엇을 알려주고, 트레이스는 어디서를 알려주며, 메트릭은 얼마나 자주를 알려줍니다. |
핵심은 간단합니다: 실패를 유발하고 엔드‑투‑엔드로 관찰할 수 있는 하나의 로컬 환경을 제공하여 추측 없이 문제를 파악할 수 있게 하는 것입니다.
Why LLM Timeouts Feel Different
- 프롬프트 변경은 배포와 같습니다: 코드는 동일할 수 있지만 지연 시간과 실패 모드가 바뀔 수 있습니다.
- 모델 및 런타임 변경은 꼬리 지연 시간을 바꿀 수 있습니다.
- 도구나 종속성 호출은 변동성을 확대합니다 — 하나의 느린 호출이 타임아웃이 될 수 있습니다.
최소 상관 관계 필드
신속한 트라이지를 위해, 모든 곳에 몇 가지 필드가 존재하길 원합니다:
| 필드 | 목적 |
|---|---|
run_id | 하나의 요청 라이프사이클을 추적 |
trace_id | 스팬 및 서비스 전반에 걸친 실행을 추적 |
prompt_version | 프롬프트 변경에 행동을 연결 |
tool_version | 통합 변경에 따른 실패를 연결 |
상관 관계가 어떻게 보여야 하는가
POST /ask → trace_id in the trace span → run_id + trace_id in logs → timeout metric increases
내가 사용하는 명명 규칙
- snake_case in logs and JSON:
run_id,trace_id,prompt_version,tool_version - camelCase in C# variables:
runId,traceId,promptVersion,toolVersion
예시 로그 라인
timeout during /ask run_id=9f0f2f3a6fdd4f5f9e9a1f4d8f6c6f3e trace_id=4c4f3b2e86d4d6a6b1f69a0d9d0d9f0a prompt_version=v1 tool_version=local-llm-v1
그 체인에서 하나의 링크라도 누락되면, 트라이지가 즉시 느려집니다.
디버깅 흐름이 어떻게 보이는가
실제로는 다음과 같은 절차를 따릅니다:
- 웹 UI에서 Simulated Timeout (504) 를 클릭합니다.
- Aspire Metrics를 열어
llm_timeouts_total가 증가했는지 확인합니다. - Traces로 이동하여 실패한
llm.run을 엽니다. trace_id를 복사한 뒤, 로그로 전환하여trace_id또는run_id로 필터링합니다.- 실패가 특정
prompt_version혹은tool_version과 일치하는지 확인합니다.
이것이 실험실의 핵심 목적입니다: 타임아웃 증상에서 몇 단계의 의도적인 절차를 통해 가능한 원인으로 이동함으로써 추측에 의존하지 않게 합니다.
사전 요구 사항
- Docker Desktop 또는 Docker Engine이 설치되어 실행 중
- 저장소의
global.json에 지정된 .NET SDK 버전이 설치됨 - Aspire 워크로드(설정에 따라 필요할 경우)
dotnet workload install aspire
- 로컬 포트 사용 가능(또는 실행 설정 조정):
18888,18889,11434 - 안정적인 API 포트 부록을 사용하는 경우
17100포트도 비워두어야 함
Step 1 — Clone and Run the Repository
git clone https://github.com/ovnecron/minimal-llm-observability.git
cd minimal-llm-observability
dotnet run --project LLMObservabilityLab.AppHost/LLMObservabilityLab.AppHost.csproj
터미널에 출력된 Aspire Dashboard URL을 엽니다. 인증 프롬프트가 표시되면 터미널에 표시된 일회성 URL을 사용하세요.
Fixed local HTTP launch settings
- Aspire Dashboard:
http://localhost:18888 - OTLP endpoint (Aspire Dashboard):
http://localhost:18889 - Web UI (
LLMObservabilityLab.Web): Aspire Dashboard 리소스 목록에서 엽니다
ASPIRE_ALLOW_UNSECURED_TRANSPORT=true 로 설정된 AppHost 실행 프로파일에 이미 보안되지 않은 로컬 전송이 활성화되어 있습니다.
로컬에서 11434 포트로 Ollama를 이미 실행 중이라면 중지하거나 AppHost에서 컨테이너 포트 매핑을 변경하세요.
Real Ollama Call 이 “model not found” 를 반환하면, 실행 중인 컨테이너에서 기본 모델을 가져옵니다:
docker exec -it "$(docker ps --filter "name=local-llm" --format "{{.Names}}" | head -n 1)" \
ollama pull llama3.2:1b
Source: …
단계 2 — 웹 UI에서 시나리오 트리거
-
Aspire Dashboard → Resources →
web-ui엔드포인트 클릭 -
LLMObservabilityLab.Web의 루트 페이지에서 원클릭 액션을 제공합니다:- Healthy Run
- Simulate Delay
- Real Ollama Call
- Simulated Timeout (504)
각 실행에서는 다음을 표시합니다:
run_idtrace_id- 상태
- 경과 시간
-
웹 UI에는 고정된 15분 트리아지 체크리스트가 포함된
/drill도 있습니다.
3단계 — 정상 기준 생성 (선택 사항)
Web UI에서 Healthy Run을 약 20번 클릭합니다. 이렇게 하면 다음 메트릭에 대한 빠른 기준을 얻을 수 있습니다:
llm_runs_totalllm_success_total
그런 다음 이후의 실패 실행을 이 기준과 비교할 수 있습니다.
Step 4 — 타임아웃 강제 및 트리아지
- 웹 UI에서 Simulated Timeout (504) 를 클릭합니다.
- 즉시 Aspire Dashboard 를 엽니다.
버튼이 제어된 504 를 반환하므로 관측 파이프라인을 필요할 때마다 테스트할 수 있습니다.
나의 트리아지 루프 (목표: ~15 분)
| Phase | Action |
|---|---|
| Spot | Metrics 에서 llm_timeouts_total 확인 |
| Drill | 실패한 llm.run 트레이스 열기 |
| Pivot | trace_id 와 run_id 로 로그 필터링 |
| Inspect | prompt_version 과 tool_version 비교 |
| Mitigate | 가장 작은 안전한 수정부터 적용 |
| Verify | 타임아웃 시나리오를 다시 실행하고 복구 확인 |
따라 하기 쉬운 흐름
- Metrics → 스파이크가 있는지
llm_latency_ms확인 - Traces →
scenario=simulate_timeout로 필터링 → 실패한llm.run열기
빠른 결정을 위해 사용하는 최소 신호
이 저장소에서 직접 제공됨
llm_runs_totalllm_success_totalllm_timeouts_totalllm_errors_totalllm_latency_ms
파생 메트릭
task_success_rate = llm_success_total / llm_runs_total * 100
시작 알림 휴리스틱
(이것들은 시드이며 — 기준에 맞게 조정하세요.)
task_success_rate가 30 분 안에 > 5 pp 감소llm_latency_ms로부터 파생된 지연 시간 백분위가 기준 대비 > 30 % 상승tool_version로 태그된 실행에 대한 도구‑버전‑범위 성공률이 < 90 % 하락
문제 해결
| 증상 | 해결 방법 |
|---|---|
| 포트 11434가 이미 사용 중 | 로컬 Ollama 인스턴스를 중지하거나 AppHost 포트 매핑을 변경하십시오 |
| 추적 또는 메트릭이 없음 | Aspire 대시보드가 실행 중인지, OTLP 엔드포인트에 접근할 수 있는지 확인하십시오 |
| 모델을 찾을 수 없음 | 컨테이너 내부에서 ollama pull … 명령을 실행하십시오 |
| CLI 또는 API 호출 실패 | Aspire 대시보드(llm‑api → Endpoints)에서 정확한 API 엔드포인트를 복사하십시오 |
검증된 내용 vs. 의견
Observability advice often mixes hard facts with personal workflow.
관측 가능성 조언은 종종 확실한 사실과 개인적인 워크플로우를 섞어 놓습니다.
검증됨 (이 저장소에서 재현 가능)
- 시나리오(정상, 지연, 타임아웃, 실제 호출)는 웹 UI에서 트리거됩니다.
- 상관 관계 체인이 존재합니다: 메트릭 카운터 →
llm.run트레이스 →run_id와trace_id가 포함된 로그.
의견 (제 경우에 작동하지만 필요에 따라 조정하세요)
- “15‑분” 목표 루프.
- 위의 알림 임계값(시작용 시드이며 보편적인 진리는 아닙니다).
- 정확히 네 개의 상관 필드(시스템에 필요하면 더 추가하세요).
최종 생각
목표는 완벽한 대시보드가 아니라 time‑to‑diagnosis를 단축하는 것입니다.
시간 초과에서 정확한 트레이스와 로그 라인으로 전환할 수 없다면, 여전히 추측하고 있는 것입니다.
저는 이 실험실을 사용해 저에게 맞는 워크플로우를 찾았으며, 여러분도 여러분에게 맞는 관측 파이프라인을 구축하는 데 도움이 되길 바랍니다.
문제가 발생하면 GitHub 이슈를 열어 주세요. 기꺼이 도와드리겠습니다.