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

제8부 운영·LLMOps

8.2 실험 · A/B · 릴리스 · 회귀 방지

이 장이 남기려는 문장은 하나다. 전통 소프트웨어에서 회귀는 크래시로 오지만, LLM 시스템에서 회귀는 "답이 조금 나빠짐"으로 온다 — 그것도 같은 입력에 매번 다른 답을 내는 시스템 위에서. 크래시는 로그에 빨간 줄로 표시되고 스택 트레이스가 범인을 가리킨다. 반면 "요약이 살짝 장황해졌다", "특정 유형 질문에서 환각이 2%p 늘었다", "신모델이 JSON 끝에 마침표를 붙이기 시작해 다운스트림 파서가 조용히 깨졌다" 같은 회귀는 어디에도 빨간 줄을 남기지 않는다. 눈에 띌 무렵이면 이미 몇 주 동안 사용자에게 나쁜 답을 대량으로 배포한 뒤다.

여기에 세 가지 어려움이 겹친다. 첫째, 출력이 비결정적이다 — 어제 답과 오늘 답이 다른 것이 내 변경 때문인지 단순히 샘플링 잡음 때문인지 구분할 수 없다(1.7). "이 커밋이 이 버그를 고쳤나"라는, 유닛테스트가 당연히 답해 주던 질문에 확답을 못 한다. 둘째, 품질에 정답 키가 없다. 회귀가 "틀림/맞음"의 이진이 아니라 연속적인 품질 저하로 오니, 무엇과 비교해 얼마나 나빠졌다고 말할 기준선 자체를 따로 세워야 한다. 셋째, 변경의 파급이 비국소적이다. 전통 코드에서 함수 하나를 고치면 그 호출부만 영향받고 정적 분석으로 추적된다. LLM에서는 시스템 프롬프트에 한 줄을 넣으면 요약·코딩·번역·안전거부까지 전부 미묘하게 바뀌고, 무엇이 바뀔지 배포 전에 정적으로 알 방법이 없다.

이 세 성질 때문에, 배포에 대한 두 가지 자연스러운 본능이 정확히 틀린다. 하나는 "일단 내보내고 지켜보자"다 — 지켜봐서 회귀를 검출하려면 그 회귀가 이미 사용자에게 도달한 뒤여야 하고, 비결정성 위에서는 "지켜본다"가 잡음과 신호를 가려내지 못한다. 다른 하나는 "새 모델이 벤치마크가 더 높으니 그냥 바꾸자"다 — 뒤에서 자세히 깨겠지만, 종합 벤치가 높다는 것은 당신의 태스크·프롬프트·형식·가드레일 조합에서 낫다는 보장이 전혀 아니다. 이 장은 이 두 본능을 규율로 대체한다. 배포 전에 실험실에서 막고(오프라인 게이트), 사용자에게 안 보이게 섀도 모드로 재보고(섀도), 소수에게 노출하며 지표를 재고(카나리·A/B), 지키며 늘리고, 나쁘면 되돌린다(롤아웃·롤백). 그 밑에는 비결정성 위에서 회귀를 통계적으로 판정하는 법과, 무엇이 배포됐었는지를 재현하는 버전 관리가 기반이 된다. 상시 온라인 품질 모니터링은 7.6이, 파인튜닝 모델 자체의 라이프사이클은 8.3이, 모델을 소유할지 빌릴지라는 더 큰 선택은 9.1이 이어받는다. 이 장에 나오는 롤아웃 비율(1%→5%→25%)이나 "신기성 효과는 1~2주"는 감각을 주기 위한 대표값일 뿐, 제품·트래픽·리스크에 따라 크게 달라진다.

8.2.1 무엇을 실험하나 — 변경의 표면과 파급 반경

배포를 안전하게 하려면 먼저 "무엇이 바뀌는가"를 정확히 파악해야 한다. LLM 시스템의 변경은 여러 층에서 오고, 각 층은 파급 반경(blast radius) — 얼마나 많은, 얼마나 예측 불가능한 행동을 바꾸는가 — 이 다르다.

변경 표면예파급 반경왜 위험한가
프롬프트시스템 프롬프트 문장 추가·재배열, few-shot 예시 교체넓고 비국소적한 줄이 무관해 보이는 태스크의 톤·형식·거부율을 바꾼다
모델프로바이더 버전 업(v1→v2), 파인튜닝 체크포인트 교체, 라우팅 대상 변경시스템 전체행동 분포 전체가 바뀐다. 비결정·프롬프트 민감이 겹쳐 재보정 필요
검색(RAG)임베딩 모델·청킹·top-k·리랭커·인덱스 갱신근거 기반 태스크 전반검색이 바뀌면 컨텍스트가 바뀌고, 같은 프롬프트·모델도 다른 답을 낸다
정책·가드레일안전 임계·거부 규칙·모더레이션 분류기안전 ↔ 유용성 경계너무 조이면 정당한 요청 거부↑, 너무 풀면 유해 출력↑ — 양방향 회귀
파라미터·설정온도·top-p·max_tokens·컨텍스트 예산·타임아웃국소적이나 미묘온도를 높이면 다양성↑·일관성↓·비결정성↑, 형식 준수·재현성에 직결
도구·에이전트함수 스키마·도구 추가·계획 로직도구 사용 경로툴 선택·인자 생성이 바뀌어 실패 모드가 새로 생긴다

이 표에서 정말 중요한 것은 개별 항목이 아니라 두 성질이다. 첫째, 변경은 거의 항상 비국소적이다. 왜 그런가를 메커니즘으로 정리해 두면 나머지가 다 따라온다. 트랜스포머는 프롬프트 토큰 전체를 하나의 컨텍스트로 받아 다음 토큰 분포를 만든다(1.4). "간결하게 답하라"는 여섯 글자가 컨텍스트에 들어가면 그 분포가 모든 후속 생성에서 미묘하게 이동한다 — 요약만 짧아지는 것이 아니라 코드 주석이 사라지고, 안전거부의 설명이 짧아져 무뚝뚝해지고, 목록을 쓰던 자리에 산문이 온다. 어느 것도 프롬프트를 고친 사람이 의도하지 않았고, 어느 것도 정적 분석으로 예측되지 않는다. 그래서 LLM의 회귀 방지는 "영향받은 코드만 테스트"가 원리적으로 불가능하고, 대표 트래픽 전체를 다시 채점하는 수밖에 없다. 전통 CI의 "바뀐 파일에 대한 테스트만 돌린다"는 최적화가 여기서는 성립하지 않는다.

