Lepisode Tech 로고

LLM Observability에 대하여 — Trace, Metric, 그리고 AI가 요구하는 추가 관측 항목

박지훈

안녕하세요. AI 기반 프로젝트 관리 플랫폼을 개발하고 있는 개발자입니다.

AI Agent를 구축하면서 비용을 추적하는 구조를 처음부터 안 짜놓으면, 나중에 프로덕션 올리고 나서 비용 인보이스만 보고 '어디서 청구된거지?' 하게 됩니다.

저희 팀은 지금 Mastra 프레임워크 위에 16개의 AI Agent와 3개의 Workflow를 구축하고 있는데요. 서비스 곳곳에 AI가 들어가는 구조다 보니, 아직 프로덕션 출시 전임에도 "이 기능이 토큰을 얼마나 먹는지"를 추적하는 게 설계 단계에서부터 필수였어요.

이 글에서는 AI 사용량을 추적하는 데 필요한 관측성(Observability)의 핵심 개념들과, 저희가 서비스 구축 과정에서 어떤 설계 판단을 내렸는지를 다루려고 합니다.


왜 기존 모니터링으로는 부족한가 — AI 시스템의 근본적 차이

소프트웨어 엔지니어링에서 **관측성(Observability)**이란 시스템 외부에서 내부 상태를 얼마나 잘 파악할 수 있는지를 의미합니다. 이를 구성하는 세 가지 축을 흔히 **3본주(Three Pillars)**라고 부르죠.

┌─────────────────────────────────────────────┐
│            Observability 3본주              │
│                                             │
│    Metric       Trace           Log         │
│   "무엇이"     "왜, 어디서"  "정확히 무슨"    │
│   일어나는가   일어났는가    일이 있었는가     │
└─────────────────────────────────────────────┘

전통적인 웹 서비스에서는 이 세 가지만으로 충분했어요. 하지만 AI 애플리케이션에서는 상황이 다릅니다. LLMOps 분야에서는 이 3본주를 그대로 가져오면서, 동시에 AI만의 고유한 관측 항목을 추가하고 있어요.

왜 그래야 할까요? LLM은 일반적인 서버와 근본적으로 다른 특성이 있기 때문입니다.

전통적인 서버LLM 기반 시스템
같은 입력 → 같은 출력 (결정적)같은 입력 → 다른 출력 (비결정적)
에러 = HTTP 500 등 명확한 신호에러 = 200 OK인데 답이 틀림 (환각)
비용 = 인프라 고정비 중심비용 = 토큰 수에 비례하는 변동비
단일 요청/응답여러 단계의 체인/에이전트 흐름

이 차이가 "관측성을 어떻게 구현하는가"의 설계를 완전히 바꿉니다.


Metric — "지금 괜찮은가?"에 답하는 숫자

Metric은 시간축에 걸쳐 집계된 숫자 데이터예요. 대시보드에서 그래프로 보고, 임계치를 설정해서 알림을 받는 용도로 쓰입니다.

AI 시스템에서 Metric이 답해야 하는 질문은 명확합니다.

"지금 시스템이 정상인가, 돈은 얼마나 나가고 있나?"

AI 시스템에서 추적해야 하는 핵심 Metric을 크게 네 가지로 분류할 수 있어요.

성능 메트릭

메트릭의미왜 중요한가
TTFT (Time to First Token)첫 번째 토큰이 생성되기까지의 시간스트리밍 UX의 체감 속도를 결정
End-to-End Latency (P50/P95)전체 응답 완료까지의 시간사용자 이탈률에 직결
Throughput (Tokens/sec)초당 처리 토큰 수동시 사용자 수용 능력 판단
Error Rate요청 실패 비율Rate Limit(429), 서버 에러(500) 감지

비용 메트릭

메트릭의미왜 중요한가
Cost per Request요청 1건당 비용기능별 단가 산출
Token Usage (Input/Output)입력·출력 토큰 분리 집계프롬프트 최적화 효과 측정
Cost per Session세션(대화) 1건당 총 비용사용자별 비용 분포 파악
Daily/Monthly Spend일별·월별 누적 비용예산 초과 알림, 트렌드 분석

품질 메트릭

