AEGLOSLAB ChatGPT 같은 AI를 만들고 서비스하기

제7부 제품·서비스(Product/Service)

7.6 관측성 · 품질 모니터링

이 장이 남기려는 문장은 하나다. LLM 제품의 실패는 조용하다. 전통적인 웹 서비스라면 무언가 잘못됐을 때 시스템이 소리를 지른다 — 500 에러, 타임아웃, 예외 스택이 대시보드를 빨갛게 물들이고, 관측의 일은 대체로 "언제·어디서 터졌나"를 찾는 것이었다. LLM 제품은 그 습관을 배신한다. 모델은 HTTP 200을 반환하면서 틀린 답을, 환각을, 정책을 벗어난 톤을, 혹은 프롬프트가 부풀어 열 배 비용이 커진 답을 태연히 내보낸다. 이때 시스템 지표는 전부 초록불이다 — 에러율 0%, 지연 정상, 재시도 없음. 그런데 사용자는 조용히 떠나고 마진은 조용히 잠식된다. 관측 없이 이런 시스템을 운영하는 것은, 계기판에 속도계만 달린 비행기를 고도·연료·자세 없이 모는 것과 같다. 속도는 완벽한데 추락한다.

그래서 이 장의 첫 교정은 태도에 있다. 200 OK는 "성공"이 아니라 "서버가 응답했다"일 뿐이다. 성공은 그 응답이 옳고·저렴하고·정책 안에 있을 때 성립하며, 그 셋 중 무엇도 HTTP 상태 코드로는 보이지 않는다. 서빙 엔지니어의 몸에 밴 습관은 지연·처리량에 있고 그것은 옳지만(5.1·5.5), LLM 제품에서 지연은 관측해야 할 세 축 중 하나일 뿐이다. 지연이 완벽해도 비용이 마진을 잠식하면 사업이 실패하고(7.5), 지연과 비용이 완벽해도 품질이 서서히 어긋나면 사용자가 말없이 이탈한다. 관측성이란 결국 지연·비용·품질을 한 화면에서, 그리고 하나의 요청 안에서 함께 보는 일이다.

이 장은 그 셋을 계측하는 순서로 흐른다. 요청 하나를 스팬 트리로 분해하는 트레이싱, 그 안에서 무엇을 어디까지 남길지와 프라이버시를 비교 평가하는 로깅, 신호를 집계해 추세로 표시하는 메트릭, 정답 키 없이 프로덕션 위에서 품질을 근사하는 온라인 평가, 그 지표가 변할 때 원인을 우리 변경과 세상 변화로 가르는 드리프트·회귀 탐지, 그리고 언제 사람을 깨울지 규율하는 알림·SLO까지. 앞선 제7부가 제품을 세우는 문제였다면 이 장은 그 제품이 트래픽을 받기 시작한 뒤의 문제 — 지금 무슨 일이 벌어지고 있는가를 어떻게 보는가 — 를 다룬다.

7.6.1 관측성과 모니터링은 다르다 — LLM 실패의 세 가지 어둠

용어부터 갈라 두면 이후가 선명해진다. 모니터링(monitoring) 은 미리 정한 지표를 지켜보는 일이다 — "에러율이 1%를 넘으면 알려라." 관측성(observability) 은 예상하지 못한 문제까지 사후에 심층 분석할 수 있도록 충분한 신호를 남겨 두는 성질이다 — "어제 오후 3시에 특정 테넌트의 답이 이상했다는데, 그 요청들을 끄집어내 무슨 일이 있었는지 재구성할 수 있는가." 모니터링은 아는 질문에 답하고, 관측성은 아직 모르는 질문에 답할 여지를 남긴다. LLM 제품에서 이 구분이 특히 문제가 되는 이유는 실패의 양태가 미리 다 예상되지 않기 때문이다. 어떤 프롬프트가 어떤 환각을 부를지 목록으로 명시할 수 없으니, "이 지표만 보면 된다"는 없다. 사후에 심층 분석할 수 있는 원 데이터를 남겨 두는 것이 방어의 절반이다.

LLM 제품이 조용히 실패하는 방식은 세 갈래이고, 셋 다 시스템 지표에는 드러나지 않는다.

① 지연의 어둠 한 요청 = TTFT + TPOT + RAG·도구· 에이전트 궤적 "요청 지연 p95" 하나로는 어디가 느린지 모른다 → 트레이싱 ② 비용의 어둠 요청마다 토큰이 100배 차 (프롬프트·출력· 모델·캐시) 지연 정상, 비용만 조용히 저하 → 토큰·비용 지표 ③ 품질의 어둠 200 OK + 틀림· 무례·환각 정답 키가 없다 에러율만 보면 "정상"으로 오인 → 온라인 품질 평가 세 어둠은 겹친다 — 셋을 함께 보지 않으면, 한 축이 좋아 보여도 나머지가 붕괴하는 중일 수 있다.
프로덕션 LLM의 세 가지 어둠 — 시스템 지표(5xx·CPU)만으로는 안 보이는 것.

지연이 특이한 것은 한 요청이 하나의 숫자가 아니기 때문이다. 사용자 체감 지연은 첫 토큰까지의 시간(TTFT)과 토큰 사이 간격(TPOT)이라는 성격이 다른 두 국면으로 갈리고(5.1), RAG·도구·에이전트가 개입하면 그 위에 여러 하위 호출의 궤적이 추가된다. "요청 지연 p95 800ms"는 그 800ms가 검색에서 왔는지, 두 번의 모델 호출에서 왔는지, 느린 도구 하나에서 왔는지 말해 주지 않는다. 비용이 어두운 것은 같은 엔드포인트라도 프롬프트 길이·출력 길이·모델 선택·캐시 적중에 따라 요청당 토큰이 백 배씩 벌어지기 때문이다 — 지연은 정상인데 비용만 낭비되는 상태가 흔하다. 품질이 어두운 것은 애초에 정답 키가 없어서다. 500 에러는 셀 수 있지만 "이 답이 틀렸다"는 자동으로 세어지지 않는다. 500만 세는 모니터링은 "성공적으로 나쁜 답을 대량 생산하는" 상태를 정상으로 집계한다.

이 셋을 함께 봐야 한다는 것이 이 장의 골격이다. 한 축만 보는 관측은 위험하다 — 지연을 완벽히 튜닝했는데 그 대가로 더 비용이 큰 모델로 갈아탔다면 비용 축이 붕괴한 것이고, 비용을 아끼려 짧은 모델로 라우팅했는데 품질이 떨어졌다면 세 번째 축이 붕괴한 것이다. 세 어둠은 서로를 가린다. 관측성의 출발점은 이 삼각형을 하나의 요청 식별자로 꿰어, 한 축에서 이상을 보면 나머지 두 축을 즉시 대조할 수 있게 만드는 데 있다.

7.6.2 네 축과 그것을 묶는 하나의 식별자

관측성 일반은 세 기둥 — 트레이스·로그·메트릭 — 으로 이야기된다. LLM 제품에는 여기에 품질이라는 넷째 축이 붙는다. 이 넷을 역할로 갈라 두면 이후 절이 제자리를 찾는다. 트레이스는 한 요청이 거친 하위 작업의 인과 트리로 "이 느린 요청은 어디서 시간을 썼나"에 답한다. 로그는 개별 이벤트의 구조화 기록으로 "이 특정 요청에서 무슨 일이 있었나"에 답한다. 메트릭은 시간에 걸쳐 집계된 수치 시계열로 "전체적으로 얼마나·어떤 추세인가"에 답한다. 품질은 응답이 좋았는지를 재는 신호로 "성공한 요청이 실제로 좋았나"에 답한다. 앞의 셋은 계측만 하면 기계가 직접 뽑아내지만, 넷째는 정답 키가 없어 대리 신호로 근사해야 한다는 점에서 근본적으로 성질이 다르다 — 이 차이가 7.6.10 전체의 이유다.