둘째, 변경끼리 결합(coupled)돼 있다. 당신의 프롬프트는 구 모델의 버릇에 맞춰 튜닝돼 있다 — 이 few-shot 예시가 이 모델에서 이 형식을 유도한다는 것을 시행착오로 찾은 것이다. 그 프롬프트를 신 모델에 그대로 추가하면 다르게(때로 나쁘게) 작동한다. 검색도 마찬가지다. top-k를 6에서 10으로 늘리면 컨텍스트가 길어져 같은 프롬프트·모델이 다른 답을 낸다. 즉 프롬프트·모델·검색·정책은 조합 전체가 하나의 시스템이고, 하나만 바꿔도 새 시스템이다. 이것이 "프롬프트만 살짝 고쳤으니 위험 없다"가 가장 흔한 착각인 이유다 — 프롬프트는 가장 값싸게, 가장 자주 바꾸면서, 가장 예측 불가능하게 행동을 바꾸는 표면이다. 그래서 프롬프트 변경도 모델 변경과 같은 게이트를 통과시켜야 한다.

변경 표면 변경 하나가 분포 전체를 교란한다 프롬프트 모델 버전 검색·RAG 정책·가드레일 파라미터·온도 결합 시스템 (프롬프트×모델×검 색×정책) 행동 분포 (품질·형식·거부· 비용·지연) 사용자 경험 대표 트래픽 전체를 재평가해야

8.2.2 릴리스 파이프라인 — 값싼 데서 최대한 걸러라

개별 기법에 들어가기 전에 전체 골격을 세우자. 골격은 다섯 마디다 — 실험실에서 막고, 그림자에서 재보고, 소수에게 흘리고, 지키며 늘리고, 나쁘면 되돌린다. 각 마디는 서로 다른 위험을 서로 다른 비용으로 검출한다.

통과 이상 없음 괴리·오류 가드레일 위반 건강 · A/B 지표 우세 위반 변경안 (프롬프트·모델·검색· 정책 v_new) 오프라인 게이트 회귀 스위트· 판정자 (회귀 감지) 차단 · 원인 분석 · 수정 섀도 배포 트래픽 미러링·사용자 영향 0 출력·지연·비용 비교 카나리 실트래픽 1~5% 가드레일 상시 감시 자동 롤백 기능 플래그 off 점진 롤아웃 5%→25%→50%→100% (단계마다 가드레일 재확인) 전량 배포 · 신 기준선 확정
임시

각 단계가 무엇을 검출하고 무엇을 못 검출하는지가 이 파이프라인의 전부다.

단계무엇을 포착하나사용자 노출비용놓치는 것
오프라인 게이트골든셋 위 명백한 회귀(품질·형식·안전)없음낮음(값싼 반복)실트래픽 분포·2차 효과
섀도실트래픽 위 크래시·지연·비용·형식 붕괴, 두 버전의 괴리없음중간(2배 추론)사용자 반응·품질 선호
카나리소수 실사용자 위 가드레일 위반·품질 하락소수중간장기·희귀 효과
A/B + 롤아웃통계적으로 유의한 지표 개선/회귀점증높음(트래픽·시간)신기성 뒤 장기효과, 네트워크 효과

핵심 원칙은 뒤로 갈수록 비용이 크고 느리다는 것이다. 그래서 값싼 게이트에서 최대한 걸러야 한다. 이것을 숫자로 확정해 보자. 오프라인 게이트에서 회귀 하나를 검출하는 비용은 골든셋 200케이스를 신·구 두 버전에 각 5회 실행하는 것 — 대략 2,000회 생성이고, 몇 분에 몇 달러다. 같은 회귀가 게이트를 빠져나가 카나리 1%까지 갔다면, 하루에 하나의 서비스가 50만 요청을 처리한다 할 때 1%인 5,000명이 나쁜 답을 받고서야 가드레일 경보가 발생한다. 게이트를 아예 건너뛰고 전량 배포했다면 하루 만에 50만 명 전원이다. 즉 같은 결함을 검출하는 데 드는 "나쁜 응답 수"가 오프라인이면 0, 카나리면 수천, 전량이면 수십만 — 100배에서 1,000배 차이다. 이 격차가 파이프라인의 존재 이유다.

반대 극단도 틀렸다. 오프라인만 믿고 바로 전량 배포하면, 오프라인이 원리적으로 못 보는 온라인 괴리(8.2.13)에 당한다. 게이트가 보는 것은 "골든셋 분포 위의 채점 가능한 품질"뿐이고, 실트래픽 분포·사용자 행동 변화·지연 같은 2차 효과는 못 본다. 그러니 게이트는 배포의 필요조건이지 충분조건이 아니고, 점진 배포는 게이트가 못 본 것을 검출하는 안전망이다. 둘 다 있어야 한다.

8.2.3 오프라인 평가 게이트 — 배포 전 회귀 차단

첫 방어선은 배포 전 실험실 채점이다. 4.2에서 세운 오프라인 스위트 — 골든셋, 결정적 검사, LLM 판정자, 안전 분류기 — 를 여기서 릴리스 게이트로 승격한다. 소프트웨어 CI가 테스트 실패 시 머지를 막듯, 회귀 스위트가 하락을 감지하면 배포를 막는다.

게이트는 성격이 다른 세 종류의 검사를 층으로 쌓는다. 맨 아래는 결정적 검사다 — JSON 스키마 유효성, 인용 형식 정규식, 정확 일치, 단위테스트. 비결정성이 없거나 작아 통과/실패가 명확하고, 형식·정확성의 상당 부분이 여기서 검출된다(6.9). 가장 값싸고 가장 신뢰할 수 있으니 검출할 수 있는 것은 최대한 여기로 밀어야 한다. 그 위가 지표 임계다 — 골든셋 위 정확도, 판정자 승률, 환각률, 거부율. 이것은 비결정적이라 단일 값이 아니라 분포·신뢰구간으로 판정한다(8.2.4). 맨 위가 안전 게이트다 — 유해·정책 위반·탈옥 취약(4.3·7.3). 여기가 다른 두 층과 등급이 다르다. 품질은 조금 나빠져도 사람이 비교 평가해 롤아웃할 수 있지만, 안전 회귀는 종합 개선과 무관하게 무조건 차단하는 하드 게이트다.