메트릭의미왜 중요한가
Hallucination Rate환각(거짓 정보 생성) 비율서비스 신뢰도의 핵심 지표
Relevance Score응답이 질문과 얼마나 관련 있는지RAG 파이프라인 효과 측정
User Feedback Rate사용자 긍정/부정 피드백 비율실제 체감 품질의 유일한 정답

에이전트·파이프라인 메트릭

메트릭의미왜 중요한가
Tool Call Success Rate도구 호출 성공률에이전트의 실행 능력 판단
Task Completion Rate작업 완료율에이전트가 실제로 쓸모 있는지
Retry/Rework Rate재시도율비효율적인 루프 감지

Metric의 핵심 가치는 빠른 감지에 있어요. "응답 시간이 200% 뛰었다", "비용이 어제 대비 3배다" — 이런 이상 신호를 초 단위로 잡아내는 게 Metric의 존재 이유입니다.

하지만 Metric은 **"왜?"**에 답하지 못합니다.

여기서 Trace가 필요해집니다.


Trace — "왜 이런 결과가 나왔는가?"를 추적

Trace는 하나의 요청이 시스템 내부를 통과하면서 만들어내는 여정의 기록이에요. 이 여정은 여러 개의 **Span(구간)**으로 구성됩니다.

전통적인 웹 서비스의 Trace는 비교적 단순해요. HTTP 요청 → 미들웨어 → 서비스 로직 → DB 쿼리 → 응답. 하지만 AI 시스템의 Trace는 훨씬 복잡한 이야기를 담고 있습니다.

[Trace: 사용자 질문 "이번 달 매출 분석해줘"]
│
├── [Span: 프롬프트 구성] 12ms
│   └── 시스템 프롬프트 + 사용자 입력 + 히스토리 조합
│
├── [Span: RAG 검색] 340ms ← 병목현상
│   ├── [Span: 임베딩 생성] 45ms
│   └── [Span: 벡터 DB 검색] 295ms
│       └── 검색된 문서 3건, relevance_score: 0.72, 0.68, 0.41
│
├── [Span: LLM 호출] 1,240ms
│   ├── model: gpt-4o
│   ├── input_tokens: 2,847
│   ├── output_tokens: 512
│   └── finish_reason: stop
│
├── [Span: 도구 호출 - 매출 데이터 API] 89ms
│   └── status: success, rows_returned: 24
│
└── [Span: 최종 응답 생성] 890ms
    ├── input_tokens: 3,421
    ├── output_tokens: 1,024
    └── cost: $0.043

이 Trace 하나를 보면 여러 가지를 동시에 알 수 있어요. RAG 검색에서 세 번째 문서의 관련도(0.41)가 낮다는 것, LLM을 두 번 호출했고 총 토큰이 7,804개라는 것, 전체 비용이 $0.043이라는 것.

Metric이 "응답이 2.5초 걸렸다"라고만 알려준다면, Trace는 "그 2.5초 중 벡터 DB 검색이 295ms, LLM 호출이 2,130ms를 차지했고, 두 번째 LLM 호출이 불필요했을 수 있다"는 맥락을 제공합니다.

Trace의 핵심 가치는 "원인 진단"에 있습니다.


Metric과 Trace의 협업 — 감지에서 진단까지

두 개념이 실제로 어떻게 함께 작동하는지, 하나의 시나리오로 정리하면 이렇습니다.

1️⃣ 감지 (Metric)
   "오후 3시부터 평균 응답 시간이 200% 증가했다"

2️⃣ 범위 좁히기 (Metric)
   "특정 기능(매출 분석)의 요청만 느리다"

3️⃣ 원인 추적 (Trace)
   "해당 기능의 Trace를 열어보니,
    벡터 DB 검색 Span이 평소 50ms → 800ms로 증가"

4️⃣ 해결
   벡터 DB 인덱스 재구성 → 응답 시간 정상화

5️⃣ 검증 (Metric)
   "오후 5시부터 응답 시간 정상 범위로 복귀 확인"

Metric 없이는 문제 발생 자체를 놓치고, Trace 없이는 원인을 찾을 수 없습니다. 둘은 경쟁 관계가 아니라 탐지-진단의 파이프라인이에요.


구축 과정에서의 설계 판단 — Mastra + ClickHouse