네 축이 각자 훌륭한 대시보드로 존재해도, 같은 요청 식별자로 묶이지 않으면 서로 남남이다. 이것이 관측성의 실무에서 가장 자주 붕괴하는 지점이다. 비용 대시보드에서 오후 3시에 비용이 튄 것을 보았다 치자. 그 시간대의 트레이스로 건너뛰고, 그 트레이스에 묶인 프롬프트·응답 로그를 열고, 그 응답의 품질 점수를 확인하는 — 이 끊김 없는 심층 추적이 가능하려면 trace_id·request_id·session_id·가명화된 user_id(9.3)가 트레이스·로그·메트릭·품질 신호에 공통으로 심겨 있어야 한다. 이 상관(correlation)이 없으면 "비용이 튀었다"에서 "왜"로 넘어가는 길이 끊긴다. 도구를 여러 개 조합하더라도 이 식별자만 일관되면 심층 추적은 이어지고, 반대로 한 벤더가 네 축을 다 제공해도 식별자가 어긋나면 끊긴다 — 그래서 도구 선택보다 식별자 규약이 먼저다.

LLM 애플리케이션 (챗·RAG·에이전트) 계측 계층 (OpenTelemetry·SDK 래퍼) 트레이스 스팬 트리 로그 프롬프트·응답· 메타 메트릭 지연·토큰·비용· 에러 관측 백엔드 (저장·인덱싱·상관) 온라인 품질 평가 (판정자· 휴리스틱· 피드백) 대시보드 알림·SLO· 에러버짓 (5.5) 플라이휠· 실험 (8.1· 8.2)

신호를 애플리케이션 코드에서 뽑아내는 지점을 계측(instrumentation) 이라 한다. 여기서 개방 표준을 쓰라는 조언에는 실질적 이유가 둘 있다. 하나는 벤더 잠금 회피다 — OpenTelemetry(OTel) 같은 표준으로 스팬·메트릭을 내보내면 관측 백엔드를 갈아탈 때 애플리케이션을 다시 계측할 필요가 없고, LLM 특화 SDK들이 그 표준 위에 "LLM 호출 스팬"을 자동으로 추가해 준다. 다른 하나는 한 요청이 여러 서비스를 건널 때 그 경계를 넘어 스팬을 하나로 잇는 분산 트레이싱(7.6.4)이 표준화된 컨텍스트 전파에 의존하기 때문이다. 계측을 자체 포맷으로 하면 이 두 이득을 처음부터 포기하는 셈이다.

7.6.3 트레이싱 — LLM 호출·검색·도구를 스팬으로

스팬(span) 은 이름·시작·끝·속성(attributes)을 가진 하나의 작업 단위다. 트레이스(trace) 는 한 요청에 속한 스팬들의 부모-자식 트리다. 각 스팬은 요청 전체가 공유하는 trace_id, 자기 자신의 span_id, 부모의 parent_span_id를 갖고, 이 세 값이 흩어진 스팬들로부터 트리를 복원한다. 여기까지는 전통적 마이크로서비스 트레이싱과 골격이 같다. LLM 트레이싱의 고유함은 어떤 종류의 스팬에 어떤 의미 있는 속성을 붙이느냐에 있고, 이 속성 설계가 관측 품질의 대부분을 결정한다.

스팬 종류붙여야 할 속성(attributes)이 속성이 없으면 못 하는 것
LLM 호출(generation)모델 ID·버전, 입력·출력 토큰 수, TTFT, 총 지연, temperature 등 파라미터, 프롬프트·응답(샘플·마스킹), 비용, 캐시 적중, 종료 사유(stop·length·filter)비용·지연·품질을 호출 단위로 귀인
검색(retrieval, RAG)쿼리, top-k, 반환 문서 ID·유사도 점수, 재순위 여부, 히트 수환각을 "검색이 근거를 못 물어옴"으로 진단(6.4)
도구 호출(tool)도구 이름, 인자, 결과 크기·성공/실패, 지연에이전트 궤적에서 어느 도구가 실패·정체했는지(6.5)
가드레일(guardrail)정책 이름, 입력/출력 단계, 판정(통과·차단·수정), 점수가드레일 적중률·오탐 추적(7.3)
에이전트 스텝(agent step)스텝 번호, 계획·관찰·행동, 반복 여부루프 폭주·정체 진단(6.7)

이 표의 핵심 규칙은 한 문장으로 명시할 수 있다. 모든 LLM 호출은 자기 스팬을 가져야 하고, 그 스팬에는 반드시 토큰 수·비용·모델버전이 붙어야 한다. 흔한 실수는 요청이 끝난 뒤 토큰과 비용을 한 번 합산해 요청 레코드에 적는 것이다. 이러면 "이 요청이 3300 토큰을 썼다"는 알아도 "그중 어느 호출이 2100 토큰을 먹었나"를 잃는다. 에이전트나 다단계 RAG처럼 한 요청 안에서 모델을 여러 번 부르는 시스템에서는 이 손실이 치명적이다 — 비용이 급증했을 때 어느 단계를 고쳐야 하는지가 스팬 단위 속성에서만 나온다.

특히 검색 스팬의 유사도 점수와 에이전트 스텝의 반복 여부는 품질 진단의 조기 신호라 놓치기 쉽지만 값지다. RAG에서 반환 문서의 최고 유사도가 낮으면 그 요청은 "근거 없이 답을 지어낼" 위험이 높다 — 응답을 채점하기도 전에 검색 스팬만 보고 환각 가능성을 예측할 수 있다(6.4). 에이전트에서 스텝 수가 평소의 두 배로 늘면 모델이 목표를 못 찾고 루프를 반복하는 중일 수 있고, 이는 곧 지연·비용 폭주로 이어진다(6.7). 이런 신호를 스팬 속성으로 남겨 두면, 개별 요청 디버깅뿐 아니라 "검색 최고점 분포가 이번 주에 낮아졌다" 같은 집계 추세로도 표시할 수 있다.

7.6.4 분산 트레이싱 — 요청은 한 프로세스에 살지 않는다

실제 LLM 제품의 한 요청은 여러 프로세스·서비스를 건넌다. API 게이트웨이(7.2)가 받아 오케스트레이터로 넘기고, 오케스트레이터는 임베딩 서비스와 벡터 DB로 검색을 실행한 뒤 서빙 엔진(vLLM 등, 5.4)에 생성을 시키고, 그 사이사이 가드레일 서비스(7.3)를 부른다. 분산 트레이싱은 이 경계들을 넘어 trace_id를 전파(context propagation)해, 서로 다른 프로세스에서 만들어진 스팬들을 하나의 트리로 다시 꿰맨다. 전파가 없으면 서비스마다 조각난 트레이스가 남고, "게이트웨이는 빨랐는데 종단이 느렸다"에서 멈춘다.

전파의 실무 함정은 대개 경계에서 발생한다.

  • 컨텍스트를 누락함. trace 컨텍스트는 HTTP 헤더(W3C traceparent 등)나 메시지 큐 속성에 실려 다음 서비스로 넘어간다. 동기 HTTP 호출에서는 라이브러리가 알아서 해 주지만, 비동기 큐·백그라운드 작업·스트리밍에서 이 전파가 끊기기 쉽다. 끊긴 지점 아래의 스팬들은 부모를 잃고 고아가 되어 트리가 조각난다.
  • 스트리밍 스팬을 언제 닫나. LLM 호출은 첫 토큰이 나온 뒤에도 스트림이 계속 흐른다. 스팬을 첫 토큰에서 닫아 버리면 TTFT는 보이지만 전체 생성 시간을 잃는다. 올바른 계측은 TTFT를 스팬 위의 이벤트로 기록하고, 스팬 자체는 마지막 토큰에서 닫는다 — 그래야 한 스팬에서 TTFT와 총 생성 시간이 함께 나온다.
  • 샘플링과 완결성의 긴장. 트레이스를 전량 저장하면 부담이 크니 일부만 남기게 되는데(7.6.7), 무작위로 버리면 정작 디버깅하고 싶은 느린·실패 요청까지 버려질 수 있다. 이때 쓰는 것이 꼬리 기반 샘플링(tail-based sampling) 이다. 요청이 끝나 전체 트레이스를 본 뒤에 "에러가 났거나·느렸거나·품질 점수가 낮으면 무조건 보존, 나머지는 표본율대로"라고 결정하면, 버리면서도 중요한 것은 항상 검출한다. 시작 시점에 확률로 버리는 머리 기반 샘플링과 달리, 꼬리 기반은 결과를 보고 결정하므로 관측의 표적인 꼬리를 놓치지 않는다.