회귀 스위트는 "한 번 고친 건 다시 깨지지 않게" 지키는 고정 테스트 집합이다. LLM 회귀는 두 결로 온다. 프롬프트 회귀는 프롬프트를 고쳐 A는 좋아졌는데 B가 나빠지는 것이다 — "간결하게"를 넣었더니 요약은 좋아졌으나 코드 설명이 불충분해지는 식. 모델 회귀는 모델 버전을 업그레이드해 종합 벤치는 올랐으나 특정 슬라이스(우리 도메인·특정 언어·긴 컨텍스트)에서 떨어지는 것이다. 둘 다 종합 점수로는 안 보인다는 것이 함정이다. 그래서 게이트의 첫 번째 규칙은 슬라이스로 판정한다이다. 골든셋을 카테고리로 분할해(사실 질의, 다단계 추론, 잡담, 근거 없는 질의…) 슬라이스별로 기준선과 비교하고, 어느 슬라이스든 유의하게 떨어지면 회귀로 취급한다. 종합 점수만 보면 "A +10%, B −15%"가 상쇄돼 -15%를 받는 사용자층을 조용히 버리게 된다.

아니오 예 있음 없음 & 목표 개선 v_new를 골든셋 위에서 실행 (N회 반복 — 비결정성 평균화) 결정적 검사 형식·정확· 인용 지표 집계 슬라이스별 승률·환각· 거부 안전 검사 유해·정책· 탈옥 기준선 v_base와 비교 안전 회귀? 어느 슬라이스든 통계적 유의 하락? 즉시 차단 (하드 게이트) 게이트 통과 → 섀도로
오프라인 평가 게이트 — 결정적 검사·지표·안전을 거쳐 슬라이스별로 판정

두 번째 규칙은 안전은 하드, 품질은 소프트다. 안전 회귀는 자동 차단이지만, 국소 품질 회귀는 "개선 폭 대비 회귀 폭"을 사람이 비교 평가할 여지가 있다. 내 견해로는, 국소 회귀가 작고 대상 슬라이스가 드물면 통과시키되 모니터링 태그를 달아 카나리에서 재확인하는 것이 실용적이다 — 모든 국소 회귀를 무조건 차단하면 어떤 변경도 통과하지 못한다. 세 번째 규칙은 기준선을 고정·버전화한다이다. "기준선 대비 하락"이 의미를 가지려면 기준선의 점수가 같은 골든셋·같은 판정자·같은 온도로 측정돼야 한다. 판정자 모델이나 골든셋이 조용히 바뀌면 비교 자체가 무의미해진다(4.2·8.2.5).

8.2.4 비결정성 위에서 회귀를 판정하기

여기가 LLM 회귀 방지가 전통 소프트웨어와 근본적으로 갈리는 지점이다. 같은 입력에 같은 출력이 안 나오면, "이 변경이 회귀인가"는 원리적으로 확률적 질문이 된다.

비결정성은 여러 곳에서 온다. 가장 뚜렷한 것은 온도 > 0의 샘플링이다 — top-k·top-p·temperature는 본질적으로 확률적이라 매 호출 다른 토큰을 뽑는다(1.7). 그런데 온도 0이라고 완전 결정적인 것도 아니다. greedy 디코딩조차 완벽히 재현되지 않는 경우가 있는데, 원인은 부동소수점의 비결합성이다. 실수 덧셈은 이론상 결합법칙을 따르지만 부동소수점에서는 $(a+b)+c \ne a+(b+c)$ 가 되는 경우가 있고, GPU 커널이 리덕션(합산) 순서를 배치 크기·시퀀스 길이에 따라 바꾸면 마지막 비트가 달라진다. 그 미세한 차이가 두 후보 토큰의 로짓이 거의 붙어 있을 때 argmax를 뒤집으면, 그 지점부터 생성이 다른 길로 갈라진다. 여기에 GPU·드라이버·라이브러리 버전 차이, MoE의 배치 의존 라우팅까지 겹친다. 그래서 "온도 0이면 재현된다"는 흔한 믿음은 부분적으로만 사실이다 — 대체로 재현되지만 보장은 아니고, 하드웨어·구현에 따라 다르다. 여기에 외부 상태(RAG 인덱스 갱신, 도구 호출 결과, 시각 의존 컨텍스트)와 핀하지 않은 프로바이더 측 모델 업데이트까지 더하면, 완전한 재현은 통제 밖의 요인들에 걸려 있다.

이 위에서 전통 소프트웨어의 스냅샷 테스트 — 출력 문자열을 저장해 두고 정확 일치 비교 — 를 그대로 쓰면 거의 항상 실패한다. 정상 변동조차 "불일치"로 뜨니 스위트가 거짓 경보로 붕괴한다. 판정 방식을 비결정성에 맞게 바꿔야 한다.

판정 방식어떻게언제 적합한계
정확 스냅샷출력 문자열 완전 일치온도 0 + 완전 고정 환경의 극히 일부정상 변동에도 깨짐 — 대개 부적합
구조·형식 검사JSON 파싱·스키마·필드 존재·타입형식이 계약인 경우내용 품질은 못 봄
허용오차 스냅샷수치 ±ε, 집합 포함, 정규화 후 비교수치·추출 태스크임계 설정이 자의적
의미 동등임베딩 유사도·NLI·판정자로 "의미가 같나"자유 생성판정자 비용·편향
분포 비교N회 반복해 지표의 평균·분산을 비교회귀 게이트 전반반복 비용, 표본 필요

핵심은 단일 출력이 아니라 분포를 비교하는 것이다. 비결정 시스템에서 한 번 실행한 결과는 표본 하나일 뿐이라, 그것 하나로 회귀를 판정하는 것은 동전을 한 번 던져 앞뒷면 확률을 논하는 것과 같다. 각 버전을 여러 번 실행해 지표의 분포를 만들고, 그 분포가 유의하게 다른지 본다. 신버전 지표 평균 $\bar m_{\text{new}}$ 이 기준선 평균에서 표준오차의 여러 배만큼 낮을 때만 회귀로 판정한다.

$$\text{회귀로 판정} \iff \bar{m}_{\text{new}} < \bar{m}_{\text{base}} - z\,\sqrt{\frac{s_{\text{new}}^2}{N} + \frac{s_{\text{base}}^2}{N}}$$

여기서 $s^2$ 은 반복 간 분산(비결정성이 만드는 잡음)이고 $N$ 은 반복 횟수다. 잡음이 클수록(온도 높음) 표준오차가 커져 더 많은 반복이 필요하다.

8.2.4.1 몇 번 반복해야 하나 — 수계산

"N회 반복하라"는 말은 N이 몇이어야 하는지 답하기 전엔 공허하다. 직접 계산해 보자. 골든셋이 $M$ 개 항목이고, 각 항목을 $N$ 회 실행해 품질 점수(0~1)를 매긴다고 하자. 한 번의 생성이 갖는 샘플링 잡음의 표준편차를 $\sigma$ 라 하면, 항목 하나의 평균은 $N$ 회로 잡음이 $\sigma/\sqrt N$ 로 줄고, 골든셋 전체 평균 $\bar m$ 의 샘플링 잡음 분산은 $\sigma^2/(MN)$ 이다.