여기서부터는 저희가 서비스 구축 과정에서 AI 관측성을 어떻게 설계했는지 구체적인 이야기예요. 아직 프로덕션 출시 전이지만, 출시 후에 "비용이 어디서 새는지 모르겠다"는 상황을 만들지 않기 위해 개발 초기부터 관측성을 구조에 녹였습니다.

왜 Mastra였는가

저희는 NestJS 백엔드 위에서 16개의 AI Agent와 3개의 Workflow를 구축하고 있어요. 이걸 관리하려면 단순 API 호출 래퍼가 아니라, Agent 오케스트레이션과 관측성을 동시에 제공하는 프레임워크가 필요했습니다.

Mastra를 선택한 이유는 세 가지였어요.

  1. Observability가 프레임워크 레벨에서 내장됨 — 별도 미들웨어 없이 모든 Agent 호출이 자동으로 Span을 생성
  2. RequestContext를 통한 메타데이터 전파 — Trace에 userId, organizationId, feature 같은 비즈니스 컨텍스트를 자연스럽게 태깅 가능
  3. Composite Storage로 관측 데이터 분리 저장 — 오퍼레이션 데이터(PostgreSQL)와 관측 데이터(ClickHouse/DuckDB)를 분리할 수 있음

다른 선택지도 검토했어요. Langfuse(별도 서비스 운영 부담), Helicone(프록시 방식이라 에이전트 내부 Span이 안 잡힘), 직접 OpenTelemetry 구현(개발 비용 과다). 결국 "프레임워크와 관측성이 동일 레이어에 있어야 에이전트 내부까지 Trace가 잡힌다"는 판단이 결정적이었습니다.

관측성 설정의 실제 모습

저희의 Mastra 초기화 코드에서 관측성 설정 부분만 추출하면 이렇습니다.

// 핵심만 추출한 의사코드
observability: new Observability({
  configs: {
    default: {
      serviceName: 'plany-server-ai',

      // 모든 Trace에 자동으로 태깅될 비즈니스 메타데이터
      requestContextKeys: [
        'feature',        // 어떤 AI 기능인지
        'userId',         // 누가 호출했는지
        'organizationId', // 어느 조직인지
        'projectId',      // 어느 프로젝트인지
        // ... 도메인별 버전 ID들
      ],

      // 프로덕션에서는 전체의 10%만 상세 추적
      sampling:
        process.env.NODE_ENV === 'production'
          ? { type: 'RATIO', probability: 0.1 }
          : { type: 'ALWAYS' },

      // 프롬프트/응답에서 민감 정보 자동 마스킹
      spanOutputProcessors: [new SensitiveDataFilter()],
    },
  },
}),

이 설정에서 세 가지 설계 판단이 있었어요.

첫 번째, requestContextKeys에 비즈니스 맥락을 녹인 것. 단순히 traceId만 있으면 "이 Trace가 어디서 온 건지" 알 수 없어요. 저희는 feature(어떤 AI 기능), userId(누가 호출), organizationId(어느 조직) 같은 키를 Agent 호출 시점에 주입하고, 이를 자동으로 모든 Span에 전파하도록 설계했습니다. 프로덕션에서 "요구사항 생성 기능이 전체 비용의 몇 %인지"를 즉시 뽑아낼 수 있는 구조를 미리 만들어둔 거예요.

두 번째, 샘플링 비율을 환경별로 나눈 것. 개발 환경에서는 모든 요청을 100% 추적하고, 프로덕션에서는 10%만 추적하도록 설계했어요. 프로덕션에서 100% 추적하면 관측 데이터 저장 비용이 AI API 비용에 버금가는 아이러니가 발생할 수 있거든요. 10%만 샘플링해도 통계적으로 유의미한 Metric은 충분히 뽑을 수 있고, 문제가 생겼을 때는 특정 사용자나 기능에 대해 일시적으로 100%로 올리면 됩니다.

세 번째, SensitiveDataFilter를 기본으로 건 것. 프롬프트와 응답에는 사용자 개인정보가 포함될 수 있어요. GDPR이나 개인정보보호법을 고려하면 Trace에 원본 프롬프트를 그대로 남기는 건 위험합니다. 관측은 필요하되, 민감 데이터는 마스킹하는 — 이 균형을 프레임워크 레벨에서 보장한 거죠.