traceparent 전파 전파 전파 API 게이트웨이 (trace 시작) 오케스트레이터 (RAG·에이전트) 임베딩·벡터DB 서빙 엔진 (LLM 호출) 가드레일 서비스 Collector (스팬 재조립) 하나의 스팬 트리 (trace_id로 재구성)

7.6.5 한 요청의 스팬 트리를 직접 따라가기

추상론은 미끄러진다. RAG와 도구를 함께 쓰는 에이전트 요청 하나를 스팬 트리로 끝까지 그려 보면, 이 장 전체가 한 그림으로 압축된다. 사용자가 "이번 분기 우리 매출과 경쟁사 뉴스를 요약해줘"라고 물었다고 하자. 오케스트레이터는 입력 가드레일을 통과시키고, 사내 문서를 검색하고, 계획 단계 모델 호출로 무엇을 더 찾을지 정하고, 웹검색 도구를 부르고, 최종 답을 생성한 뒤 출력 가드레일을 통과시킨다. 수치는 전부 개략 규모를 보여 주기 위한 개략값이다.

트레이스를 시간축 위에 눕히면 워터폴(waterfall) 이 된다 — 각 스팬이 언제 시작해 얼마나 걸렸는지가 한눈에 보인다.

0 600ms 1200ms 1800ms 2400ms chat.request (root) guardrail.input retrieval embed.query vector.search llm.call (planner) TTFT tool.web_search llm.call (final) TTFT guardrail.output 파랑 = LLM 호출 · 초록 = 검색 · 주황 = 도구·가드레일 · 세로선 = 첫 토큰(TTFT) 시점. 두 번의 LLM 호출과 한 번의 웹검색이 직렬로 이어져 종단 지연의 대부분을 차지한다.
한 요청의 트레이스 워터폴 — 스팬별 시작·길이(개략, 총 ≈ 2.4초).

같은 트레이스를 스팬 트리로 보면 부모-자식 구조와 각 스팬의 속성이 드러난다.

chat.request · 2400ms 총 토큰 in 3300 out 550 · 비용 ≈ $0.018 guardrail.input · 30ms 판정: 통과 retrieval · 140ms embed.query · 45ms vector.search · 80ms top-k 5 · 최고점 0.83 llm.call planner · 800ms TTFT 300ms · in 1200 out 150 tool.web_ search · 500ms 결과 3건 · 성공 llm.call final · 870ms TTFT 250ms · in 2100 out 400 guardrail.output · 35ms 판정: 통과

이제 이 트레이스에서 두 가지를 직접 계산해 보자. 먼저 종단 지연. 스팬들이 직렬로 이어졌으므로 종단은 각 구간의 합이다.

$$30 + 140 + 800 + 500 + 870 + 35 = 2375\ \text{ms}$$

루트 스팬이 2400ms이니 그 차이 25ms가 오케스트레이션·직렬화 오버헤드다. 워터폴이 곧바로 말해 주는 것은 직렬 병목이다 — planner 호출(800ms) → 웹검색(500ms) → final 호출(870ms)이 순차로 이어져 종단의 약 90%를 차지한다. 트레이스가 없으면 "느리다"만 알지만, 트레이스가 있으면 최적화 후보가 눈에 띈다. planner와 사내 검색을 병렬로 실행할 수 있나? 웹검색을 계획 단계와 겹쳐 프리페치할 수 있나? "어디를 고칠지"는 스팬 트리에서 나온다.

다음은 비용이다. 두 번의 LLM 호출이 토큰을 썼다. 예시 단가로 입력 100만 토큰당 \$3, 출력 100만 토큰당 \$15를 쓰면(실제 단가는 모델·시점마다 다르며 반드시 자기 모델 기준으로 다시 계산해야 한다),

  • planner: $\frac{1200}{10^6}\times 3 + \frac{150}{10^6}\times 15 = 0.0036 + 0.00225 = \$0.00585$
  • final: $\frac{2100}{10^6}\times 3 + \frac{400}{10^6}\times 15 = 0.0063 + 0.0060 = \$0.0123$

합치면 요청당 약 $\$0.0182$다. 이 한 요청의 비용이 planner보다 final에서 두 배 넘게 나온 것 — 긴 입력 컨텍스트가 원인 — 은 호출별 스팬에 토큰을 붙였기에 보인다. 하루 10만 요청이면 이 한 종류의 요청만 약 $\$1{,}820/일$, 월 5만 달러대다. 요청당 비용은 작아 보여도 트래픽을 곱하면 유닛 이코노믹스의 중심이 되고(7.5), 그 귀인의 원자 단위가 바로 이 LLM 호출 스팬이다.

이 하나의 트레이스에서 이 장의 모든 축이 나온다는 것을 표로 정리하면 이렇다.

지표값(개략)어느 스팬에서이어지는 곳
종단 지연2400ms루트 스팬 길이지연 p95(5.1·5.5)
TTFT(체감)300msplanner 호출의 첫 토큰 이벤트체감 반응성(5.1)
LLM 호출 수2generation 스팬 개수에이전트 스텝 폭주 감지(6.7)
입력·출력 토큰3300 / 550두 호출 합산토큰·비용(7.5)
요청 비용$0.018호출별 비용 합비용/요청·마진(7.5)
검색 최고 유사도0.83vector.search 속성검색 실패·환각 위험(6.4)
도구 성공1/1tool 스팬 상태도구 에러율
가드레일 판정입력·출력 통과guardrail 스팬가드레일 적중률(7.3)
품질 점수(사후) 판정자·피드백온라인 평가(7.6.10)품질 드리프트(7.6.11)

관측성이란 결국 이 레코드를 요청마다 남기고, 시간에 걸쳐 집계·상관·경보하는 일이다.

7.6.6 로깅 — 무엇을 남기고, 무엇으로 회귀를 귀인하나

트레이스가 요청의 "구조"라면 로그는 그 "내용"이다. 요청당 구조화 로그(JSON) 한 줄에 최소한 아래를 담는다. 필드 하나하나가 나중의 어떤 질문에 답하기 위한 것인지를 함께 봐야 한다 — 안 남긴 필드는 그 질문에 영원히 답하지 못한다.

필드예이 필드로 답하는 질문
식별자trace_id·request_id·session_id·user_id(가명)네 축 상관·요청 재구성
모델·버전model, model_version, prompt_version"이 회귀는 어느 변경 때문인가"
파라미터temperature·top_p·max_tokens·seed재현·비결정성 해석(1.7)
토큰input_tokens·output_tokens·cached_tokens비용·캐시 효율(7.5)
지연ttft_ms·total_ms·큐 대기지연 분해(5.1)
비용cost_usd유닛 이코노믹스(7.5)
종료 사유stop·length·content_filter·tool절단·거부 진단
내용프롬프트·응답(샘플·마스킹 후)품질·디버깅·플라이휠(8.1)
컨텍스트검색 문서 ID·도구 결과 요약RAG·에이전트 디버깅
피드백좋아요·수정·재생성(사후 결합)온라인 품질(7.6.10)