여기서 결정적인 설계 선택이 하나 있다. 신·구 버전을 같은 골든셋 항목으로 비교하는 짝지음(paired) 설계를 쓰면, 항목마다 원래 다른 난이도에서 오는 항목 간 분산이 두 버전의 차이 $\bar m_{\text{new}} - \bar m_{\text{base}}$ 에서 상쇄된다. 남는 것은 두 버전 각자의 샘플링 잡음뿐이라, 차이의 표준오차는

$$\text{SE} = \sqrt{\frac{2\sigma^2}{MN}}$$

이다. 크기 $\delta$ 의 실제 하락을 검정력 있게 검출하려면(유의수준과 검정력을 함께 만족하려면) 차이가 표준오차의 약 $2.8$ 배($z_{0.975}+z_{0.8}=1.96+0.84$)를 넘어야 한다.

$$\delta > 2.8\,\sqrt{\frac{2\sigma^2}{MN}} \quad\Longrightarrow\quad MN > \frac{2\times 2.8^2\,\sigma^2}{\delta^2} = \frac{15.7\,\sigma^2}{\delta^2}$$

숫자를 넣자. 온도가 있는 서빙에서 항목당 점수 잡음 $\sigma = 0.15$, 검출하려는 하락 $\delta = 0.02$(0~1 척도에서 2점)라면

$$MN > \frac{15.7 \times 0.15^2}{0.02^2} = \frac{15.7 \times 0.0225}{0.0004} \approx 883$$

골든셋이 $M = 200$ 항목이면 $N > 883/200 \approx 4.4$, 즉 항목당 5회 반복하면 이 크기의 회귀를 통계적으로 검출할 수 있다. 이 5라는 숫자가 뒤의 손 예제(8.2.12)에서 쓰는 $N=5$ 의 출처다 — 임의로 고른 것이 아니라 잡음과 검출 목표에서 나온 값이다. 계산이 알려 주는 것은 방향이다. 온도를 0에 가깝게 낮춰 평가하면 $\sigma$ 가 작아져 $N=1{\sim}2$ 로도 충분하고, 반대로 더 작은 회귀($\delta$ 를 반으로)를 검출하려면 $MN$ 이 4배로 증가한다. 그래서 회귀 판정용 실행은 제품이 온도 1로 서빙하더라도 온도를 낮춰 잡음을 줄이는 것이 표준이다 — 다만 온도 0의 행동이 실제 서빙 온도의 행동을 완전히 대표하지는 않음을 유념해야 한다.

이 계산은 잡음이 정규분포이고 반복이 독립이라는 이상화 위에 있다. 실제로는 판정자 자체의 편향과 잡음이 더해지므로(4.2), $\sigma$ 는 순수 샘플링 잡음보다 크다고 봐야 한다. 그래도 결론은 확고하다. LLM 회귀 판정은 유닛테스트처럼 "빨강/초록"이 아니라 "통계적으로 유의한 하락인가"라는 확률적 판정이고, 이 성격을 부정하고 정확 일치를 고집하면 스위트가 붕괴한다.

8.2.5 프롬프트·모델 버전 관리와 재현성

회귀를 판정하고 롤백하려면, 무엇이 배포됐었는지를 정확히 알아야 한다. "지난주엔 잘 됐는데 지금 안 된다"를 디버깅하려면 지난주의 프롬프트·모델·검색·파라미터 조합 전체를 재현할 수 있어야 한다. LLM 시스템의 재현성은 이 버전 관리에서 온다.

프롬프트를 코드 안에 문자열로 흩뿌리거나 관리자 UI에서 즉석 수정하면, 무엇이 언제 왜 바뀌었는지 추적 불가능해진다. 프롬프트는 코드다 — 버전 있는 아티팩트로 다뤄야 한다. 각 프롬프트에 버전 ID(summarize@v7)를 붙이고, 배포된 버전은 불변으로 둔다. 고치면 새 버전을 만들지 과거 버전을 덮어쓰지 않는다. 프롬프트 한 줄이 시스템 행동을 바꾸므로(8.2.1), 프롬프트 변경도 코드처럼 리뷰·PR을 거쳐야 한다. 프롬프트를 코드에서 떼어 레지스트리에 두면 코드 재배포 없이 버전을 교체·롤백할 수 있고 A/B에 붙이기 쉬운데, 여기서 흔한 함정이 "코드 없이 바꿀 수 있음"을 "리뷰 없이 바꿔도 됨"으로 오해하는 것이다 — 레지스트리는 배포를 빠르게 할 뿐 검증을 면제하지 않는다.

재현성과 귀인의 핵심은 모든 요청에 그때의 조합을 기록하는 것이다. 이 로그가 없으면 A/B 귀인도, 사고 조사도, 롤백 판단도 불가능하다(관측성 7.6과 직결).

{
  "request_id": "req_8f21...",
  "timestamp": "2026-07-17T09:12:03Z",
  "prompt_version": "rag_answer@v7",
  "model": "vendor-model-x",
  "model_version": "2026-05-01",
  "decoding": {"temperature": 0.2, "top_p": 0.9, "max_tokens": 800, "seed": 42},
  "retrieval": {"embed_model": "embed-v3", "index_snapshot": "kb-2026-07-15", "top_k": 6, "reranker": "rr@v2"},
  "policy_version": "safety@v4",
  "experiment": {"flag": "concise_prompt", "variant": "B", "bucket": 37}
}

이 하나가 이 응답을 만든 시스템의 완전한 지문이다. 무언가 회귀하면 이 지문으로 어느 버전 조합에서 문제가 생겼는지 좁힌다. 특히 model_version 과 index_snapshot 은 프로바이더나 인덱스가 조용히 바뀌어도 그 변화를 사후에 추적하게 해 주는 닻이다 — 프로바이더가 5월 1일자 모델을 6월에 말없이 갱신했다면, 6월 이후 요청의 지문이 같은 model_version 을 달고 있음에도 행동이 달라진 것이 로그에 남아 원인 후보로 떠오른다.

정직하게 말하면 완전한 재현은 불가능할 수 있다. 위 지문을 다 고정해도 하드웨어 레벨 비결정과 핀할 수 없는 프로바이더 모델이 남는다. 그래서 재현성의 목표는 "비트 단위 동일 출력"이 아니라 "같은 조합에서 같은 분포·같은 품질"이다. API 모델을 쓰면 모델 가중치 자체를 소유하지 못하므로, 프로바이더가 그 버전을 폐기하면 과거를 재현할 수단이 아예 사라진다 — 이것이 build vs buy에서 API 의존의 숨은 비용이고(9.1), 규제·감사 요구가 강한 도메인에서 자체 호스팅을 고려하게 만드는 이유 중 하나다.