저장소의 이중 전략 — ClickHouse와 DuckDB를 나눈 이유

관측 데이터를 어디에 저장할지는 설계 단계에서 적지 않게 고민한 부분이에요.

관측 데이터는 쓰기 빈도가 극단적으로 높고, 읽기는 집계 위주라는 특성이 있어요. 프로덕션에서 트래픽이 올라오면 Span 테이블이 빠르게 불어날 텐데, 이걸 비즈니스 데이터와 같은 PostgreSQL에 두면 OLTP 쿼리 성능에 영향을 줄 가능성이 높습니다. 그래서 처음부터 관측 데이터와 비즈니스 데이터를 분리하는 구조로 설계했어요.

프로덕션: PostgreSQL (비즈니스) + ClickHouse (관측)
개발환경: PostgreSQL (비즈니스) + DuckDB 파일 (관측)

ClickHouse를 프로덕션 관측 저장소로 선택한 이유는, 컬럼 압축으로 저장 효율이 높고 집계 쿼리 성능이 우수하기 때문이에요. "지난 30일간 기능별 토큰 사용량"같은 쿼리를 대량 데이터에서도 빠르게 뽑기 위한 선택입니다.

하지만 개발 환경에서까지 ClickHouse를 띄우는 건 과도해요. 로컬에서 도커로 ClickHouse를 관리하는 것도 번거롭고, CI에서도 불필요한 의존성이에요. 그래서 개발 환경에서는 파일 기반의 DuckDB를 사용하도록 설계했습니다. 같은 SQL 집계를 돌릴 수 있으니 쿼리 호환성도 유지되고요.

Graceful Degradation — 관측 실패가 서비스를 죽이면 안 된다

관측은 부가 기능이에요. 관측 시스템에 장애가 나더라도 메인 서비스까지 죽으면 안 됩니다. ClickHouse 연결이 끊기거나 쓰기가 실패했을 때, 그 에러가 Agent 호출 전체를 실패시키는 상황이 발생할 수 있다는 걸 코드 리뷰 과정에서 발견했어요.

그래서 SafeClickhouseObservabilityStorage라는 래퍼를 미리 만들어 두었습니다. 핵심 아이디어는 간단해요.

// 관측 저장소 래퍼의 핵심 패턴
class SafeStorage extends BaseStorage {
  private available = true;

  async createSpan(args) {
    if (!this.available) return; // 장애 시 무시
    try {
      await super.createSpan(args);
    } catch (error) {
      this.available = false; // 한 번 실패하면 비활성화
      log.warn("ClickHouse 쓰기 실패. no-op 모드 전환.");
    }
  }
}

한 번 쓰기에 실패하면 available 플래그를 false로 바꾸고, 이후 모든 쓰기 시도를 무시합니다. 에이전트는 계속 동작하고, 관측 데이터만 유실되는 구조예요. **"관측은 있으면 좋고, 없어도 서비스가 살아야 한다"**는 원칙을 코드 레벨에서 보장하기 위한 안전장치입니다.


비용 어트리뷰션 — "누가, 어디서, 얼마나" 쓰고 있는가

AI 비용은 전통적인 인프라 비용과 근본적으로 다른 구조를 가지고 있어요. 서버 비용은 예측 가능하지만, AI API 비용은 토큰 수 × 모델 단가로 결정되기 때문에 사용 패턴에 따라 극단적으로 변동합니다.

저희 프로젝트에서는 모든 AI 호출에 feature 태그를 붙이고, 이를 기반으로 기능별 비용을 추적할 수 있는 구조를 설계했어요.

// 모든 AI Agent 호출에 필수로 붙이는 메타데이터
const result = await agent.generate(prompt, {
  requestContext: createRequestContext({
    feature: "generate-requirements", // 기능 식별자
    userId: currentUser.id,
    organizationId: org.id,
    projectId: project.id,
  }),
  tracingOptions: createTracingOptions("generate-requirements", {
    userId: currentUser.id,
    organizationId: org.id,
  }),
});

이 태그가 있어야 "요구사항 생성 기능이 전체 AI 비용의 몇 %를 차지하는지"를 분석할 수 있어요.

Admin 대시보드에서는 다음 차원으로 비용을 분석할 수 있도록 API를 설계했습니다.