이 목록에서 조용히 가장 중요한 두 필드는 model_version과 prompt_version이다. 대부분의 팀이 처음에는 이 둘을 안 남긴다 — 지금 쓰는 모델·프롬프트가 하나뿐이니 굳이 적을 이유가 없어 보인다. 그러다 프롬프트를 고치고 모델을 배포한 어느 날 품질이 떨어졌을 때, 로그에 버전이 없으면 "언제부터 나빠졌나"는 알아도 "무엇 때문인가"에 답할 수 없다. 회귀 귀인(7.6.12) 전체가 모든 레코드에 심긴 이 두 태그 위에 선다. 프롬프트를 코드처럼 버전 관리하고, 배포할 때마다 해시나 순번을 로그에 기록해 두는 습관을 처음부터 들이는 편이 나중에 훨씬 비용이 적게 든다.

seed와 파라미터를 남기는 것도 비슷한 이유다. LLM은 온도가 0이 아니면 같은 입력에도 다른 답을 내므로(1.7), 재현 불가능한 실패를 디버깅하려면 그 요청이 어떤 파라미터로 실행됐는지가 있어야 한다. 종료 사유(finish reason)는 놓치기 쉽지만 값진 필드다 — 응답이 length로 끝났다면 max_tokens에 걸려 답이 잘린 것이고, content_filter로 끝났다면 안전 필터가 개입한 것이며, 이 둘은 사용자에게 "이상한 답"으로 체감되지만 원인이 완전히 다르다. 종료 사유를 집계하면 "절단율이 갑자기 올랐다 → max_tokens 설정이나 프롬프트 길이 문제"처럼 증상을 원인으로 좁힐 수 있다.

7.6.7 샘플링 — 다 남기지 말되, 꼬리는 다 남겨라

풀 트래픽의 프롬프트·응답 전문을 영구 저장하면 저장 비용·프라이버시 노출·인덱싱 부담이 함께 급증한다. 그렇다고 다 버리면 디버깅과 플라이휠(8.1)의 재료가 사라진다. 해법은 신호마다 보존 정책의 층을 달리하는 것이다.

신호보존 정책(예)이유
메트릭·토큰·지연·비용전량(집계는 값싸다)퍼센타일·추세는 표본이 클수록 정확
트레이스 구조정상 요청 일부 + 에러·느린 요청 전량(꼬리 기반)관측의 표적은 꼬리에 있다
프롬프트·응답 전문표본(예: n%) + 이상 케이스 전량저장·프라이버시 비용 절감
사용자 피드백이 달린 요청전량희소하고 값진 신호

원칙은 한 줄로 요약된다. 집계 지표는 전량, 원문은 표본, 단 꼬리는 전량. 여기서 "꼬리"란 에러가 났거나·느렸거나·품질 점수가 낮거나·부정 피드백이 달린 요청이다. 디버깅하고 싶은 대상은 거의 항상 이 꼬리에 있으므로, 표본율과 무관하게 이것들은 전량 보존한다. 이는 지연을 p99로 봐야 하는 것과 같은 논리다(5.1) — 평균적인 요청은 이미 잘 실행되고 있고, 문제는 극단에 숨는다.

원문 샘플링에서 흔한 오해는 "1%만 남겨도 충분한가"다. 답은 목적에 달렸다. 집계 지표(평균 응답 길이, 언어 분포)를 위해서라면 1% 표본으로도 분포가 잘 포착된다. 하지만 희귀한 실패 유형 — 이를테면 만 건에 한 번 나오는 특정 환각 패턴 — 을 검출하려면 1% 표본에는 그것이 백만 건당 한 번꼴로만 걸려 사실상 안 보인다. 그래서 무작위 표본만으로는 부족하고, 품질·피드백 신호로 걸러낸 표적 표본을 함께 남긴다. 무작위 표본은 "평소가 어떤가"를, 표적 표본은 "무엇이 깨졌나"를 담당한다.

7.6.8 PII와 프라이버시 — 로깅의 어두운 면

여기가 로깅에서 가장 부하가 큰 지점이다. 사용자 프롬프트는 개인정보의 저수지다 — 이름·이메일·전화·주소·건강·금융·비밀번호·회사 기밀이 자연어에 섞여 흘러 들어온다. 바로 그 프롬프트·응답을 남겨야 디버깅·품질 분석·플라이휠이 되는데, 무심코 남기면 데이터 유출·규제 위반·내부 오남용의 표면이 된다(9.3). 디버깅에 필요한 것과 프라이버시에 위험한 것이 정확히 같은 데이터라는 이 긴장이 LLM 로깅의 본질이다. 방어는 하나로는 부족하고 층으로 쌓는다.

  • 수집 단계 마스킹(redaction). 로그에 쓰기 전에 PII 탐지기(정규식 + 분류기)로 이메일·카드번호·주민번호 등을 [EMAIL]·[CARD]로 치환한다. 완벽하진 않아도 대량 노출은 막는다.
  • 최소 수집·목적 제한. 품질 분석에 꼭 필요한 것만 남긴다. "혹시 몰라서 다" 저장하는 것은 자산이 아니라 부채다 — 언젠가 유출될 데이터를 늘리는 일이다.
  • 접근 통제·감사. 프롬프트 로그는 최소 권한으로 열되, 누가 언제 무엇을 열람했는지 감사 로그를 남긴다. 외부 공격만큼이나 내부자 오남용이 현실적인 위협이다.
  • 보존 기한·삭제권. 정해진 기간이 지나면 자동 삭제하고, 사용자의 삭제 요청(GDPR·개인정보보호법, 9.3)을 처리할 경로를 둔다. 가명 user_id와 원본 신원의 매핑은 분리 보관해, 로그 자체만으로는 개인을 특정하지 못하게 한다.
  • 판정자·외부 SaaS로 원문을 보낼 때. 온라인 LLM-judge(7.6.10)나 외부 관측 도구에 프롬프트·응답을 넘기면 제3자 모델 API로 민감정보가 나간다. 마스킹 후 전송하거나, 자체 호스팅 판정자를 쓰거나, 표본만 보내는 선택지를 데이터 민감도에 맞춰 고른다.

마스킹에 관해 정직해야 할 지점이 있다. 마스킹은 노출을 줄이는 것이지 없애는 것이 아니다. 자유 텍스트의 PII를 100% 검출하는 탐지기는 존재하지 않고, 정규식으로 안 걸리는 준식별자(quasi-identifier) — 나이·지역·직업의 조합 같은 — 는 문맥으로 재식별(re-identification)될 수 있다. 그래서 마스킹 하나에 기대지 말고 최소 수집·접근 통제·보존 기한을 함께 적용해, 한 겹이 우회돼도 다음 겹이 막게 한다. 그리고 "로그는 내부용이라 괜찮다"는 가장 흔하고 위험한 오해다 — 내부 유출·오남용·규제 조사는 정확히 내부 로그를 겨냥한다. 상세는 9.3에서 다루지만, 로깅을 설계할 때 프라이버시를 사후 대책이 아니라 필드 선택 단계에서부터 반영해야 한다는 것만은 여기서 명시해 둔다.

7.6.9 무엇을 모니터링하나 — 지표 카탈로그

트레이스·로그에서 뽑은 신호를 집계된 메트릭으로 기록해야 "전체적으로 어떤가"가 보인다. LLM 제품이 지켜볼 지표는 네 묶음 — 지연·트래픽/신뢰성·비용·품질/안전 — 으로 나뉜다.