8.2.6 A/B 테스트 — LLM 제품에서 무엇을 재나

오프라인 게이트가 "실험실에서 나쁘지 않은가"를 봤다면, A/B는 "실제 사용자에게 정말 더 나은가"를 잰다. 사용자를 무작위로 두 그룹으로 나눠 A(기준선)와 B(신버전)를 동시에 서빙하고, 지표 차이가 우연이 아닌지 통계로 판정한다.

전통 A/B는 클릭률·전환율 같은 명확한 행동을 재지만, LLM 제품의 어려움은 "응답 품질"이 직접 안 보인다는 것이다. 그래서 사용자 행동에서 품질을 역추론한다.

지표 부류예무엇을 뜻하나주의
수용답변 복사·"좋아요"·코드 삽입·제안 채택응답이 쓸모 있었다명시 피드백은 희소·편향(불만족자가 더 많이 누름)
재생성"다시 생성" 클릭·재질문·rephrase첫 답이 불충분했다(품질 하락 신호)탐색적 재생성과 구분 어려움
이탈대화 중단·세션 이탈·응답 후 무행동답이 만족스럽지 않거나 막힘만족해서 떠난 것과 구분 불가할 수 있음
만족명시 평점(thumbs·CSAT)·설문직접적이나 희소응답률 편향·척도 변동
참여·유지세션 길이·재방문·이후 사용량장기 가치신기성에 오염
과업 완수목표 달성·에러 해결·전환궁극 가치에 가장 가까움측정 정의가 어렵고 지연됨

이 지표들의 공통 함정은 전부 대리 신호(proxy)라는 것이다. 수용률은 "복사했다 = 좋았다"라고 가정하지만, 사용자는 나쁜 답을 복사해 직접 고칠 수도 있고, 좋은 답을 읽고 만족해 아무것도 안 누를 수도 있다. 재생성률은 품질 하락의 신호이지만, 창작 태스크에서는 "더 많은 선택지를 보고 싶다"는 만족의 신호이기도 하다. 그래서 하나의 지표로 판단하면 안 된다 — 수용률만 높이면 모델이 장황하고 아첨하는 답으로 유도될 수 있는데, 긴 답일수록 "뭔가 해 줬다"는 인상에 복사·좋아요가 늘기 때문이다. 이것은 4.2에서 본 판정자의 verbosity 편향과 같은 병이다. 방어는 층을 두는 것이다. 북극성 지표(과업 완수·유지)를 궁극 목표로 두되 지연되고 측정이 어려우니, 수용·재생성 같은 대리 지표를 즉각적 신호로 함께 보고, 아래에서 볼 가드레일로 하한을 지킨다.

8.2.7 가드레일 지표 — 이겼고, 아무것도 안 망가뜨렸나

가드레일 지표는 "이건 나빠지면 안 된다"는 하한선이다. 주 지표가 개선돼도 가드레일이 붕괴하면 배포하지 않는다. 전형적인 가드레일은 지연(p50·p95·p99), 비용/쿼리(7.5), 거부율, 안전 위반율(4.3·7.3), 오류·형식 위반율이다.

왜 이것이 별도의 개념이어야 하는지는 지연과 품질의 얽힘을 보면 분명해진다. 신모델이 품질은 좋지만 느리다면, 사용자 만족이 오른 것인지 내린 것인지 애매해진다 — 품질 개선이 지연 악화로 상쇄될 수 있고, 반대로 응답이 빨라져 만족이 오른 것을 품질 덕으로 오해할 수도 있다. 지연을 가드레일로 통제해야 주 지표의 변화를 품질 변화로 해석할 수 있다. 비용도 마찬가지다. 인용을 붙이거나 추론 단계를 늘려 품질을 샀는데 토큰이 늘어 마진이 붕괴하면, 지표상 이겼어도 사업은 진 것이다.

가드레일의 실제 요점은 비대칭 규칙으로 쓴다는 데 있다. 주 지표는 "유의하게 개선돼야 채택"이고, 가드레일은 "유의하게 악화되면 거부"다. 이 비대칭 때문에 A/B의 질문이 "이겼나"에서 "이겼고, 그리고 아무것도 안 망가뜨렸나"로 바뀐다. 그리고 안전 가드레일만은 등급이 다르다. 지연·비용은 개선 폭과 비교 평가할 여지가 있지만, 어떤 품질 개선도 안전 회귀를 정당화하지 않는다 — 유해 출력이나 탈옥 성공률이 오르면 주 지표가 아무리 좋아도 배포는 없다.

8.2.8 표본 크기와 유의성 — 얼마나 오래 실행하나

작은 차이를 신뢰하려면 큰 표본이 필요하다. 얼마나 큰지는 배포 전에 계산해야 "며칠 돌려야 결론이 나는가"를 안다. 두 비율(예: 수용률)을 비교할 때, 유의수준 $\alpha$·검정력 $1-\beta$·최소 검출 효과(MDE) $\delta$ 에 대해 한 그룹당 필요한 표본은 근사적으로

$$n \approx \frac{2\,\bar p(1-\bar p)\,(z_{1-\alpha/2}+z_{1-\beta})^2}{\delta^2}$$

이다. $\alpha=0.05$(양측, $z\approx1.96$), 검정력 $0.8$($z\approx0.84$)이면 $(1.96+0.84)^2\approx 7.84$ 다. 기준 수용률 $\bar p=0.40$ 에서 2%p 절대 개선($\delta=0.02$)을 검출하려면

$$n \approx \frac{2\times 0.40\times 0.60 \times 7.84}{0.02^2} = \frac{0.48\times 7.84}{0.0004} \approx 9{,}400 \ \text{(그룹당)}$$

즉 그룹당 약 9,400세션, 양쪽 합쳐 1만 9천 세션가량이다. 여기서 놓치면 안 되는 성질은 표본이 MDE의 제곱에 반비례한다는 것이다 — $\delta$ 를 반(1%p)으로 줄이면 표본은 4배(그룹당 3만 8천)로 폭증한다. 이 제곱 관계 때문에 "아주 작은 개선을 통계적으로 증명"하는 것은 트래픽이 거대하지 않으면 사실상 불가능하고, 그래서 처음부터 검출할 가치가 있는 효과 크기를 정해 두는 것이 실무적이다.