분석 차원API 엔드포인트답하는 질문
기능별/admin/ai-usage/features어떤 AI 기능이 가장 비싼가?
조직별/admin/ai-usage/organizations어떤 고객이 AI를 가장 많이 쓰는가?
사용자별/admin/ai-usage/users특정 사용자의 사용 패턴은?
모델별/admin/ai-usage/modelsGPT-4o vs Claude, 어디에 비용이 집중되는가?
시계열/admin/ai-usage/timeseries비용 트렌드가 어떻게 변하고 있는가?

비용은 사후 산출 방식으로 계산해요. API 호출 시점에는 정확한 비용을 모르기 때문에, 모델별 단가 카탈로그를 관리하면서 Trace에 기록된 토큰 수에 단가를 곱해 추정 비용을 산출합니다.

추정 비용 = (입력 토큰 / 1,000,000) × 입력 단가
          + (출력 토큰 / 1,000,000) × 출력 단가

100% 정확하진 않지만, 기능 간 상대적 비용 비교와 이상 탐지에는 충분합니다.


3본주를 넘어서 — AI 모니터링에서 추가로 챙겨야 하는 것들

여기까지가 전통적인 관측성 프레임이에요. 하지만 AI 시스템에서는 Trace와 Metric과 Log만으로는 부족합니다.

LLM은 "HTTP 200을 반환하면서 완벽한 형태의 거짓 정보를 줄 수 있는" 극히 특수한 시스템이기 때문이에요.

1. 평가 (Evaluation) — 사실상 AI 관측성의 4번째 기둥

전통적인 관측성에서는 "요청이 성공했는가"가 이분법적으로 판단돼요. HTTP 200이면 성공, 500이면 실패.

하지만 AI에서는 **"요청은 성공했는데, 답이 틀렸다"**는 상황이 일상적으로 발생해요. 이를 감지하려면 출력의 품질을 별도로 평가하는 시스템이 필요합니다.

평가 방법설명적합한 상황
자동 평가정해진 기준(정규식, 유사도, PII 검출)으로 자동 채점실시간 모니터링, 대량 처리
LLM-as-a-Judge별도의 평가 전용 모델이 출력을 채점정확도·충실도 검증
Human-in-the-Loop사람이 직접 샘플링하여 평가자동 평가의 보정, 엣지 케이스
사용자 피드백좋아요/싫어요, 재질문 패턴 분석실제 만족도의 유일한 정답

OpenTelemetry 커뮤니티에서도 이를 인식하고, gen_ai.* 네임스페이스에 평가 관련 시맨틱 컨벤션을 추가하고 있는 추세입니다. 기존 3본주가 "시스템이 돌아가고 있는가"를 확인한다면, Eval은 "시스템이 제대로 돌아가고 있는가"를 확인합니다.

2. 가드레일 모니터링 — 안전장치가 작동하는지 감시하기

AI 시스템에는 모델과 사용자 사이에 가드레일이라는 안전장치가 있어요. 입력에서 프롬프트 인젝션을 걸러내고, 출력에서 개인정보(PII)나 유해 콘텐츠를 차단하죠.

가드레일 자체를 모니터링해야 하는 이유가 있어요.

추적 항목왜 중요한가
가드레일 트리거 비율비정상적 급증 = 공격 시도 의심
가드레일 레이턴시성능 병목이 모델인지 가드레일인지 분리
우회 시도 패턴새로운 공격 벡터 사전 감지
False Positive 비율정상 요청이 잘못 차단되면 사용자 경험 훼손

3. 사용자 피드백 루프 — 모든 자동화의 정답 보정

모든 자동 평가의 한계는 동일합니다 — 진짜 사용자가 만족하는지는 사용자만 안다는 것.

명시적 피드백(좋아요/싫어요)과 암시적 피드백(재질문, 대화 이탈)을 수집하고, 이를 Trace와 연결해서 분석해야 합니다.

좋아요/싫어요 비율 → Metric으로 감지
  ↓
부정 피드백이 집중된 기능 → Metric 필터링
  ↓
해당 요청의 프롬프트·응답 → Trace 분석
  ↓
프롬프트 수정 또는 RAG 품질 개선
  ↓
개선 후 피드백 비율 변화 확인 → Metric

