2026년 4월 30일 | 개인 프로젝트 GraphRAG
History 파일이 길어지기 시작하면, 성능 변화보다 먼저 장부가 섞이는 문제가 보인다. 이번 GraphRAG 반복에서는 sample fixture로 만든 history와 operational run으로 만든 history를 같은 파일 안에 둘 수는 있되, 비교 기준선은 서로 건드리지 않도록 history namespace를 분리했다. 겉으로 보면 옵션 하나를 추가한 작은 작업인데, 실제로는 앞으로 quality history를 오래 쌓기 위한 청소에 가깝다.
조금 허무하게 들릴 수도 있지만, 이런 장부 정리가 retrieval 평가에서는 꽤 중요하다. 모델 점수나 top-1 결과가 흔들렸는지 보기 전에, 먼저 서로 비교해도 되는 run인지를 잘라야 한다. query set이 다르거나 gate 기준이 다른 run을 억지로 비교하면 `baseline mismatch`가 계속 늘어난다. 그런데 그중 일부는 진짜 warning이 아니라, 애초에 다른 실험장에 있던 기록이다.
Figure 1: sample history와 operational history를 같은 파일 안에서 namespace 기준으로 분리하는 흐름
왜 namespace가 필요했나
바로 앞 반복에서는 `history-scope-growth.json`을 붙였다. warning scope나 hard-fail scope가 새로 늘어난 profile만 따로 queue로 뽑는 장치였다. 그 덕분에 baseline mismatch가 있는 날에도 다시 볼 profile을 꽤 좁힐 수 있었다. 문제는 그 다음이었다. sample query set으로 만든 오래된 probe와 운영용 query set으로 만든 run을 한 history 파일에 같이 넣으면, sample 쪽 entry가 매번 `query_set` mismatch로 튀어나올 수 있다.
처음에는 그것도 나쁘지 않다고 생각했다. 어쨌든 mismatch report에 남아 있으면 기준선이 달랐다는 사실을 볼 수 있으니까. 그런데 몇 번 쌓아 보니 이건 warning이라기보다 소음에 가까웠다. 운영 run을 읽는 순간에는 sample fixture가 일부러 만든 흔들림이 궁금한 게 아니다. 내가 보고 싶은 건 운영 namespace 안에서 이전 run보다 품질 범위가 커졌는지다.
그래서 이번에는 mismatch reason을 하나 더 늘리지 않았다. `history_namespace`가 다르면 `query_set`이나 `quality_gate` mismatch로 넣지 않고, 그냥 ignored entry로 뺐다. 같은 namespace 안에 있는 entry끼리만 compatible history와 baseline mismatch를 계산한다. 다른 장부는 warning이 아니라 다른 장부로 남기는 쪽이 맞다고 봤다.
이번에 바꾼 구조
구현 자체는 크게 복잡하지 않았다. 핵심은 history entry를 만들 때 namespace를 같이 저장하고, history를 읽을 때 current run의 namespace와 다른 entry를 먼저 걸러내는 것이다. 옵션 이름은 --history-namespace로 두었다. 값이 없으면 기존 history와 호환되도록 default로 본다.
- history entry에
history_namespace필드를 추가했다. - CLI 옵션으로
sample,operational,experiment같은 이름을 넘길 수 있게 했다. - namespace가 다른 entry는 compatible / incompatible 계산에서 제외했다.
- 제외된 entry는
namespace_ignored_entries로 수량과 라벨만 얇게 남겼다. - 텍스트 요약에는
history_namespace=operational과history_namespace_ignored=7을 바로 보이게 했다.
여기서 신경 쓴 부분은 과거 파일을 깨지 않는 것이었다. 기존 history에는 namespace가 없다. 이 값들을 전부 새 포맷으로 강제로 마이그레이션하면 작업은 깔끔해 보이지만, 예전 재현 경로가 갑자기 달라질 수 있다. 그래서 namespace가 없으면 `default`로 읽고, 명시적으로 다른 namespace를 준 경우에만 분리되도록 했다.
테스트에서 보고 싶었던 장면
이번 테스트는 숫자 하나를 맞추는 것보다, 잘못된 warning이 사라지는지를 보는 쪽에 가까웠다. 일부러 `sample` namespace에 오래된 baseline entry를 하나 넣고, 현재 run은 `operational` namespace로 돌렸다. 그 오래된 entry에는 query fingerprint를 다르게 넣었다. 예전 구조라면 `query_set` mismatch가 떠야 하는 상황이다.
새 구조에서는 그 entry가 mismatch report에 들어가면 안 된다. 대신 `namespace_ignored_run_count`가 1이 되고, `history_warning`은 비어 있어야 한다. 이 테스트가 통과하면 적어도 다른 namespace의 query set 차이를 운영 warning으로 착각하지 않는다는 기준선이 생긴다.
| 항목 | 이전 해석 | 이번 해석 |
|---|---|---|
| sample fixture entry | query_set mismatch 후보 | 다른 namespace라 ignored |
| operational current run | sample baseline과 섞일 수 있음 | operational 기준선 안에서만 비교 |
| mismatch report | 비교 불가능한 warning이 늘어남 | 같은 namespace의 mismatch만 남김 |
실제 run 결과
이번에는 iter24의 sample history 7개를 `sample` namespace로 두고, 새 run은 `operational` namespace로 append했다. 같은 sample dataset과 query를 쓰기는 했지만, 의도는 ranking 자체보다 namespace 필터가 제대로 먹는지 보는 것이었다. 결과는 예상한 대로 나왔다.
- 기존 sample entry: 7개
- 이번 operational entry: 1개
- history_namespace_ignored: 7
- mismatch_profile_entry_count: 0
- 테스트: 29개 통과
profile별 상태는 기존 기준선과 비슷하게 나왔다. default와 relation_weight_dense는 PASS, path_bridge_probe는 WARN, path_bridge_focus는 HARD-FAIL이다. 다만 이번에 중요한 점은 상태 자체가 아니라, 이 상태들이 operational namespace 안에서만 history sequence로 시작했다는 점이다. 그래서 텍스트 요약도 `history_sequence=PASS`, `history_sequence=WARN`, `history_sequence=HARD-FAIL`처럼 각각 한 줄짜리 시작점으로 남았다.
이건 당장 대단한 성능 개선은 아니다. 오히려 성능을 더 정확히 읽기 위한 바닥 정리다. 하지만 이런 정리가 없으면 다음 단계에서 운영 query set을 붙였을 때, sample fixture가 만든 mismatch를 매번 손으로 머릿속에서 빼야 한다. 나는 이런 류의 수동 보정이 쌓이기 시작하면 나중에 꼭 한 번 틀린 판단을 한다고 보는 편이다.
작게 남은 삽질
처음에는 namespace mismatch도 하나의 reason으로 넣을까 고민했다. 예를 들어 `history_namespace` mismatch를 `reason_counts`에 같이 넣으면 report가 더 풍부해 보인다. 그런데 그렇게 하면 다시 같은 문제가 생긴다. 다른 namespace라는 사실은 warning의 이유가 아니라, warning 계산 전에 이미 갈라야 하는 조건이다. 그래서 report에는 넣지 않고 ignored count로만 남겼다.
또 하나는 기본값이었다. 새 옵션을 붙이면 과거 history가 전부 이름 없는 상태가 된다. 여기서 기본값을 비워 두면 비교마다 예외 처리가 늘어난다. 반대로 전부 `default`로 읽으면 기존 테스트와 문서가 거의 그대로 살아난다. 결국 `없으면 default`가 가장 덜 놀라는 선택이었다.
다음에는 무엇을 볼지
이제 sample과 operational을 같은 history 파일 안에서 분리할 수 있으니, 다음 반복은 운영 query set을 따로 만드는 쪽이 자연스럽다. 지금까지는 sample dataset 안에서 relation profile과 gate 구조를 검증했다면, 이제는 조금 더 실제 문서 검색에 가까운 질문 묶음을 `operational` namespace로 고정해 볼 수 있다.
내가 다음에 보고 싶은 건 세 가지다. 첫째, namespace별 history summary를 한 번에 보여 주는 얇은 report. 둘째, sample fixture와 operational run의 scope growth queue를 나란히 비교하는 smoke check. 셋째, query set이 커졌을 때 `history_namespace_ignored`가 정말로 소음을 줄여 주는지다. 이번 변경은 작은 옵션 하나였지만, GraphRAG 평가 루프를 오래 굴리려면 이런 분리선이 먼저 있어야 한다.
'[AI 실험실] > [개인 프로젝트] GraphRAG' 카테고리의 다른 글
| GraphRAG | Stale transition split 추가 (0) | 2026.05.02 |
|---|---|
| GraphRAG | History stale status queue 추가 (0) | 2026.05.01 |
| GraphRAG | History scope growth queue 추가 (0) | 2026.04.30 |
| GraphRAG | History mismatch 이유별 섹션 (0) | 2026.04.29 |
| GraphRAG | History mismatch 리포트 추가 (0) | 2026.04.27 |