묶음지표어떻게 재나참조
지연TTFT p50/p95/p99첫 토큰까지, 분위수로5.1
TPOT/ITL p50/p95토큰 간 간격5.1
종단 지연 p95/p99요청 전체5.1
goodputSLO를 만족한 처리량5.1·5.5
트래픽·신뢰성RPS·동시성요청/초, 동시 스트림5.5
에러율5xx·타임아웃·빈 응답·절단5.5
재시도·폴백률재시도·모델 폴백 빈도5.6·7.2
큐 대기·포화대기 시간·배치 점유5.5
토큰·비용입력·출력 토큰/요청호출 스팬 합7.5
비용/요청·비용/사용자·비용/세션토큰×단가7.5
캐시 적중률프리픽스·프롬프트 캐시5.6
모델 믹스요청의 모델별 분포5.6·7.5
품질·안전가드레일 적중률(입력·출력)차단·수정 비율7.3
거부율(refusal)모델이 답을 거절한 비율4.3·7.3
온라인 품질 점수판정자·휴리스틱(표본)7.6.10
사용자 피드백 신호좋아요·수정·재생성·이탈7.6.10
품질 드리프트시간에 따른 품질 변화7.6.11

이 카탈로그를 볼 때 두 가지 규율이 지표 자체보다 중요하다. 첫째는 분위수로 보라는 것이다. 지연·토큰·비용·품질 모두 분포가 두터운 꼬리(heavy tail)를 갖기 때문에 평균은 거짓말을 한다. 종단 지연을 예로 들면(5.1),

$$\text{종단 지연} \approx \text{TTFT} + (N_{\text{out}} - 1)\times \text{TPOT}$$

평균 종단 지연이 좋아도 p99가 나쁘면 100명 중 1명은 크게 기다린다. 한 사용자가 여러 요청을 하거나 한 요청이 여러 컴포넌트를 거치면 가장 느린 조각에 의해 제한되므로(꼬리 증폭), 실제 사용자 경험은 평균이 아니라 p95·p99에 산다(5.5).

비용의 꼬리는 특히 극적이라 직접 확인해 볼 값이 있다. 요청당 비용이 이렇게 분포한다고 하자 — 요청의 99%는 짧은 챗이라 $\$0.002$, 나머지 1%는 긴 컨텍스트나 에이전트 루프라 $\$0.20$(100배). 평균 비용/요청은

$$0.99\times 0.002 + 0.01\times 0.20 = 0.00198 + 0.002 = \$0.00398$$

이다. 이 숫자를 읽는 두 방식이 갈린다. 평균 $\$0.004$만 보면 "요청당 비용이 낮다"고 안심하지만, 계산의 두 항을 뜯어 보면 전체 비용의 절반($0.002 / 0.00398 \approx 50\%$)이 요청의 1%에서 나온다. 그리고 중앙값 요청($\$0.002$)만 보고 용량과 가격을 계획했다면 실제 평균 비용을 두 배 과소평가한 것이다. 이 폭주하는 1%는 요청당 토큰 히스토그램과 비용 p99를 봐야 드러나고, 평균 대시보드에는 절대 나타나지 않는다. 폭주의 정체 — 긴 컨텍스트인지, 안 끝나는 에이전트 루프인지 — 는 다시 스팬 트리로 추적해야 나온다.

둘째 규율은 슬라이스로 분할하라는 것이다. 종합 지표는 국소적 문제를 평균에 묻어 숨긴다. "전체 품질은 그대론데 특정 언어·특정 기능·특정 모델버전에서만 무너진" 상황은, 지표를 모델버전·기능·언어·테넌트·사용자 코호트별로 분할해 두어야 보인다. 슬라이스가 없으면 회귀를 국소화할 수 없어 "전체가 3% 나빠졌다"에서 멈추지만, 슬라이스가 있으면 "새 프롬프트 버전이 일본어에서만 12% 떨어뜨렸다"까지 좁힌다.

예 요청당 레코드 (트레이스·로그) 집계 퍼센타일·비율·합 슬라이스 모델버전·기능·언어·테넌트 지연 패널 TTFT·TPOT·종단 p95/p99 비용 패널 비용/요청·토큰·캐시 적중 품질·안전 패널 가드레일·거부· 판정자·피드백 SLO·에러 버짓 초과? 비용 예산 초과? 품질 드리 프트·회귀? 알림·온콜

7.6.10 온라인 품질 평가 — 정답 키 없이 품질을 근사하기

지연·비용은 기계가 직접 잰다. 품질은 다르다. 오프라인 평가(4.2)는 골든셋이라는 고정 문제집 위에서 배포 전에 채점했고, 참조답안이 있었다. 온라인 평가는 실사용 트래픽 위에서, 대부분 참조답안 없이, 배포 후에 품질을 근사해야 한다. 정답 키가 없다는 이 제약이 온라인 품질을 근본적으로 어렵게 만들며, 그래서 하나의 신호에 기대지 않고 성격이 다른 세 갈래를 삼각 측량한다.

① 휴리스틱·규칙 형식·길이·언어· 정규식 JSON 유효·스키마 (6.9) 거부·빈 응답·반복 값싸다 · 전량 적용 얕지만 즉시 ② LLM 판정자 표본에 참조없는 채점 유용성·환각·톤· 안전 RAG면 근거 대조 (6.4) 비쌈 · 표본만 대리지표(4.2 편향) ③ 사용자 피드백 명시: 좋아요· 싫어요·평점 암묵: 수정·재생성· 복사 이탈·후속질문·수용 궁극 신호 · 희소· 편향 불만족자가 더 남긴다 셋을 함께 — 휴리스틱으로 전량 거르고, 판정자로 표본을 깊이 보고, 피드백으로 실제 선호에 정박. 어느 하나도 단독으로는 "품질"이 아니다 — 서로를 보정하는 삼각 측량이다.
온라인 품질 신호의 세 갈래 — 저비용부터 고비용까지.

첫 갈래는 휴리스틱·규칙이고, 값싸고 결정적이라 전량에 붙일 수 있다. 형식 준수(JSON·스키마 유효성, 6.9), 길이 이상(너무 짧거나 max_tokens에 잘림), 언어 불일치(한국어로 물었는데 영어 답), 금지어·반복 루프, 빈 응답·거부 패턴 같은 것들이다. 이런 검사는 판정자를 부를 필요 없이 모든 요청에 걸 수 있어 "품질 사고의 조기 경보" 역할을 한다. 결정적으로 검출되는 것에 값비싼 판정자를 낭비하지 않는 것도 규율이다 — JSON이 깨졌는지는 파서가 즉시 답하는데 굳이 모델에게 물을 이유가 없다.

둘째 갈래는 LLM 판정자이고, 휴리스틱으로 안 검출되는 개방형 품질에 쓴다. 유용성·정확성·환각·톤·안전처럼 규칙으로 정의되지 않는 것들은 LLM-judge로 채점한다(4.2). 온라인의 결정적 차이는 두 가지다. 하나는 참조답안이 거의 없다는 것 — 실사용 프롬프트엔 정답 키가 없으니 참조없는(reference-free) 판정이 기본이고, 그만큼 판정자 자신의 편향에 더 노출된다. 다른 하나는 표본에만 실행한다는 것 — 전량에 판정자를 실행하면 비용·지연·프라이버시가 감당 안 되므로 트래픽의 몇 퍼센트나 이상 신호가 붙은 요청만 채점한다. RAG라면 제공한 문맥 대비 응답이 근거에 충실한지(faithfulness)를 판정자나 NLI로 재는 것이 특히 값지다 — 이것이 환각을 온라인에서 검출하는 가장 직접적인 신호다(6.4).

판정자를 표본에만 실행하는 이유를 비용으로 따져 보자. 하루 10만 요청에서 2%를 판정하면 2000건이다. 판정 한 번이 프롬프트·응답·채점 지침을 합쳐 입력 약 2500 토큰, 출력 약 300 토큰을 쓴다면, 앞의 예시 단가로 판정당 $\frac{2500}{10^6}\times 3 + \frac{300}{10^6}\times 15 = 0.0075 + 0.0045 = \$0.012$이고, 하루 $\$24$다. 만약 전량(10만 건)을 판정했다면 $\$1{,}200/일$로, 이는 앞서 계산한 서비스 자체 비용($\$1{,}820$)에 육박한다. 관측이 서비스만큼 비용이 커지는 것은 본말전도다 — 관측성에도 유닛 이코노믹스가 있고, 표본율은 그 예산에서 역산한다.