이 표본을 시간으로 환산하면 실험 기간이 나온다. 한 팔에 하루 3,000세션이 들어온다면 그룹당 9,400세션을 모으는 데 최소 $9{,}400/3{,}000\approx 3.1$ 일이 걸린다. 하지만 3일에 멈추면 안 된다 — 아래에서 볼 신기성 효과가 아직 가라앉지 않았을 수 있어서, 표본 크기가 충족돼도 보통 1~2주는 실행한다. 즉 실제 실험 기간은 "통계적 검정력이 차는 시점"과 "신기성이 가라앉는 시점" 중 늦은 쪽이다.

유의성 판정은 두 비율의 z-검정으로 한다.

$$z = \frac{\hat p_B - \hat p_A}{\sqrt{\hat p(1-\hat p)\left(\frac{1}{n_A}+\frac{1}{n_B}\right)}}, \qquad \hat p = \frac{x_A + x_B}{n_A + n_B}$$

$|z|>1.96$ 이면 $\alpha=0.05$ 에서 유의하다. 하지만 유의성은 필요조건이지 충분조건이 아니다. $z=2.1$ 로 유의해도 효과가 0.3%p라면 사업적으로 무의미할 수 있고, 신뢰구간이 [0.1%p, 3.9%p]처럼 넓다면 "얼마나 개선됐는가"에 대한 답이 사실상 없는 것이다. 유의성·효과 크기·신뢰구간·실질적 중요성을 함께 봐야 한다. 그리고 이 계산 전체가 표본이 독립이고 정규근사가 성립한다는 가정 위에 있다 — 실제 A/B는 한 사용자가 여러 세션을 내는 클러스터 구조, 요일·시간대의 비정상성 때문에 더 복잡하고, 그때는 클러스터 표준오차나 부트스트랩이 필요하다.

8.2.9 신기성 효과 — 초기 결과를 믿지 마라

새 기능·새 응답 스타일은 그 자체가 새로워서 초기 반응을 왜곡한다. 두 방향으로 온다. 신기성 효과는 새것이라 사용자가 더 눌러보고 참여해 지표가 일시적으로 부풀었다가 가라앉는 것이다. 반대로 변화 저항(change aversion)은 익숙한 것이 바뀌어 초기에 반발·이탈이 커졌다가 적응하며 회복되는 것이다. 둘 다 초기 며칠에서 2주의 A/B 결과를 실제 효과로 착각하게 만든다. 지속 기간은 제품·변화 크기에 따라 다르지만, 공통점은 초기 관측이 정상 상태를 대표하지 않는다는 것이다.

초기(신뢰 금지) 0 5 10 15 20 25 30 30 35 40 45 50 55 시간(일) → 지표(수용률) [%] 기준선 A B의 실제 효과 신기성 스파이크 안정 구간(판정)
신기성 효과 — 초기 스파이크가 실제 효과로 수렴(개략)

방어는 세 가지다. 스파이크가 가라앉아 안정 구간에 들 때까지 충분히 오래 실행한다 — 앞서 말했듯 표본 크기가 충족돼도 신기성이 남아 있으면 이르다. 신규 사용자와 기존 사용자를 나눠 보면 신기성/저항을 구분할 수 있다 — 기존 사용자만 초반에 이탈이 크다면 변화 저항이고, 신규만 초반에 참여가 높다면 신기성이다. 그리고 장기 홀드백(long-term holdout)으로 일부 사용자를 계속 기준선에 남겨, 신기성이 사라진 뒤에도 이득이 유지되는지 몇 주~몇 달에 걸쳐 측정한다. 이 홀드백은 여러 실험이 누적된 뒤 "우리가 지난 분기에 배포한 것들이 정말 합쳐서 나아졌나"를 재는 유일한 정직한 방법이기도 하다.

8.2.10 점진 배포 — 섀도·카나리·롤아웃·롤백·기능 플래그

오프라인 게이트와 A/B 사이, 그리고 A/B 승리 이후 전량 배포까지를 잇는 것이 점진 배포다. 위험을 소수에게 먼저 노출해 폭발 반경을 통제한다.

섀도(shadow, dark launch)는 실트래픽을 신버전에도 흘려보내되 그 응답을 사용자에게 안 보여준다. 기존 버전이 실제 응답을 내고, 신버전은 몰래 같은 입력을 받아 응답을 로깅만 한다. 이것이 검출하는 것은 실트래픽 분포 위의 크래시·타임아웃·지연·비용·형식 붕괴, 그리고 두 버전 출력의 괴리다 — 골든셋에 없던 실제 입력에서 신버전이 붕괴하는지 본다. 못 검출하는 것은 사용자 반응·선호다. 아무도 신버전 응답을 안 보므로 품질 선호는 알 수 없다. 비용은 미러링한 트래픽에 대해 추론이 2배라, 보통 트래픽의 일부(10% 등)만 미러링한다.

미러링(복제) 응답 폐기 · 로깅만 사용자 요청 프로덕션 v_base 사용자에게 응답 섀도 v_new 비교 로그 (지연·비용·형식·괴리)

카나리(canary)는 신버전을 소수의 실사용자(1~5%)에게 실제로 서빙하고 가드레일을 상시 감시하며 위반 시 자동 롤백한다. 섀도와 달리 실제로 응답을 보여주므로 품질·행동 신호를 얻지만, 소수만 노출돼 위험이 제한된다. 여기서 중요한 설계는 롤백이 롤아웃보다 빠르고 무조건이어야 한다는 것이다. 오류율·지연 p99·비용·안전 위반·거부율이 임계를 넘으면 사람이 밤에 깨지 않아도 자동으로 플래그를 꺼야 한다 — 롤백을 사람 판단에 맡기면 그 판단이 오는 몇 시간 동안 피해가 쌓인다. 카나리가 건강하면 노출을 단계적으로 늘린다(1%→5%→25%→50%→100%). 각 단계에서 가드레일을 재확인하고, 이 롤아웃 비율이 곧 A/B의 B군 비율이 되어 그 위에서 지표를 잰다.

이 모든 것을 가능하게 하는 인프라가 기능 플래그(feature flag)다. 핵심 아이디어는 "코드 배포"와 "기능 노출"을 분리하는 것이다 — 신버전 코드는 이미 배포돼 있고, 플래그가 누구에게 켜질지를 런타임에 정한다. 이 분리가 세 가지를 준다. 재배포 없이 플래그만 끄면 즉시 기준선으로 복귀하는 킬 스위치, 특정 사용자군·지역·요청 유형에만 켜는 타깃팅(내부 직원 → 베타 → 전체), 그리고 같은 사용자를 항상 같은 변형에 안정적으로 배정하는 버킷팅(경험이 일관돼야 한다).

플래그 off (전량 기준선) 섀도 미러링 카나리 1~5% 점진 25/50% 전량 100% 신 기준선 확정 가드레일 위반 (자동 롤백) 가드레일 위반 (자동 롤백) Off Shadow Canary Rollout Full
기능 플래그의 롤아웃 단계 상태 머신 — 섀도·카나리·점진 롤아웃과 자동 롤백