이 루프가 돌아야 AI 시스템이 학습합니다. 모델 자체의 학습이 아니라, 모델을 둘러싼 시스템의 학습이에요.


구축 과정에서 배운 설계 포인트

몇 가지 구현 과정에서 배운 것들을 정리합니다.

Health Check는 관측 파이프라인 자체에도 필요하다. 저희는 /admin/ai-usage/health 엔드포인트를 만들어서, Span 테이블이 정상인지, 메타데이터 커버리지(feature 태그가 몇 %나 채워져 있는지)가 기준 이상인지를 점검할 수 있게 해뒀어요. 관측 시스템이 조용히 죽어 있으면, 데이터가 비어 있다는 걸 한참 뒤에야 알게 되거든요.

Span 데이터에서 토큰 수를 추출하는 건 생각보다 지저분하다. 모델 제공자마다 토큰 사용량을 Span attributes에 기록하는 키가 다릅니다. gen_ai.usage.input_tokens일 수도 있고, usage.inputTokensusage.promptTokens일 수도 있어요. 저희는 SQL에서 COALESCE 체인으로 6~7개의 가능한 경로를 폴백하면서 추출하도록 구현했습니다. 이건 공식 문서만 봐서는 알기 어렵고, 실제 Span 데이터를 들여다봐야 발견할 수 있는 부분이에요.

비용 추적은 첫 번째 API 호출 전에 설계해야 한다. 나중에 태그를 붙이려고 하면 이미 쌓인 데이터에 소급 적용이 불가능합니다. 저희는 createRequestContextcreateTracingOptions 두 개의 헬퍼를 만들어서, 모든 Agent 호출에 메타데이터를 필수로 주입하도록 패턴화했어요.


전체 지도 — AI 모니터링에 필요한 것들을 한눈에

┌─────────────────────────────────────────────────┐
│              AI 관측성 (Observability)           │
│                                                 │
│  ┌───────────── 전통적 3본주 ──────────────┐    │
│  │                                         │    │
│  │  Metric       Trace         Log         │    │
│  │  감지         진단        상세 기록      │    │
│  └─────────────────────────────────────────┘    │
│                                                 │
│  ┌───────────── AI 확장 영역 ──────────────┐    │
│  │                                         │    │
│  │  Eval         — 품질 평가                │    │
│  │  Cost Attr.   — 비용 귀속                │    │
│  │  Guardrail    — 안전장치 모니터링         │    │
│  │  Feedback     — 사용자 피드백 루프        │    │
│  └─────────────────────────────────────────┘    │
│                                                 │
└─────────────────────────────────────────────────┘

전통적 3본주는 "시스템이 살아 있는가"를 확인합니다. AI 확장 영역은 "시스템이 기대대로 동작하는가"를 확인합니다. 둘 다 없으면 AI 시스템은 블랙박스에요.


돌아보며

AI 사용량 모니터링은 결국 하나의 질문으로 수렴합니다.

"우리 AI가 제대로 동작하고 있다는 걸, 어떻게 증명할 수 있는가?"

Metric은 이상 신호를 잡아내고, Trace는 원인을 추적하고, Log는 세부 사항을 기록합니다. 여기까지는 전통적인 소프트웨어와 같아요.

하지만 AI는 "정상적으로 틀릴 수 있는" 시스템이기 때문에, Eval로 품질을 검증하고, Cost Attribution으로 비용을 쫓고, Guardrail로 안전을 확인하고, 사용자 피드백으로 모든 자동화의 정답을 교정해야 합니다.

16개의 Agent와 3개의 Workflow를 구축하면서 체감한 건, 관측성은 "나중에 붙여야지"라는 태도로는 안 된다는 거예요. 첫 번째 Agent를 만드는 순간부터 메타데이터 태깅, 비용 추적, Graceful Degradation이 설계에 녹아 있어야 합니다. 프로덕션에 올리고 나서 허겁지겁 붙이는 것보다, 구축 단계에서 구조를 잡아두는 게 훨씬 저렴하니까요.

레피소드와
함께하고 싶다면?

DX를 넘어 AX까지, 기획, 디자인, 개발 한팀이 수행하는 검증된 레피소드와 함께하세요.

문의하기

이름, 연락처, 이메일을 남겨주시면 확인 후 연락드리겠습니다.