셋째 갈래는 사용자 피드백이고, 궁극 기준이지만 희소하고 편향된다. 사람의 실제 반응이 품질의 최종 심판이나(4.2), 프로덕션에서 명시적으로 피드백을 남기는 사용자는 소수이고 대개 편향돼 있다. 명시 신호와 암묵 신호로 나뉜다.

신호종류무엇을 시사함정
좋아요/싫어요·평점명시직접 만족도응답률 낮음, 불만족자가 더 남김(선택 편향)
답변 수정·이어서 고쳐 씀암묵응답이 부족했음사용자 실수와 구분 어려움
재생성(regenerate) 클릭암묵첫 응답에 불만단순 다양성 탐색과 구분 어려움
답변 복사·채택암묵유용했음(긍정)복사가 곧 채택은 아닐 수 있음
대화 이탈·짧은 세션암묵실패했거나 완결됐거나(양의적)해석이 정반대일 수 있음
후속 질문·수용 발화암묵진행이 매끄러움잡음이 많음

암묵 신호는 양날의 검이다. "짧은 세션"은 사용자가 포기한 실패일 수도, 원하는 답을 바로 얻은 성공일 수도 있다 — 같은 데이터가 정반대를 뜻할 수 있다. 그래서 단일 암묵 신호를 인과로 읽으면 안 되고, 여러 신호를 결합하고 명시 피드백·판정자와 교차 검증한다. 명시 피드백조차 불만족한 사용자가 더 자주 남기는 선택 편향을 안고 있어, "싫어요 비율"을 곧 "불만족률"로 읽으면 과대평가한다. 세 갈래를 함께 두는 이유가 여기 있다 — 휴리스틱으로 전량을 거르고, 판정자로 표본을 깊이 보고, 피드백으로 실제 선호에 정박하되, 어느 하나도 홀로 "품질"이라 부르지 않는다.

온라인 신호는 정박(anchor)이 없으면 표류하므로, 오프라인 평가(4.2)와의 순환을 명확히 해 둔다. 배포 전에는 골든셋으로 회귀를 걸러 내고(오프라인 → 온라인), 온라인은 골든셋이 못 담은 실제 분포의 롱테일을 포착한다. 온라인에서 발견한 실패 — 부정 피드백·낮은 판정 점수·환각 사례 — 는 골든셋에 회귀 케이스로 고정한다(온라인 → 오프라인). 이렇게 고정한 케이스가 데이터 플라이휠(8.1)의 입구이고, 다시 실험·릴리스의 배포 전 게이트(8.2)로 이어진다. 그리고 온라인 판정자도 사람 표본과의 일치도(코헨의 $\kappa$·상관)를 주기적으로 재야 믿을 수 있다 — 프로덕션 분포는 골든셋과 다르므로, 오프라인에서 잰 일치도를 그대로 옮겨 쓰면 안 된다(4.2).

실패 사례 고정 배포 전 게이트 개선 재료 사람 표본 프로덕션 트래픽 휴리스틱 (전량) 판정자 (표본) 사용자 피드백 (희소) 온라인 품질 신호 드리프트·회귀 탐지 (7.6.11) 골든셋·회귀 스위트 (4.2) 실험·릴리스 (8.2) 데이터 플라이휠 (8.1) 판정자 보정 (κ·상관, 4.2)

7.6.11 드리프트와 회귀는 원인이 다르다

품질·비용·지연은 우리가 코드를 수정하지 않아도 변한다. 세상이 바뀌고, 우리가 바꾸고, 우리가 의존하는 API가 뒤에서 바뀐다. 이 변화를 조기에 검출하는 것이 드리프트·회귀 탐지인데, 둘은 원인이 달라 처방도 다르다.

드리프트(drift) 는 우리가 아무것도 안 바꿨는데 입력이나 출력 분포가 서서히 이동하는 것이다. 새 사용자층이 유입돼 질문 유형이 바뀌거나, 계절·이벤트로 주제가 몰리거나, 세상의 사실이 바뀌어(뉴스) 모델의 사전지식이 노후화되는 경우다. 변화가 점진적이고 우리 배포와 무관하다. 회귀(regression) 는 우리가 무언가를 바꿔서 — 프롬프트·모델버전·검색 인덱스·가드레일 — 품질이 불연속으로 하락하는 것이다. 대개 배포 시점과 정확히 상관되고 계단처럼 떨어진다. 이 둘을 구분하는 실익은 분명하다. 품질이 떨어졌을 때 그것이 드리프트면 우리 시스템은 멀쩡하고 트래픽이 변한 것이므로 성급한 롤백이 헛수고이고, 회귀면 방금 배포한 것을 되돌려야 한다. 원인을 오진하면 처방이 반대로 간다.

그래서 출력 품질을 채점하기 전에 입력이 변했는지부터 본다. 입력이 이동했다면 품질 변화의 원인이 우리 시스템이 아니라 트래픽일 수 있기 때문이다. 프롬프트의 임베딩(6.2) 분포, 길이 분포, 언어·주제 분포, 도구 사용 빈도 등을 시계열로 추적하고, 분포 이동의 크기를 하나의 숫자로 요약하는 데 흔히 쓰는 것이 모집단 안정성 지수(Population Stability Index, PSI) 다.

$$\text{PSI} = \sum_{i} (q_i - p_i)\,\ln\frac{q_i}{p_i}$$

여기서 $p_i$는 기준 기간, $q_i$는 현재 기간에서 구간 $i$에 속한 비율이다. 각 항 $(q_i - p_i)\ln(q_i/p_i)$는 두 비율이 같으면 0이고, 벌어질수록 커진다. 직접 한 번 계산해 보자. 프롬프트 길이를 다섯 구간으로 나누고, 기준 주와 이번 주의 비율이 아래와 같다고 하자.

길이 구간(토큰)기준 $p_i$현재 $q_i$$q_i - p_i$$\ln(q_i/p_i)$항 기여
0–1000.100.05−0.05−0.6930.0347
100–5000.400.25−0.15−0.4700.0705
500–10000.300.300.000.0000.0000
1000–40000.150.250.100.5110.0511
4000+0.050.150.101.0990.1099

기여를 모두 더하면

$$\text{PSI} \approx 0.0347 + 0.0705 + 0 + 0.0511 + 0.1099 = 0.266$$

이다. 관례적으로 PSI가 0.1 근처면 "주목할 만한 이동", 0.25를 넘으면 "큰 이동"으로 보는데(이 경계값은 절대 기준이 아니라 관행이니 자기 데이터로 보정해야 한다), 0.266은 그 관행선을 넘는다. 표를 읽으면 이동의 방향까지 보인다 — 짧은 프롬프트(0–500)의 비율이 줄고 긴 프롬프트(1000+)가 늘었다. 사용자들이 더 긴 입력을 넣기 시작한 것이다. 이 사실 하나가 여러 관측을 설명한다. 이번 주에 TTFT p95가 나빠졌다면 프리필이 길어진 탓일 수 있고(5.1), 비용/요청이 올랐다면 입력 토큰이 늘어난 탓일 수 있다. 중요한 것은 이것이 우리 변경이 아니라 세상 변화라는 진단이다 — 롤백할 것이 없고, 대신 긴 컨텍스트를 감당할 용량·캐시·라우팅을 손봐야 한다(5.6).

텍스트의 드리프트는 길이 같은 스칼라만으로는 다 안 검출되므로, 임베딩 공간에서 클러스터의 이동이나 새 클러스터의 출현으로도 본다. 어제까지 없던 주제 군집이 갑자기 트래픽의 10%를 차지하면, 그것이 새 사용 사례의 유입이거나 오남용·공격의 신호일 수 있다. PSI는 "얼마나 이동했나"를 요약하고, 임베딩 클러스터는 "어디로 이동했나"를 보여 준다 — 둘을 함께 봐야 드리프트가 명확해진다.