롤백을 "그냥 이전 커밋으로 되돌리기"로 생각하면 늦다. 롤백은 배포 전에 설계돼야 한다 — 킬 스위치 플래그, 자동 트리거, 이전 프롬프트·모델 버전의 즉시 복원 경로(8.2.5). 특히 파인튜닝이나 인덱스 갱신처럼 되돌리기 어려운 변경은(8.3) 그 자체를 플래그 뒤에 두거나 이전 아티팩트를 보존해 둬야, 문제가 발생했을 때 "복구에 6시간"이 아니라 "플래그 off로 6초"가 된다.

8.2.11 모델 버전 마이그레이션 — 버전이 바뀌면 행동이 바뀐다

가장 위험한 변경은 모델 버전 교체다. 파급 반경이 시스템 전체이기 때문이다. 왜 행동이 바뀌는지를 조목조목 보면, 이것이 "파라미터 하나 바꾸기"가 아니라 작은 프로젝트인 이유가 분명해진다.

같은 프롬프트에도 톤·장황함·형식·거부 성향이 달라진다 — 종합 벤치 점수가 올라도 당신의 슬라이스에서는 떨어질 수 있다. 당신의 프롬프트는 구 모델에 맞춰 튜닝돼 있어서, few-shot 예시와 지시 문구가 구 모델에서 통하던 방식이 신 모델에서는 다르게(때로 나쁘게) 작동한다 — 프롬프트는 모델과 결합돼 있다. JSON 출력의 미묘한 형식 차이, 함수 호출 스키마 준수도의 변화, 특수 토큰·채팅 템플릿 차이가 다운스트림 파서를 조용히 깨뜨린다. 같은 온도에서도 출력 분포가 달라 회귀 판정의 기준선 자체가 변동한다(8.2.4). 그리고 판정자·안전 분류기·가드레일 임계가 구 모델 분포에 맞춰져 있었다면, 신 모델에서는 그 보정이 어긋난다 — 구 모델에서 "환각 8%"를 뜻하던 판정자 점수가 신 모델에서는 다른 것을 뜻할 수 있다.

그래서 모델 교체는 재보정·재평가를 포함하는 절차로 다룬다.

구 모델 버전 핀 유지 (기준선 보존) 신 모델에 회귀 스위트 실행 슬라이스별 프롬프트 재튜닝 신 모델에 맞춰 재작성·재검증 재보정 판정자·안전 임계·가드레일 신 모델 분포에 맞춤 형식·파서 회귀 점검 JSON·함수호출·특수토큰 섀도로 실트래픽 괴리 측정 카나리 + A/B 가드레일 감시 통과 시 롤아웃 · 구 버전 폐기 유예

절차의 골격은 이렇다. 프로바이더가 자동 업그레이드하게 두지 말고 버전을 명시적으로 핀해, 신버전을 병행 평가하는 동안 구버전이 프로덕션을 지키게 한다. 프로바이더가 구버전 폐기를 예고하면 그 일정이 곧 마이그레이션 데드라인이 되는데 — 이 "폐기 강제"가 API 의존의 비용이고, 모델을 소유하지 않을 때 원치 않는 때에 마이그레이션이 강제되는 리스크의 정체다(9.1). 구 모델용 프롬프트를 그대로 쓰지 말고 신 모델에서 골든셋으로 재검증·재작성하고, 판정자·안전 분류기·가드레일 임계가 신 모델 분포에 맞는지 다시 잰다. 이 재보정은 8.3의 모델 라이프사이클과 이어진다.

여기서 이 장에서 가장 값비싼 통념을 정면으로 깬다. "새 모델이 MMLU나 아레나에서 더 높으니 즉시 교체"는 회귀의 지름길이다. 종합 벤치는 당신의 태스크 분포가 아니고(4.1), 프롬프트·형식·가드레일이 모두 구 모델에 맞춰져 있으며, 공개 벤치에는 평가셋 오염이 섞였을 수 있다. 벤치 순위가 높다는 것은 그 벤치 분포에서 평균적으로 낫다는 뜻이지, 당신의 태스크·프롬프트·형식·가드레일 조합에서 낫다는 보장이 전혀 아니다. 공개 벤치는 후보를 좁히는 참고일 뿐이고, 교체 결정은 당신의 회귀 스위트 + 섀도 + 카나리로만 내려야 한다.

8.2.12 워크스루 — 프롬프트 변경의 회귀 스위트 + 카나리

지금까지의 조각을 하나의 구체적 절차로 꿰어 보자. RAG 챗봇의 시스템 프롬프트에 "답변에 출처를 인용하라"는 지시를 추가하는 변경을 배포한다고 하자. 파일 하나 고치는 작은 변경이지만 파급은 넓다.

먼저 프롬프트를 새 버전으로 만든다(구 버전 불변 보존).

rag_answer@v7  (기준선, 프로덕션)
rag_answer@v8  (신규: "관련 문서를 인용하고 [출처 N] 형식으로 표기하라" 추가)

기능 플래그 cite_sources 를 만들되 일단 off로 둔다(전량 v7). 다음으로 오프라인 회귀 스위트를 실행한다. 골든셋(대표 200케이스, 카테고리 슬라이스)을 v7·v8 각각 $N=5$ 회 실행해(8.2.4.1에서 이 5가 어디서 나왔는지 계산했다) 슬라이스별 지표를 낸다. 판정은 스왑 pairwise 판정자(v8 vs v7, 위치 편향 상쇄, 4.2) + 결정적 검사(인용 형식 정규식·JSON 유효성)로 한다.

슬라이스v8 승률(vs v7)인용 형식 준수환각률판정
사실 질의(근거 있음)62% ↑94%8% → 5% ↓개선
다단계 추론55% ↑88%유지개선
잡담·창작41% ↓(해당없음)유지회귀 의심
근거 없는 질의48%71% ↓—회귀: 없는 출처 날조

발견이 있다. "출처 인용" 지시가 근거 없는 질의에서 출처를 지어내는 부작용을 냈다 — 형식 준수는 71%지만 인용된 출처가 허구라, 환각의 새 유형이 생긴 것이다. 잡담에서는 불필요한 인용으로 톤이 어색해졌다. 종합 승률만 봤다면 사실 질의의 큰 개선에 묻혔을 이 국소 회귀가 슬라이스 분해 덕에 드러났다. 게이트가 차단한다.

