2026년 4월 20일 | 개발 공부
Quality Gate는 실험 결과나 검색 profile을 배포 후보인지 아닌지로 자르는 최소 판정 레이어다. 처음에는 나도 pass/fail 두 칸이면 충분하다고 생각했다. 기준선을 넘으면 통과시키고, 못 넘으면 탈락시키면 되는 일처럼 보였기 때문이다. 그런데 최근 retrieval 실험을 계속 보다 보니, 바로 버려야 하는 후보와 아직 더 실험해볼 후보가 같은 fail 안에 눌려 버리는 순간이 생각보다 자주 나왔다.
특히 GraphRAG 쪽에서 score profile을 여러 개 비교할 때 이 차이가 또렷했다. 어떤 profile은 overall 품질이 조금만 미끄러져서 "배포는 아직 이르지만 방향은 볼 만한 상태"에 머무르고, 어떤 profile은 핵심 question family까지 같이 무너져서 바로 뒤로 빼야 했다. 그런데 둘 다 fail로만 남기면 다음 액션이 흐려진다. 그 뒤로는 quality gate를 단순한 차단기보다 실험 후보와 배포 후보를 분리하는 얇은 운영 레이어로 이해하게 됐다.
1. FAIL 한 칸 안에 너무 다른 후보가 같이 눌렸다
pass/fail만 둘 때 제일 답답했던 건 실패의 결이 전혀 다르다는 점이었다. 예를 들어 어떤 profile은 overall top1 hit와 full coverage가 83%쯤으로 조금 내려가고, 흔들림도 docs bridge 계열 질문에 주로 몰린다. 이런 후보는 당장 배포할 상태는 아니지만, 어느 질문 family가 민감한지 보면서 더 만져볼 가치는 남아 있다.
반대로 다른 profile은 overall이 67%까지 내려가고, coverage-sensitive 같은 핵심 묶음까지 같이 깨진다. 이 경우는 warning이 아니라 hard fail에 가깝다. 이미 질문을 덜 완성하는 방향으로 정렬이 무너지고 있어서, trace를 길게 읽기 전에 우선순위를 뒤로 보내는 편이 낫다. 둘을 같은 fail로 두면 실험 로그를 읽는 사람은 다시 summary를 처음부터 끝까지 훑으면서 "이 fail은 어느 정도로 나쁜 fail인가"를 손으로 판단해야 한다.
| profile | overall top1 / coverage | 내가 읽은 상태 | 다음 액션 |
|---|---|---|---|
| relation_weight_dense | 100% / 100% | 안정형 | 배포 후보 유지 |
| path_bridge_probe | 83% / 83% | 경고형 | 문제 scope만 좁혀서 추가 실험 |
| path_bridge_focus | 67% / 67% | 즉시 제외형 | 우선순위 뒤로 보내고 다른 후보 먼저 보기 |
나는 이 표를 머릿속에 두고 나서야, gate가 단순히 좋은 결과와 나쁜 결과를 나누는 장치가 아니라는 걸 받아들였다. 실제로는 같은 나쁨 안의 차이를 남겨야 실험 루프가 덜 엉킨다.
2. WARN 구간은 탐색 루프와 배포 루프를 분리해 준다
경고 구간이 필요한 이유는 의외로 단순하다. 실험 중인 profile과 배포 후보 profile은 같은 기준으로 읽히지 않기 때문이다. 배포 후보는 기준선을 넘지 못하면 바로 제외하는 편이 맞지만, 탐색용 후보는 어느 질문군이 흔들리는지 보는 것만으로도 충분히 값이 남을 수 있다. 그래서 PASS와 HARD-FAIL 사이에 WARN 한 칸이 생기면, 결과를 보는 리듬이 훨씬 차분해진다.
내가 가장 유용하다고 느낀 설계는 gate를 두 겹으로 나누는 방식이었다. warning은 "배포 후보에서는 내리지만 탐색은 계속 가능"을 뜻하고, hard-fail은 "탐색 우선순위도 당장 낮춘다"를 뜻한다. 이 차이가 생기면 같은 fail이라도 실험 노트의 톤이 달라진다. 전자는 왜 흔들리는지 추적할 가치가 있고, 후자는 지금은 덜 보는 쪽이 맞다.
warning
- overall: top1_hit >= 0.90
- overall: full_coverage >= 0.90
- docs / doc-bridges 쪽은 더 빡빡하게 본다
hard_fail
- overall: top1_hit >= 0.80
- overall: full_coverage >= 0.80
- coverage-sensitive 같은 핵심 묶음은 따로 하한선을 둔다
여기서 중요한 건 숫자 자체보다 판정의 의미다. warning은 "조금 나쁨"이 아니라, 배포와 탐색을 다르게 취급하자는 선언에 가깝다. 나는 이 구분이 생긴 뒤로 fail 로그를 읽을 때 덜 서두르게 됐다.
3. overall만 보면 어디가 흔들렸는지 너무 늦게 알게 된다
gate를 overall 숫자 하나로만 걸면 또 다른 문제가 생긴다. 전체 평균은 간신히 버티는데, 특정 cluster나 tag에서만 먼저 무너지는 경우가 꽤 많기 때문이다. retrieval 계열 실험에서는 이런 국소 실패가 실제 체감 품질을 더 빨리 흔드는 편이다. 질문 family 하나가 유난히 약해지면, 로그 상으로는 "대체로 괜찮다"고 보이는데 실제 답변은 특정 유형에서만 자꾸 허전해진다.
그래서 overall, cluster, tag를 같이 두는 편이 좋았다. 전체 상태를 먼저 보고, 그다음 docs bridge인지 coverage-sensitive인지 scope를 바로 읽을 수 있어야 다음 trace diff가 짧아진다. quality gate는 점수표를 복잡하게 만드는 장치가 아니라, 어디부터 열어야 하는지 순서를 줄이는 장치에 더 가깝다.
이 그림을 보고 있으면 relation_weight_dense, path_bridge_probe, path_bridge_focus가 서로 다른 이유가 한눈에 드러난다. 이전 같으면 세 profile의 summary를 길게 읽고 나서야 감이 왔을 차이가, 이제는 status와 failure scope만으로 먼저 보인다. 나는 이런 식의 압축이 실험을 오래 끌고 갈수록 더 중요하다고 느낀다.
4. quality gate가 바꾸는 건 점수표보다 읽는 순서다
내가 이 레이어를 좋아하게 된 이유는 성능 숫자를 더 예쁘게 만들어서가 아니다. 오히려 사람이 로그를 읽는 순서를 바꿔 준다는 점이 더 컸다. 예전에는 summary를 처음부터 다 읽고, 흔들린 query를 찾고, 그제야 trace bundle을 열었다. 지금은 순서가 더 단순하다. hard-fail인지 먼저 보고, hard-fail이 아니면 warning scope를 본 다음, 필요한 경우에만 trace를 연다.
- HARD-FAIL이면 긴 설명보다 우선순위 조정이 먼저다.
- WARN이면 어느 cluster와 tag가 민감한지부터 좁힌다.
- PASS일 때만 다음 후보와의 세밀한 차이를 비교한다.
이 변화는 생각보다 실무적이다. 실험 개수가 적을 때는 누구나 summary를 꼼꼼히 읽을 수 있다. 하지만 후보가 쌓이기 시작하면, 결국 먼저 자를 것과 나중에 읽을 것을 구분하는 힘이 더 중요해진다. 내 기준에서 quality gate는 바로 그 분류 순서를 고정해 주는 메모리 역할에 가깝다.
5. 지표를 남기는 것과 컷을 고정하는 건 다른 일이다
여기서 한 번 더 느낀 건, metric을 많이 남긴다고 판정이 자동으로 또렷해지지는 않는다는 점이었다. top1 hit, top-k hit, full coverage가 모두 보이더라도, 어디서 멈출지 적어 두지 않으면 사람은 결국 결과를 본 뒤에 다시 기준을 움직이게 된다. 점수가 조금만 좋아 보여도 "이번에는 예외로 볼까"가 슬그머니 끼어든다.
그래서 quality gate는 측정 레이어 뒤에 붙는 운영 메모처럼 느껴졌다. 숫자를 읽는 일과, 그 숫자로 무엇을 할지 정하는 일을 분리해 주기 때문이다. 나는 최근에 실험을 볼 때 "무엇을 더 볼까" 못지않게 "어디서 멈출까"를 먼저 적어 두는 편이 더 안정적이라는 걸 자주 느꼈는데, staged gate는 그 감각을 코드 쪽으로 옮긴 버전에 가까웠다.
이 관점이 생기고 나니 quality gate는 차가운 체크리스트라기보다 판정 흔들림을 줄이는 얇은 약속처럼 읽혔다. 좋은 실험 루프는 많은 숫자를 남기는 루프가 아니라, 다음 행동을 덜 흔들리게 만드는 루프라는 생각도 같이 들었다.
6. 짧게 남겨두고 싶은 내 식의 정리
지금의 나는 quality gate를 단순한 통과선으로 보지 않는다. 내게 더 유용한 정의는 이쪽이다. quality gate는 후보를 평가하는 레이어가 아니라, 내 시간을 어디에 더 쓸지 정하는 레이어다. PASS는 그대로 유지하고, WARN은 범위를 좁혀 다시 보고, HARD-FAIL은 우선순위를 뒤로 미룬다. 이렇게 읽으면 같은 실험 로그도 훨씬 덜 무겁다.
아마 한동안은 retrieval이나 ranking 계열 실험을 볼 때 이 구조를 계속 떠올리게 될 것 같다. 결과가 많아질수록 PASS/FAIL 두 칸만으로는 설명되지 않는 중간 상태가 꼭 생긴다. 그 틈을 그냥 사람 감으로 메우기보다, WARN 같은 한 칸을 먼저 만들어 두는 편이 내게는 훨씬 실용적이었다.
'[개발 공부]' 카테고리의 다른 글
| MCP, stdio와 Streamable HTTP를 같은 transport로 보지 않기 (2) | 2026.04.23 |
|---|---|
| Query Coverage, 질문 완성도를 따로 보는 레이어 (0) | 2026.04.21 |
| Reranker를 검색 마지막 미세조정보다 순서 복구 단계로 이해하게 된 이유 (0) | 2026.04.06 |
| Speculative Decoding, 작은 모델의 초안이 큰 모델의 속도로 이어지는 방식 (0) | 2026.04.04 |
| HNSW를 벡터 DB보다 그래프 탐색으로 읽게 된 이유 (0) | 2026.04.03 |