이번 실험은 한 가지 질문에서 시작했다. 대화와 문서를 계층화해 기억하고, lexical 검색과 semantic 검색을 결합하는 메모리 시스템이 있다면, 지금 쓰는 Markdown 기반 LLM Wiki도 중앙 서버로 옮기는 편이 낫지 않을까.
참고한 시스템은 TencentDB Agent Memory였다. 원문과 파생 기억을 나누고, 검색 경로를 여러 층으로 구성하며, 에이전트가 API를 통해 필요한 기억을 가져오는 방식은 매력적이었다. 특히 lexical 검색과 vector 검색을 합치는 RRF(Reciprocal Rank Fusion)는 기존 Wiki 검색의 약점을 보완할 수 있어 보였다.
그럴듯한 아이디어를 평가하는 가장 나쁜 방법은 설계 문서만 읽고 결론을 내리는 것이다. 그래서 실제 서버를 만들었다.
기: Markdown Wiki를 중앙 검색 서비스로 옮겨보기
먼저 LLM Wiki의 3,799개 파일을 일관된 snapshot으로 봉인했다. 원본 Wiki는 건드리지 않고, 이 snapshot만 읽는 shadow 서비스를 별도로 구현했다. 서비스에는 인증된 loopback HTTP, health와 metadata, 문서 read/search, 공통 registry를 기반으로 한 MCP operation metadata를 넣었다. 쓰기와 외부 bind는 처음부터 막았다.
첫 구현은 deterministic lexical 검색만 제공했다. Windows와 WSL에서 각각 26개 테스트를 통과했고, 기존 104-case 회귀 suite도 두 번 연속 104/104를 기록했다. 여기까지만 보면 꽤 성공적인 서버였다.
하지만 104/104는 기존 기능 계약을 깨지 않았다는 뜻일 뿐, 검색 품질이 좋아졌다는 뜻은 아니다. 이 구분이 이후 실험 전체를 결정했다.
승: semantic 검색과 RRF를 붙이다
다음 단계에서 semantic index와 RRF를 추가했다. lexical lane과 semantic lane을 따로 실행하고, 두 순위를 합쳐 최종 후보를 만드는 구조였다. 검색 결과에는 어떤 문서가 어느 lane에서 올라왔고 fusion 뒤에 어떻게 순위가 바뀌었는지 추적할 수 있는 receipt도 남겼다.
초기 수치는 희망적이었다. 한 평가에서는 H@1이 0.5192에서 0.5288로, MRR은 0.5337에서 0.5497로 올랐다. 그러나 평균 점수 뒤에는 회귀 5건과 중요한 route 실패가 숨어 있었다. 일부 문서는 더 잘 찾았지만, 이미 잘 찾던 중요한 문서를 놓쳤다.
색인 범위도 문제였다. 전체 3,799개 가운데 실제 색인된 문서는 100개 안팎에 불과한 단계가 있었고, 포함·제외 이유를 증명하지 못하면 높은 점수도 의미가 없었다. 결국 전체 coverage manifest, source pointer 검증, 장애와 timeout, stale snapshot, 결정론적 rebuild까지 평가 범위가 커졌다.
전: 더 복잡한 검색이 더 좋은 검색은 아니었다
첫 번째 본격 품질 gate에서 결과는 냉정했다. held-out 42건의 MRR은 기존 검색과 hybrid가 모두 0.3690이었다. 승·무·패는 1·40·1이었다. hybrid는 사실상 아무 우위도 만들지 못했다.
운영 비용은 더 나빴다. warm p95 latency는 기존 214.924ms에서 hybrid 544.044ms로 늘었다. 약 2.5배다. RSS는 361,504,768 bytes로 사전에 정한 256MiB 한도를 넘었다. 새 방식은 더 느리고 더 많은 메모리를 쓰면서 품질은 같았다. 새로운 critical regression까지 하나 생겼다.
평가기 자체의 결함도 드러났다. source pointer의 출력 형식과 scorer 계약이 달랐고, 3,799개 파일을 다시 hash하는 preflight에는 근거 없이 120초 제한이 걸려 있었다. blind 평가에서 이런 결함을 발견하면 정답을 본 뒤 코드를 고쳐 같은 세트를 다시 돌릴 수 없다. 평가 독립성이 깨지기 때문이다. 회차를 폐기하고 새 blind set을 만들어야 했다.
전체 경과시간은 약 28시간, Codex의 실제 작업 시간은 약 16시간이었다. 산출물과 평가기는 6세대 이상 생겼지만, 최종 lexical과 hybrid 실행은 각각 한 번뿐이었다. 시간이 오래 걸린 주된 이유는 검색 계산량이 아니라, cheap contract test와 preflight에서 잡았어야 할 문제를 blind 이후에 발견한 평가 설계였다.
평가기와 실행 계약을 고친 뒤 새로 봉인한 48건에서도 hybrid는 MRR 0.480208에서 0.496875로 소폭 앞섰다. 숫자만 보면 개선이다. 그러나 95% bootstrap 구간의 하한은 0이었고, 승·무·패는 1·47·0이었다. 실질적으로 이긴 질의는 하나뿐이었다. 결과를 거부해야 할 때도 항상 다섯 개 경로를 반환하는 adapter 계약 때문에 abstention failure도 24건 남았다.
42건 평가와 48건 평가는 서로 다른 세트와 고쳐진 평가 계약을 사용했으므로 점수 자체를 이어 붙여 개선 추세로 해석할 수는 없다. 두 회차가 공통으로 보여준 것은 hybrid의 우위가 안정적으로 재현되지 않았다는 사실이다.
마지막으로 개선 후보를 분리해 다시 비교했다. weighted RRF는 개발 세트 MRR을 0.0993 올렸지만 중요한 사례 3건과 route 2건을 회귀시켰다. metadata boost는 0.0035에 그쳤고, semantic supplement와 confidence calibration은 개선이 없었다. query expansion은 MRR을 0.0226 낮추고 72건 모두 timeout을 일으켰다. 품질과 안전성을 동시에 만족한 후보가 하나도 없었으므로 새 blind set을 소모할 이유도 없었다.
결: 만든 서버를 선택하지 않는 것이 실험의 결론이었다
최종 판정은 RETAIN LEXICAL이었다. LLM Wiki를 버린다는 뜻이 아니다. 3,799개 문서로 구성된 현재 Wiki는 그대로 유지하고, 그 안의 검색기를 semantic+RRF hybrid로 교체하지 않는다는 뜻이다.
이 결과는 단순한 방식이 언제나 낫다는 주장도 아니다. 현재 Wiki는 제목, 요약, hub, 문서 유형이 이미 사람이 읽을 수 있는 구조로 정리돼 있다. 이런 corpus에서는 field-aware lexical 검색이 생각보다 강하다. semantic 검색이 유용하려면 “의미상 비슷한 문서도 찾는다”는 일반론이 아니라, lexical이 놓치는 실제 질의를 중요한 회귀 없이 반복해서 구해낸다는 증거가 필요하다.
이번 실험에서 가장 값비싼 산출물은 서버 코드가 아니었다. 기능 테스트의 녹색과 운영 채택의 근거를 분리한 평가 계약이었다. 다음 실험은 cheap contract test → preflight smoke → 구현·회귀 → 단 한 번의 blind 순서로 진행한다. evaluator와 snapshot은 미리 동결하고, 기준은 결과를 본 뒤 완화하지 않는다.
주말 동안 중앙 서버를 만들고 semantic index와 RRF까지 붙였지만, 운영에는 아무것도 넣지 않았다. 이미 만든 코드와 들인 시간이 아까워서 기준을 낮추는 순간, 실험은 검증이 아니라 배포를 정당화하는 절차가 된다. 이번에는 그 선을 넘지 않았다.