7.6.12 품질 회귀를 변경에 귀인하기

입력이 안 변했는데 품질 지표(판정자 점수·피드백·가드레일·거부율)가 불연속으로 떨어졌다면 회귀를 의심한다. 회귀 탐지의 핵심은 품질 시계열을 변경 이력과 겹쳐 보는 것이다. 모든 레코드에 model_version·prompt_version을 심어 뒀으므로(7.6.6), 하락 시점을 배포 이벤트에 정확히 붙일 수 있다.

정상 범위(신뢰구간) 0.0 2.5 5.0 7.5 10.0 12.5 60 70 80 90 100 시간 (일) → 품질 (%) 배포: prompt_v7 → v8 회귀 경보 (하한 이탈)
품질 회귀 탐지 — 지표 시계열에 배포 이벤트를 겹친다(개략). 배포 직후 품질이 정상 범위 아래로 불연속 하락 → prompt_v8에 귀인 → 롤백/수정(8.2).

귀인을 정확히 하려면 몇 가지 규율이 필요하다. 첫째, 한 번에 하나씩 배포하고 태깅한다. 여러 변경을 한 릴리스에 뭉치면 품질이 떨어져도 무엇 때문인지 가를 수 없다 — 프롬프트 때문인지 모델 업그레이드 때문인지 섞여 버린다. 각 배포에 버전 태그를 남겨 시계열에 마커로 추가하면 시각적으로 원인이 특정된다. 둘째, 시간 상관만으로는 인과를 확정하지 못한다. 배포와 동시에 트래픽 구성이 바뀌었을 수도 있으니(교란), 새 버전을 소량 트래픽에 먼저 적용하는 카나리로 같은 시간대의 대조군과 비교해야 회귀를 인과적으로 식별한다 — 이것이 8.2의 핵심이고, 관측성은 그 실험의 측정 계기다. 셋째, 카나리의 품질·에러·비용이 대조군 대비 임계 이상 나빠지면 자동으로 롤백하는 게이트를 건다. 관측 신호가 곧 게이트의 입력이 된다.

여기서 반드시 살펴봐야 할 함정은 LLM 지표가 비결정적으로 변동한다는 점이다(1.7). 판정자 점수는 표본마다 변동하고, 온도가 있는 모델은 같은 입력에도 다른 답을 낸다. 그래서 "지표가 3% 떨어졌다"가 실제 회귀인지 표본 잡음인지를 가려야 하고, 이를 위해 신뢰구간이 필요하다. 예를 들어 온라인 판정자의 통과율 기준선이 90%인데 하루에 200건을 표본으로 채점한다고 하자. 비율의 표준오차는

$$\text{SE} = \sqrt{\frac{p(1-p)}{n}} = \sqrt{\frac{0.9\times 0.1}{200}} = \sqrt{0.00045} \approx 0.0212$$

이다. 95% 신뢰구간의 반폭은 $1.96\times 0.0212 \approx 0.042$, 즉 $\pm 4.2\%$다. 이 말은, 어제 통과율이 90%로 찍혔어도 실제 값은 표본 잡음만으로 85.8%~94.2% 사이 어디든 될 수 있다는 뜻이다. 그렇다면 통과율이 90%에서 87%로 3%p 떨어진 하루를 보고 회귀 경보를 울리는 것은 성급하다 — 그 3%p는 잡음 대역($\pm 4.2\%$) 안에 있다. 잡음을 절반으로 줄여 SE를 1%로 만들려면 표본을 $n \approx 900$까지 늘려야 한다($\sqrt{0.09/900}=0.01$). 이 계산이 두 가지를 말해 준다. 하나는 작은 표본의 하루치 등락에 하드 게이트를 걸면 안 된다는 것 — 이동평균과 신뢰구간으로 추세를 봐야 한다. 다른 하나는 더 예민하게 회귀를 검출하고 싶으면 표본을 키워야 하고, 그만큼 판정 비용이 든다는 것 — 여기서도 관측의 유닛 이코노믹스가 다시 나온다.

마지막으로, 우리가 아무것도 안 바꿨고 입력도 안 변했는데 품질·형식이 변했다면 모델 뒤의 조용한 변경을 의심한다. 매니지드 API의 모델은 버전 이름이 고정돼 있어도 벤더가 뒤에서 가중치나 시스템 프롬프트를 갱신할 수 있고, 이때 우리 쪽 코드는 그대로인데 출력이 미묘하게 달라진다. 이 가설을 검증하는 길은 골든셋을 재실행해 배포 전 잰 점수와 지금 점수를 비교하는 것이다 — 우리 것을 안 바꿨는데 골든셋 점수가 움직였다면 원인은 우리 밖에 있다(4.2).

7.6.13 알림·대시보드·SLO로 규율하기

지표를 다 모았어도 언제 사람을 깨울지 정하지 않으면 두 실패 중 하나에 빠진다 — 아무도 안 보거나(대시보드만 있고 알림이 없다), 너무 자주 깨워 아무도 안 믿거나(알림 피로). 여기서 5.5의 SLO·에러버짓 틀을 그대로 빌려 온다.

대시보드는 독자별로 나눈다. 하나에 모든 지표를 담으면 아무도 자기에게 필요한 것을 못 찾아 결국 아무도 안 본다.

대시보드독자핵심 지표
서비스 헬스온콜 엔지니어에러율·지연 p95/p99·포화·재시도(5.5)
품질제품·ML 팀판정자 점수·피드백·가드레일·거부·드리프트
비용·유닛 이코노믹스제품·재무비용/요청·비용/사용자·토큰·캐시 적중·모델믹스(7.5)
실험·릴리스릴리스 담당카나리 vs 대조군·버전별 슬라이스(8.2)

SLO(Service Level Objective) 는 지켜야 할 목표(예: TTFT p95 ≤ 1초를 99%의 시간 동안 달성)이고, 에러버짓은 그 SLO 위반을 허용하는 여분이다(5.5). LLM 제품의 특수성은 SLO를 지연만이 아니라 품질과 비용에도 세울 수 있다는 데 있다. 지연 SLO는 TTFT·TPOT·종단을 분리해 p95/p99로 걸고 goodput으로 재는 것이 정직하다. 품질 SLO는 아직 신흥이지만 "온라인 판정 점수의 이동평균이 기준선 대비 X% 이상 하락하지 않음"이나 "가드레일 오탐률 < Y%" 같은 형태로 세울 수 있다 — 다만 품질은 대리지표이고 표본 잡음이 크므로, 앞 절의 신뢰구간 논리에 따라 하드 게이트보다는 추세 경보로 시작하는 편이 안전하다는 것이 내 견해다. 비용 SLO는 "비용/요청 p95 < Z"나 "일일 토큰 예산 초과 시 경보"로 폭주 요청과 프롬프트 부풀림을 조기에 검출한다.

알림을 거는 원칙은 증상이 아니라 SLO 위반(과 그 조짐)에 건다는 것이다. "CPU 80%"는 그 자체로는 알림 대상이 아니다 — 그것이 사용자 경험 SLO를 위협할 때만 사람을 깨운다. 그리고 두 종류의 지표를 다르게 쓴다. 이미 발생한 뒤에 오르는 후행 지표(p99 지연 급증)로는 위반을 확인해 온콜을 호출하고, 터지기 전에 오르는 선행 지표(큐 깊이·포화)로는 자동 대응 — 오토스케일·폴백·유입 제어(5.5) — 을 먼저 발동해 조기에 반응한다. 선행 지표로 막으면 후행 지표가 애초에 발생하지 않는다.

아니오 조짐(선행 지표) 위반(꼬리 지표) 관측 신호 지연·에러·비용·품질 SLO 위반 또는 조짐? 정상 — 에러버짓 소진 추적 자동 대응 오토스케일·폴백·유입 제어 (5.5) 온콜 호출 런북·원인 분석 트레이스로 심층 추적 (7.6.5) 수정·롤백(8.2)