프롬프트를 고친다. "제공된 문서에 근거가 있을 때만 인용하고, 근거가 없으면 인용하지 말고 '근거 문서에 없음'이라고 밝혀라"로 바꿔 rag_answer@v9 를 만든다. 재실행하니 근거 없는 질의의 날조가 사라지고 잡담 슬라이스도 회복된다. 안전 게이트 통과, 슬라이스 회귀 없음 — 게이트 통과. 여기서 얻을 교훈은, "한 줄 고쳤을 뿐"이 근거 날조라는 새 실패 모드를 만들었고 그것을 오프라인 게이트가 사용자에게 도달하기 전에 검출했다는 것이다. 이것이 게이트가 존재하는 이유다.

이제 cite_sources 를 섀도 모드로 켠다. 실트래픽 10%를 v9에도 미러링해 사용자에겐 v7 응답을 주되 v9를 로깅·비교한다. 발견: v9는 인용 처리로 출력 토큰이 평균 18% 증가해 지연·비용이 오른다. 가드레일 관점에서 기록해 둔다. 다음으로 cite_sources 를 5% 카나리로 켜고 A/B를 붙인다. 주 지표는 수용률(답변 복사·좋아요), 가드레일은 p99 지연·비용/쿼리·안전 위반·거부율이다.

지표A(v7)B(v9, 5%)판정
수용률40.0%43.1%+3.1%p, $z=2.6$ 유의 ✓
재생성률12%10%개선
p99 지연2.1s2.5s가드레일 내(임계 3s) ✓
비용/쿼리기준+14%가드레일 내(임계 +20%) ✓
안전 위반0.02%0.02%변화 없음 ✓

표본이 그룹당 약 9천에 도달할 때까지 실행하고(8.2.8의 계산), 신기성 스파이크가 가라앉는지 재방문 사용자로 확인한다. 주 지표가 유의하게 개선됐고 어떤 가드레일도 안 붕괴했다 — 다음 단계로 간다. 5%에서 25%, 50%, 100%로 늘리며 각 단계 가드레일을 재확인하고, 만약 어느 단계에서 p99가 3s를 넘거나 안전 위반이 급증하면 플래그를 꺼 즉시 v7로 되돌린다. 전량 도달 후 v9를 새 기준선으로 확정하고, 골든셋의 기준선 점수를 v9로 갱신한다 — 그리고 이 사이클이 8.1의 데이터 플라이휠로 되돌아간다. 작은 프롬프트 한 줄조차 버전화, 슬라이스 오프라인 게이트, 섀도, 카나리+A/B, 지키며 롤아웃, 킬 스위치 롤백을 다 거쳤다는 점이 이 예제의 전부다.

8.2.13 오프라인↔온라인 괴리와 A/B의 함정

이 장의 도구들은 강력하지만, 각각이 무엇을 못 보는지를 흐리면 배포가 오도된다. 오프라인 게이트를 통과하고도 온라인에서 회귀하는 일은 흔하다. 골든셋은 실트래픽의 근사일 뿐이라 실사용자는 골든셋에 없는 입력을 내고(분포 괴리), 판정자가 좋다고 한 응답을 실사용자는 싫어할 수 있으며(판정자 ≠ 사용자, 판정자의 장황함 편향이 오프라인 점수를 부풀렸을 수 있다), 오프라인은 지연·비용·UX 같은 2차 효과를 못 본다. 그래서 게이트 통과는 "배포 안전"이 아니라 "카나리로 갈 자격"이고, 최종 판단은 온라인에서 내린다.

온라인 A/B에도 함정이 여럿이다. 가장 근본적인 것은 지표 게이밍(Goodhart)이다 — 수용률만 최적화하면 장황·아첨으로 유도돼 실질 품질은 안 오르고 지표만 오른다. 이는 3.4의 보상 해킹, 4.2의 판정자 게이밍과 같은 근원이다. 고정된 대리 신호를 향해 과최적화하면 그 대리 신호의 편향까지 최적화되기 때문이다. 방어는 북극성 + 가드레일 + 주기적 인간 검증이다. 그다음이 엿보기(peeking)다 — A/B를 계속 들여다보다 "지금 유의하다"에서 멈추면 거짓 양성이 폭증한다. 유의성 검정은 "한 번 볼 때 5% 확률로 우연히 유의"를 허용하는데, 매일 들여다보며 유의해질 때까지 기다리면 그 5%를 여러 번 재사용하는 셈이라 실제 거짓 양성률이 훨씬 높아진다. 방어는 표본 크기를 미리 정하고 그때까지 판정을 미루거나, 순차 검정·항상 유효한 신뢰구간을 쓰는 것이다.

나머지 함정들도 실무에서 자주 물린다. 표본 비율 불일치(SRM)는 A·B에 배정된 실제 트래픽이 설계와 다른 것(50/50인데 48/52)으로, 버킷팅·로깅 버그의 신호이니 결과 전체를 의심해야 한다. 심슨의 역설은 종합은 이겼는데 모든 세그먼트에서 지는(혹은 반대) 현상으로, 세그먼트별로 분할해 확인해야 한다. 다중 지표·다중 슬라이스를 동시에 보면 우연히 하나는 유의해 보이니 보정하거나 사전 등록된 주 지표에 집중한다. 그리고 동시에 실행하는 여러 실험이 서로 간섭하거나 네트워크 효과(한 사용자의 변화가 다른 사용자에 영향)로 독립 가정이 깨질 수 있다.

이 함정들을 관통하는 한 문장은 이렇다 — 이 장의 모든 도구는 어떤 위험을 값싸게 조기에 검출하는 필터이지, 그 자체로 최종 판정이 아니다. 오프라인 게이트는 온라인을 완전히 예측하지 못하고, A/B는 대리 신호의 편향을 함께 최적화하며, 벤치는 당신의 태스크가 아니다. 그래서 안전한 배포는 하나의 완벽한 관문이 아니라, 서로 다른 것을 놓치는 여러 값싼 관문을 순서대로 통과시키는 규율이다.

이 장이 "변경을 안전하게 내보내는 법"이었다면, 그 변경의 한 종류인 파인튜닝 모델을 어떻게 만들고 버전 관리하고 재현하고 폐기하는가 — 데이터에서 학습, 평가, 배포, 폐기까지의 라이프사이클 — 는 8.3이 이어받는다. 이 장의 오프라인 게이트·회귀 스위트·버전 지문·카나리는 파인튜닝 모델 배포에도 그대로 쓰이되, 거기서는 모델 아티팩트 자체의 계보·재현·롤백이라는 층이 하나 더 추가된다. 상시 온라인 품질 모니터링과 트레이싱은 7.6이, 자체 학습이냐 API냐 오픈소스냐라는 마이그레이션 강제의 근원이 되는 더 큰 전략 선택은 9.1이 다룬다.