PR #195 구현(e9b5202) 기준으로 빠졌던 디테일 보강:
- chunk_id(위치 기반 벡터 식별자) vs cache_key(내용 해시 조회 키) 구분 callout
- §7 호환성/마이그레이션 신설: 본문 재색인 불필요, V012 가산이나 binary 교체 필요,
별칭 sentinel 묶음→개별 변경의 기존 KB 영향(레거시 호환)
- version_key 에 kind 토큰("doc|") 반영, orphan sentinel cleanup(LIKE prefix) 명시
- embed_with_cache 순서 보존 불변, 별칭 개별 벡터 근거(희석 13/18→16/18)
- 정정: derivation_cache_gc 는 메서드만 존재하고 미연결(캐시 현재 무한 누적, 후속)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
115 lines
7.7 KiB
Markdown
115 lines
7.7 KiB
Markdown
# 나무위키 대규모 측정 — doc-side expansion 별칭 효과 + 파생물 캐시
|
||
|
||
> 2026-05-31. Phase 2 doc-side expansion(별칭) 의 효과를 실사용 규모(한국어 나무위키
|
||
> corpus)로 검증하고, 그 과정에서 드러난 별칭 생성 비용 문제를 "내용 해시 기반 파생물
|
||
> 캐시"로 해결한 기록. 선행: `2026-05-30-phase2-doc-expansion-kickoff.md`,
|
||
> 설계: `../specs/2026-05-30-dense-alias-vectors-design.md`,
|
||
> `../specs/2026-05-31-derivation-cache-design.md`.
|
||
|
||
## 1. 출발 질문 (사용자 제기)
|
||
|
||
측정을 진행하며 사용자가 던진 질문들이 설계를 단계적으로 교정했다:
|
||
|
||
1. **"테스트 모수가 너무 적지 않나? 더 넓게(대규모, 영+한 혼합) 테스트하자."**
|
||
→ 기존 8~32개 golden 으로는 "변형 일관성 개선"이 우연인지 실재인지 판단 불가.
|
||
2. **"실사용은 약 2천 개 한국어 위키 문서다."** + 기존 크롤링한 나무위키 parquet
|
||
(`/build/cache/namu-crawler/pages.parquet`, 119만 문서) 제공.
|
||
→ 측정 corpus 를 실사용에 맞춤. 노이즈는 크게, 별칭은 정답 문서에만(비용).
|
||
3. **"정답과 주제가 완전히 다르면(야구·게임) 검색이 너무 쉬워 별칭 효과가 과소평가된다.
|
||
실사용은 한 개발조직 위키 = 유사 주제 밀집이다."**
|
||
→ 노이즈를 정답과 같은 분야(CS/IT)로 교체. 진짜 어려운 "유사 경쟁" 환경 구성.
|
||
4. **"대조군(정답 없는 질문)도 측정하자."** → false-positive(별칭이 노이즈를 grounded
|
||
answer 로 끌어오는지) 검증.
|
||
5. **"별칭 벡터 생성이 너무 오래 걸린다(18문서 2.5시간). 캐싱이 절실하다 — 별칭뿐 아니라
|
||
비용 큰 모든 데이터에."** → 내용 해시 기반 파생물 캐시 설계·구현.
|
||
6. **"비싼 계산을 외부 CPU ollama 서버에서 하고 결과 DB 파일만 가져오고 싶다. 가능한가?"**
|
||
→ KB 이식성 검증.
|
||
|
||
## 2. corpus 구축
|
||
|
||
- 소스: 나무위키 덤프 119만 문서(`pages.parquet`, redirect 제외 완료).
|
||
- **노이즈 979개**: 본문 3k~30k자 + "분류" 헤더에 CS 키워드(컴퓨터공학·프로그래밍·알고리즘
|
||
…)가 있는 문서 ~70% 정밀도로 필터 → 무작위 샘플(CCleaner·LLaMA·SQL·멀티스레딩 등).
|
||
정답과 같은 임베딩 공간(유사 주제 밀집)이라 현실적 난이도.
|
||
- **정답 18개**: 명확한 CS 개념(경사하강법·TCP·정렬·이진탐색·뮤텍스·정규표현식 …),
|
||
전부 한국어 문서 → 영어 변형은 자동으로 cross-lingual(영→한) 시나리오.
|
||
- **변환 핵심 교훈**: nawiki `text_extracted` 는 **개행 0**인 한 덩어리라 md 청커(단락
|
||
경계 분할)가 거대 청크(4000+토큰)를 만들어 e5 512토큰 한계에서 잘렸다. → `html`
|
||
컬럼을 pandoc(`-f html -t markdown_strict-raw_html`)으로 변환 + base64/링크 정제 →
|
||
헤딩·단락 구조 복원 → 청크 중앙값 272토큰으로 정상화.
|
||
- golden: 변형 18그룹 × 4변형(한국어 용어 / 영어 용어 / 동의어·약어 / 설명형) + 대조군 10
|
||
(`/build/dogfood/namu_golden.yaml`).
|
||
|
||
## 3. 측정 결과
|
||
|
||
### 3.1 변형 일관성 (search run, hybrid k=50)
|
||
|
||
| 구성 | fully_consistent | A(MisRanked) | B(Missing) | mean_spread@10 |
|
||
|------|------------------|--------------|------------|----------------|
|
||
| baseline (별칭 off) | 14/18 | 2 | 2 | 0.222 |
|
||
| 별도-벡터 (별칭 묶음 1벡터) | 13/18 | 2 | 3 | 0.278 (악화) |
|
||
| **개선 (별칭 개별 벡터 + boilerplate skip)** | **16/18** | 1 | 1 | **0.111** |
|
||
|
||
- baseline 약점은 **전부 "설명형" 변형**(용어·약어·영어는 18그룹 전부 완벽). 자연어 설명이
|
||
문서 전문용어와 어휘가 멀어 벡터 검색이 못 잡음 = "어휘 격차".
|
||
- **별도-벡터(묶음)가 오히려 악화**한 원인 진단: ① 청크당 별칭 8개를 줄바꿈으로 묶어 한
|
||
벡터로 임베딩 → 평균화로 특정 표현 **희석** ② 나무위키 메뉴(boilerplate) 청크에도 별칭
|
||
생성 → 18문서 공통 노이즈.
|
||
- **개선판**: 별칭을 줄별 **개별 sentinel 벡터**(`{orig}#alias#N`) + boilerplate 청크 skip.
|
||
→ linked_list·sorting 회복, tcp 회귀 복구. 남은 약점은 stack·svm 설명형 2개.
|
||
|
||
### 3.2 대조군 (RAG run, refusal_correctness)
|
||
|
||
- refusal 0.6 (대조군 10개 중 6개 정상 거부, 4개 grounded).
|
||
- **false-positive 4개(graphql·oauth·react·grpc)의 인용 출처는 전부 노이즈 본문**
|
||
(GitHub_Mobile·API·Svelte), **별칭 sentinel 인용 0** → 별칭이 false-positive 를
|
||
유발하지 않음(별칭 무죄). 게다가 answer 는 "근거에서 찾을 수 없다"고 정직히 거부했는데
|
||
grounded 판정이 "부분 언급 인용 있음"을 grounded 로 오분류 → 실제 refusal 은 0.6 보다 높음.
|
||
(kebab grounded/refusal 판정의 별도 개선 여지 — HOTFIXES 후보.)
|
||
|
||
### 3.3 정답 RAG
|
||
|
||
- 변형 72개 중 대부분 grounded=True + 정답 문서 다수 인용(sort 28·linked_list 23 등). 양호.
|
||
|
||
## 4. 파생물 캐시 (V012)
|
||
|
||
별칭 18문서 재생성 2.5시간이 근본 병목. `chunk_id` 가 `ordinal+span`(위치) 기반이라
|
||
chunk_id 캐싱은 중간 수정 시 무력 → **청크 text 내용 해시**를 키로 한 범용 캐시 설계.
|
||
|
||
- `derivation_cache(cache_key, kind, payload, created_at, last_used_at)` (SQLite, V012).
|
||
- `cache_key = blake3(kind ‖ text_blake3 ‖ version_key)`. version_key 에 model/prompt/
|
||
dimensions 포함 → §9 cascade 와 정합(버전 bump 시 자동 miss).
|
||
- **위치 밀림에도 캐시가 듣는 이유**: chunk_id 는 위치(ordinal+span) 기반이라 문서 중간
|
||
삽입 시 뒤 청크의 chunk_id 가 바뀌어 row 가 재작성되지만(싼 DB write), cache_key 는
|
||
*내용 해시*라 내용 불변 청크는 히트 → 비싼 재계산(embedding/LLM) 0. chunk_id 와
|
||
cache_key 가 별개라는 게 핵심. 설계 근거·동작은 spec §1 / §3.4 참조.
|
||
- 적용: embedding(본문 + 별칭 벡터 양쪽) + 별칭 LLM. korean_tokens 는 우선순위 낮아 보류.
|
||
- **측정: 정답 3개 cold 1879초(31분) → warm 13초 ≈ 145배.** 18문서 환산 시 2.5h → ~80s.
|
||
derivation_cache 1237 엔트리(alias 140 + embedding 1097).
|
||
- 기존 KB 호환성(본문 재색인 불필요 / V012 가산 / 이전 binary mismatch / 별칭 재생성은
|
||
선택)은 설계 spec §7 참조 — 이 handoff 는 측정 과정·결과만 담는다.
|
||
|
||
## 5. KB 이식성 (외부 계산 워크플로)
|
||
|
||
- `storage_path`(asset 절대경로)는 search/ask 경로에서 **사용처 0** — 저장·재처리에서만.
|
||
- **search/ask 는 `kebab.sqlite` + `lancedb` 만으로 동작**(asset 불필요).
|
||
- 실증: 원본 KB 와 다른 경로로 복사한 portable KB(asset 제외)의 search 결과가 score·순서·
|
||
문서까지 **완전 동일**.
|
||
- 결론 워크플로:
|
||
```
|
||
[외부 CPU ollama 서버] 같은 corpus + 같은 e5 모델/버전 + 같은 parser/chunker/embedding 버전
|
||
kebab ingest → 별칭 LLM + embedding (비싼 계산, 캐시 워밍)
|
||
↓ kebab.sqlite(+derivation_cache) + lancedb/ 만 복사
|
||
[로컬] kebab search/ask → 계산 0. 증분 수정 시 외부 캐시가 머신 독립적으로 히트.
|
||
```
|
||
|
||
## 6. 결정 / 후속
|
||
|
||
- **채택**: 별칭 개별 sentinel 벡터 + boilerplate skip(효과·안전 입증) + 파생물 캐시(V012).
|
||
- **보류**: stack·svm 설명형 2그룹 추가 개선, korean_tokens 캐시, 이식용 캐시 export/import
|
||
명령, 별칭 default-on 여부(현재 off-by-default, 실사용 관찰 후 재결정).
|
||
- **별도 이슈**: grounded/refusal 판정이 부분 인용을 grounded 로 오분류 — 정직한 거부가
|
||
false-positive 로 집계됨.
|
||
- 측정 데이터: corpus `/build/dogfood/corpus/markdown/namu-wiki/`,
|
||
golden `/build/dogfood/namu_golden.yaml`, 로그 `/build/dogfood/logs/`.
|