알림 피로는 관측 체계의 최대 적이다. 거짓 경보가 잦으면 사람은 실제 경보까지 무시하게 되고, 그러면 알림이 없느니만 못하다. 규율은 네 겹이다 — SLO 기반으로만 깨우고, 심각도(즉시 호출 vs 기록만)를 나누고, 비결정성·표본 잡음을 신뢰구간으로 걸러 작은 등락에 경보를 걸지 않고, 경보마다 런북(runbook)을 붙여 "울리면 무엇을 하나"를 명시한다. 런북이 없는 경보는 한밤중에 깨워 놓고 무엇을 하라는지 안 알려 주는 것과 같다.

7.6.14 LLMOps 관측 도구의 지형

구체 제품의 서열이나 추천은 이 책의 범위를 벗어나고(BK-04), 무엇보다 그런 목록은 몇 달 단위로 노후화된다. 대신 범주로 지형을 정리해 두면 무엇을 조합할지 스스로 판단할 수 있다. 실제 스택은 대개 한 제품이 아니라 여러 범주의 조합이다.

범주무엇을 하나다른 축과의 관계
범용 관측·APM메트릭·로그·트레이스 저장·대시보드·알림(일반 서비스용)시스템 헬스·인프라. LLM 특화 신호는 위에 추가해야 함
분산 트레이싱 표준·수집스팬 계측·전파·수집(OpenTelemetry 등)LLM SDK가 이 위에 LLM 스팬을 추가함. 벤더 중립
LLM 관측·트레이싱 특화LLM 호출·RAG·에이전트 궤적을 스팬으로, 토큰·비용·프롬프트/응답 로깅7.6.3~7.6.4의 트레이스 대상. 프롬프트 버전·플레이그라운드 결합이 흔함
온라인/오프라인 평가·판정자판정자 실행·골든셋·회귀·일치도 보정4.2·7.6.10의 품질 신호를 운영
피드백·주석(annotation)사용자·전문가 피드백 수집, 실패 라벨링, 데이터셋화7.6.10·8.1의 플라이휠 입구
가드레일·안전 모니터링정책 위반·유해성·PII 탐지·적중률7.3·9.3와 결합
비용·FinOps토큰·비용 귀속·예산·이상 탐지7.5의 유닛 이코노믹스

도구를 고를 때 나는 네 가지를 먼저 본다. 하나는 개방 표준 우선이다 — 계측을 OpenTelemetry 같은 표준으로 해 두면 관측 백엔드를 갈아탈 때 애플리케이션을 다시 계측할 필요가 없고, 특정 벤더 SDK에 깊이 묶일수록 잠금 비용이 커진다. 둘은 데이터 거주지·프라이버시다 — 프롬프트·응답이 외부 SaaS로 나가면 7.6.8의 PII·9.3 규정에 걸리므로, 민감 도메인이면 자체 호스팅 가능 여부가 결정타가 된다. 셋은 상관이다 — 범주별로 최고의 도구를 골라도 트레이스·로그·메트릭·품질·비용이 같은 식별자로 이어지지 않으면 심층 추적이 끊기니, 도구 개수보다 식별자 일관성이 중요하다. 넷은 작게 시작하는 것이다 — 요청당 트레이스(토큰·비용·모델버전이 붙은)와 사용자 피드백 수집이면 초기엔 충분하고, 정교한 온라인 판정자와 드리프트 파이프라인은 제품이 커지면서 점진적으로 추가하면 된다. 구체 도구명·기능·가격은 빠르게 변하니 이 절을 특정 제품 추천으로 읽지 말고, 범주와 "무엇을 이어야 하는가"의 원리만 가져가라. 선정은 자기 제약 — 프라이버시·규모·기존 스택·예산 — 에서 도출하는 것이 맞다.

7.6.15 이 장의 도구가 거짓말하는 지점들

지금까지의 도구는 강력하지만 근본적 한계가 있고, 그 한계를 흐리면 관측이 오히려 잘못된 확신을 준다. 마지막으로 이 함정들을 정면으로 본다.

자동 품질 지표는 전부 대리지표다. 온라인 판정자·휴리스틱·암묵 피드백은 "진짜 품질"의 근사일 뿐, 품질 그 자체가 아니다. 판정자는 자기 편향 — 위치·장황함·자기선호·형식 선호 — 을 그대로 전이하고(4.2), 참조답안이 없는 온라인 설정에선 더 취약하다. 여기에 굿하트의 법칙이 작동한다 — 대리지표를 최적화하면 실제 품질이 아니라 대리지표의 편향까지 함께 최적화된다. 판정자가 긴 답을 선호한다면, 판정자 점수를 높이려는 최적화는 실제로 더 좋은 답이 아니라 더 긴 답을 만드는 쪽으로 시스템을 밀 수 있다. 그래서 온라인 판정자도 사람 표본으로 주기 보정하고(4.2), 중요한 결정을 판정자 단독으로 내리지 않는다.

암묵 피드백은 양의적이다. "짧은 세션·재생성·이탈"은 실패의 신호일 수도 성공의 신호일 수도 있으니, 단일 신호를 인과로 읽지 말고 결합·교차검증한다. 명시 피드백은 불만족자가 더 자주 남기는 선택 편향을 안고 있어 그대로 만족률로 읽으면 안 된다.

로깅은 프라이버시·보안의 표면이다. 프롬프트·응답에는 PII와 기밀이 섞이고, 마스킹은 노출을 줄일 뿐 없애지 못하며, "내부 로그라 괜찮다"는 위험한 오해다. 최소 수집·마스킹·접근 통제·보존 기한을 층으로 쌓아야 하고, 판정자·외부 SaaS로 원문을 보낼 때 특히 조심해야 한다(7.6.8·9.3).

관측 자체에도 비용과 프라이버시가 있다. 풀 트레이스·전문 로깅·전량 판정은 저장·연산·프라이버시 비용을 만든다 — 앞서 계산했듯 전량 판정은 서비스 자체 비용에 육박할 수 있다. 관측성에도 유닛 이코노믹스가 있으니, 샘플링과 보존 정책으로 관측 비용이 서비스 비용을 잠식하지 않게 한다.

그리고 이 장을 연 통념으로 돌아가자. "지연만 보면 된다"는 틀렸다. 지연은 세 축 중 하나이고, 지연이 완벽해도 비용이 마진을 잠식하거나 품질이 드리프트하면 제품은 실패한다. 200 OK는 "성공"이 아니라 "서버가 응답했다"일 뿐이다. 이 장에서 검증된 공학이라 부를 수 있는 것 — 요청을 스팬 트리로 분해하면 병목이 보인다, 분위수·슬라이스로 봐야 꼬리와 국소 회귀가 드러난다, 결정적 검사는 판정자보다 먼저 건다, PSI로 입력 드리프트를 우리 변경과 가른다 — 은 방법론적 사실이다. 반면 판정자-인간 일치도의 구체적 수치, PSI 0.25 같은 임계, 암묵 신호의 해석력은 자기 제품·데이터에서 다시 재고 보정해야 할 조건부 값이다. 이 둘을 섞지 않는 것이 관측 데이터를 정직하게 읽는 마지막 규율이다.

관측성은 제품을 지켜보는 일이지만, 그 신호를 개선으로 환류시키는 순환이 다음 부의 주제다. 여기서 모은 사용 데이터·실패 사례·피드백을 프롬프트·검색·모델 개선의 입력으로 환류시키는 데이터 플라이휠(8.1)이 바로 이어지고, 그 개선을 카나리·A/B·롤백 게이트로 안전하게 배포하는 규율(8.2)이 이 장의 회귀 탐지를 정식화한다. 관측성은 그 모든 운영 순환의 눈이다.