당신의 위키엔 아직 RAG가 필요 없다 — 세컨드 브레인 구축 사다리 Tier 0→3
오해부터 정리 — “비효율”이 가리키는 세 가지 다른 문제
핵심 요점
- "LightRAG 비효율"은 ①원본 저장소 부적합(스키마 잠김·수정 반영·삭제) ②인덱싱 비용이 O(총 토큰) ③질의 지연 세 문제를 뭉뚱그린 말이며, 처방이 각각 층 분리 / 비동기·모델 분리 / 라우팅·사전계산으로 완전히 다르다
- 논문 §3.4 기준 색인 LLM 호출은 `총 토큰 ÷ chunk_size` 회다. 500편·40만 토큰 볼트의 전체 재색인은 Haiku 4.5 기준 $2.57(배치 $1.28), Opus 5 기준 $12.83 — 개인 규모의 병목은 돈이 아니라 벽시계 시간·병합 재요약의 예측 불가능성·쓰기 경로 결합이다
- LightRAG의 증분 업데이트가 GraphRAG보다 싼 이유는 커뮤니티 재구성(1,399×2×5,000 토큰)이 없기 때문일 뿐, 추출 비용 T_extract는 그대로 남는다 — '증분은 공짜'가 아니다
- 질의 지연의 구조적 하한은 직렬 LLM 라운드트립 2회(키워드 추출 + 최종 생성)다. README가 KEYWORD 역할을 '지연에 민감한 경량 non-thinking 모델'로 권고하는 것 자체가 임계 경로라는 증거
- LightRAG 논문의 평가 데이터셋(UltraDomain)은 10~94문서 / 62만~508만 토큰이다. 전형적 개인 볼트 40만 토큰은 그 최소치보다도 작고, Anthropic 가이드 기준 20만 토큰 미만이면 RAG 자체가 불필요하다
- 손익분기: 40만 토큰을 통째로 프롬프트에 넣으면 Opus 5 정가 질의당 $2 / 캐시 적중 $0.20(하루 10회면 월 $60, 기본 TTL 5분이라 개인 사용 패턴엔 캐시가 잘 안 붙음). 20만~40만 토큰 사이에 검색층의 첫 해금 조건이 있고, 그 첫 층이 그래프일 이유는 없다 — contextual retrieval은 문서 100만 토큰당 $1.02에 검색 실패율 5.7%→2.9%(−49%)
- 임베딩 모델은 색인 전에 확정해야 하고 교체 시 청크·엔티티·관계 전량 재임베딩이 필요하며 자동 마이그레이션 도구가 없다 — ①을 '튜닝'이 아니라 '층 분리'로 풀어야 하는 이유다
선행글 결론은 여기서 재논증하지 않는다
앞 글의 결론은 한 줄이다: LightRAG를 위키의 원본 저장소로 쓰지 마라. 파생 인덱스로는 유효하다. 3층 분리의 논증, 검색 모드 5종의 내부 동작, DDL·라우트·MCP 스키마는 이미 그 글에 있다. 이 글은 그 다음 질문에서 시작한다.
"비효율" 이라는 한 단어에 세 개의 다른 문제가 들어 있다
커뮤니티에서 "LightRAG 비효율" 이라고 말할 때 실제로 겪은 사건은 셋 중 하나다. 셋은 원인도, 처방도, 심각도도 다르다. 뭉뚱그린 채로는 어느 것도 못 고친다.
| # | 증상 | 실제 원인 | 처방 | 개인 규모 심각도 |
|---|---|---|---|---|
| ① | "고쳐도 반영이 안 된다 / 지운 게 남아 있다" | 스키마 잠김 + 재색인 결합 | 층 분리 (파일이 정본, 인덱스는 파생) | 🔴 치명 — 자료 손실로 이어짐 |
| ② | "인덱싱이 오래 걸리고 돈이 든다" | LLM 호출이 총 토큰 ÷ chunk_size 회 |
비동기 + 모델 분리 + 배치 API | 🟡 중간 — 돈보다 벽시계 시간 |
| ③ | "질문하면 답이 늦다" | 최소 2회 직렬 LLM 라운드트립 | 라우팅 + 사전계산 | 🟠 높음 — 하루 질의 수를 직접 깎음 |
이 글 전체가 쓰는 "개인 규모" 의 고정 정의: 문서 300~1,000편, 편당 600~1,500 토큰, 총 0.2M~1.5M 토큰, 하루 질의 5~30회, 월 증가 20~60편(≈3만~9만 토큰). 아래 수치는 전부 이 범위 기준이다.
① 원본 저장소 부적합 — 처방은 튜닝이 아니라 층 분리
LightRAG는 KV·벡터·그래프·문서상태 네 개의 저장소를 동시에 요구한다(KV_STORAGE, VECTOR_STORAGE, GRAPH_STORAGE, DOC_STATUS_STORAGE). 문제는 이 네 개가 한 몸으로 잠긴다는 점이다.
- 임베딩 모델 교체 = 전량 재색인. README가 명시한다 — 임베딩 모델은 색인 전에 확정해야 하고 질의 단계에서도 같은 모델을 써야 하며, 바꾸면 청크·엔티티·관계를 전부 다시 임베딩해야 하고 자동 마이그레이션 도구는 없다. 일부 백엔드는 벡터 차원을 테이블 생성 시점에 고정하므로 테이블 재생성까지 따라온다.
- 수정 반영은 "덮어쓰기" 가 아니다. 엔티티 병합은
FORCE_LLM_SUMMARY_ON_MERGE,MAX_SOURCE_IDS_PER_RELATION같은 파라미터가 지배하는 LLM 재요약 과정이다. 같은 문서를 고쳐 다시 넣으면 결과 그래프가 결정적으로 재현되지 않는다. - 삭제는 LLM 캐시에 의존한다. 문서 ID 단위 삭제 API는 있지만, 영향받은 엔티티·관계 복원은 색인 시 만들어둔 LLM 캐시를 재사용한다. 캐시를 날린 뒤의 삭제는 사실상 재추출이다.
권고: 마크다운 파일(또는 Postgres 테이블)이 정본이고, LightRAG 인덱스는 언제든 지우고 다시 만들 수 있는 파생물이어야 한다. 재색인이 "복구 불가능한 사건" 이 아니라 "재실행 가능한 배치" 가 되는 순간 ①은 문제이기를 그만둔다.
단, 반대인 경우: 문서가 전부 기계 생성이고 사람이 손으로 고치지 않으며 원본이 이미 다른 곳(이슈 트래커, 로그)에 있다면 층을 하나 더 두는 것은 순수한 낭비다. 이때는 LightRAG를 단일 저장소로 써도 된다.
② 인덱싱 비용 — O(문서 수)가 아니라 O(총 토큰)이고, 개인 규모에선 돈이 병목이 아니다
LightRAG 논문의 복잡도 분석은 명확하다: 색인 단계에서 LLM은 총 토큰 ÷ chunk_size 회 호출된다. 문서를 몇 편으로 쪼갰는지가 아니라 총량이 비용을 정한다. 논문 Table 3의 증분 업데이트 열도 자주 오해된다 — LightRAG는 GraphRAG처럼 커뮤니티 리포트를 재생성(1,399 × 2 × 5,000 토큰)하지 않을 뿐, T_extract 는 그대로 남는다. 증분 업데이트가 공짜인 게 아니라 커뮤니티 재구성이 없을 뿐 추출 비용은 여전히 신규 토큰에 비례한다.
실제 얼마인가. 500편 × 800토큰 = 40만 토큰 볼트를 처음부터 전체 재색인할 때(chunk 1,200 → 약 334회 호출, 호출당 입력 ≈2,700 / 출력 ≈1,000 토큰 가정):
| 추출 모델 | 정가 (입력/출력, 1M 토큰당) | 전체 재색인 1회 | Batch API(-50%) | 월 증분 40편 |
|---|---|---|---|---|
| Claude Haiku 4.5 | $1 / $5 | $2.57 | $1.28 | ≈$0.21 |
| Claude Sonnet 5 | $3 / $15 | $7.70 | $3.85 | ≈$0.62 |
| Claude Opus 5 | $5 / $25 | $12.83 | $6.42 | ≈$1.03 |
임베딩은 반올림 오차 수준이다(공시 단가 기준 40만 토큰에 1센트 미만 — 단가는 재확인 필요).
결론이 반직관적이다: 개인 규모에서 인덱싱 "비용" 은 병목이 아니다. 전체 재색인이 커피 한 잔이고 월 증분은 몇백 원이다. 실제로 사람을 괴롭히는 건 세 가지 다른 것이다 — (a) 벽시계 시간: 334회 LLM 호출은 동시성 8에서도 수 분~수십 분이고 rate limit·타임아웃(EXTRACT_LLM_TIMEOUT)에 걸린다, (b) 병합 재요약: 비용이 문서 수가 아니라 엔티티 중복도에 비례해 늘어 예측이 안 된다, (c) 쓰기 경로 결합: 저장 버튼이 LLM 호출을 기다리면 글 쓰기가 느려지고, 그러면 문서가 안 늘어난다 — 이 글이 지목하는 핵심 실패 모드다.
처방은 셋 다 같은 방향이다 — 쓰기와 색인을 떼어내고, 역할별로 모델을 나눠라.
# LightRAG는 역할별로 LLM을 따로 잡는다 (README 기준 4개 역할: EXTRACT/QUERY/KEYWORD/VLM)
# EXTRACT : 엔티티·관계 추출 → 싸고 빠른 non-thinking 모델. 색인 비용의 ~95%가 여기서 난다
# KEYWORD : 질의 키워드 추출 → 지연에 민감. 대화형 임계 경로의 앞단
# QUERY : 최종 답변 생성 → 사용자가 품질을 체감하는 유일한 지점
EXTRACT_LLM_MODEL=claude-haiku-4-5 # 이것만 바꿔도 위 표의 $12.83 → $2.57
KEYWORD_LLM_MODEL=claude-haiku-4-5
QUERY_LLM_MODEL=claude-opus-5
FORCE_LLM_SUMMARY_ON_MERGE=false # 병합 재요약 off → 비용 예측 가능(품질 트레이드오프 있음)
# 쓰기 경로에서 색인을 분리 — 저장은 파일에, 색인은 큐로
# save(note) -> git commit -> enqueue(reindex, doc_id) # 사용자는 여기서 기다리지 않는다
# worker -> 모아서 Batch API 제출(-50%) -> 실패 시 파일이 정본이므로 재실행만 하면 끝
단, 반대인 경우: 문서 총량이 5만 토큰 미만이면 이 분리 자체가 과잉설계다. 동기 색인이 몇 초에 끝나므로 큐·워커·상태 테이블을 만들 이유가 없다.
③ 질의 지연 — 구조적 하한을 먼저 계산하고 나서 최적화하라
LightRAG의 기본 mix 모드 한 번은 최소한 이렇게 흐른다: 키워드 추출 LLM 호출(1회) → 엔티티/관계/청크 벡터 검색 → 1-hop 이웃 확장 → 컨텍스트 조립 → 최종 생성 LLM 호출(1회). 즉 직렬 LLM 라운드트립이 최소 2회다. README가 KEYWORD 역할을 "가볍고 지연에 민감한 non-thinking 모델" 로 권고하는 것 자체가 이 호출이 임계 경로에 있다는 증거다.
대화형 예산을 숫자로 고정하라. 권장: 첫 토큰 2초, 완결 8초. 그리고 세 구간을 따로 재라 — 키워드 추출 / 검색·조립 / 최종 생성. 벡터 검색은 개인 규모(수천 벡터)에서 수십 ms다. 예산을 먹는 건 거의 항상 키워드 추출 라운드트립과 비대해진 컨텍스트로 인한 생성 지연이다. 처방은 라우팅(대부분의 질의는 naive/local로 충분하며, 모드 선택을 규칙으로 처리하면 키워드 LLM을 통째로 건너뛴다)과 사전계산(자주 쓰는 전역 질의의 답을 미리 만들어 둔다)이다. 구체적 라우팅 설계는 (→ Tier 1/Tier 3 섹션에서 다룸).
단, 반대인 경우: 질의가 비대화형(야간 배치 요약, 주간 리포트)이라면 ③은 문제가 아니다. mix 모드의 8~20초 지연은 그냥 지불하면 된다.
그래서 이 글이 답할 진짜 질문
세 처방 모두 "LightRAG를 어떻게 잘 쓸까" 에 대한 답이다. 그런데 개인 세컨드 브레인에는 그보다 앞선 질문이 있다.
애초에 그래프층이 필요한가?
LightRAG가 실제로 잘하는 일은 명확하다 — 여러 문서에 흩어진 엔티티를 연결해야 답이 되는 질의(멀티홉·전역 테마 질의). 논문에서 GraphRAG 대비 우위가 가장 컸던 것도 가장 큰 데이터셋(Legal)이었고, 검색 단계에서 GraphRAG가 610개 커뮤니티 리포트 × 1,000토큰 = 610,000 토큰 + 수백 회 API 호출을 태우는 자리를 LightRAG는 100토큰 미만 + 1회 호출로 대체한다. 이건 진짜 성과다.
문제는 그 성과가 어느 규모에서 측정됐는가이다.
| 데이터셋 | 문서 수 | 총 토큰 | 개인 볼트(500편×800토큰) 대비 |
|---|---|---|---|
| UltraDomain Mix | 61 | 619,009 | 1.5배 |
| UltraDomain Agriculture | 12 | 2,017,886 | 5배 |
| UltraDomain CS | 10 | 2,306,535 | 5.8배 |
| UltraDomain Legal | 94 | 5,081,069 | 12.7배 |
| 개인 볼트 (이 글의 기준) | 500 | 400,000 | — |
논문이 "그래프 우위는 데이터셋 크기에 따라 벌어진다" 고 보고한 바로 그 축에서, 전형적인 개인 볼트는 가장 작은 평가 데이터셋보다도 아래에 있다. 게다가 40만 토큰은 오늘날 1M 컨텍스트 모델에 통째로 들어간다. Anthropic의 공개 가이드는 지식 베이스가 20만 토큰(약 500페이지) 미만이면 RAG 없이 전부 프롬프트에 넣으라고 못 박는다.
그러면 검색층이 아예 필요 없다는 뜻인가? 아니다 — 여기가 손익분기다. 40만 토큰을 Opus 5에 매 질의 통째로 넣으면 정가 질의당 $2, 프롬프트 캐시 적중 시 $0.20이다. 하루 10회면 월 $60. 캐시 쓰기는 1.25배이고 기본 TTL은 5분이라, 하루 몇 번 띄엄띄엄 쓰는 개인 사용 패턴에서는 캐시가 잘 붙지 않는다. 즉 "전부 넣기" 는 20만 토큰까지 압도적으로 옳고, 40만 토큰 부근에서 비용이 꺾이며, 그 사이 어딘가에 검색층의 첫 해금 조건이 있다. 그리고 그 첫 층이 그래프일 이유는 없다 — 참고로 Anthropic의 contextual retrieval은 임베딩만으로 상위 20청크 검색 실패율을 5.7%→3.7%(−35%), BM25 결합으로 2.9%(−49%), 리랭킹까지 붙여 1.9%(−67%)로 떨어뜨리며, 문서 100만 토큰당 $1.02의 일회성 비용이다. 그래프 추출의 1/3 값에, LLM 호출은 색인 시 한 번뿐이다.
이 글의 나머지가 답할 것: (1) 만들지 않는 선택지를 포함한 Tier 0→3 사다리와 각 층의 정량 해금 조건, (2) 그래프 대신 쓸 수 있는 지식·관계층 대안들의 정면 비교 (→ 대안 비교 섹션에서 다룸), (3) 개인 규모에서 각 층의 손익분기 수치.
이 섹션의 체크리스트
- [ ] "비효율" 이라고 느낀 사건이 ①/②/③ 중 무엇이었는지 하나로 특정했다. 셋 다라면 ①부터 고친다.
- [ ] 내 볼트의 총 토큰 수를 실제로 셌다("문서 몇 편" 이 아니라). 20만 미만이면 이 글의 Tier 1 이상은 아직 필요 없다.
- [ ] 색인 비용을 추측하지 않고 계산했다:
총 토큰 ÷ chunk_size× (호출당 입력×단가 + 출력×단가). 커피값이면 ②는 문제가 아니다. - [ ] 질의 지연을 키워드 추출 / 검색·조립 / 최종 생성 세 구간으로 나눠 실측했다. 안 재고 최적화하지 않는다.
- [ ] 원본이 LightRAG 밖에 있고, 인덱스를 통째로 날렸다가 다시 만들어봤다. 못 하면 ①이 아직 안 고쳐진 것이다.
참고 출처
↗ LightRAG: Simple and Fast Retrieval-Augmented Generation (Findings of EMNLP 2025, pp. 10746-10761)↗ LightRAG: Simple and Fast Retrieval-Augmented Generation (arXiv:2410.05779)↗ HKUDS/LightRAG — GitHub README (storage backends, LLM roles, deletion, re-embedding)↗ Introducing Contextual Retrieval — Anthropic↗ Claude models overview & pricing — Anthropic↗ Prompt caching — Anthropic docs개인 규모의 실제 숫자 — 당신의 위키는 생각보다 작다
핵심 요점
- 공개 Obsidian 포럼 자기보고 데이터로 개인 규모를 고정: 문서 수 중앙값 ~6,000개, 문서당 125~375단어(기준값 250), 월 증가 20~150개(실측: 8개월에 1,000개 = 월 125개; 루만은 월 167장)
- 1만 문서 = 2.5M단어 = 약 3.3M토큰 = 마크다운 15MB. 1M 컨텍스트의 3배라 '전부 넣기'는 3,000문서에서 끝나지만, 인프라 관점에서는 ripgrep이 수십 ms에 훑는 크기다
- 벡터 인덱스를 미루는 근거는 비용이 아니다 — 1만 문서 전량 임베딩이 $0.07, 청크 2만 개가 RAM 123MB라 ANN 인덱스조차 불필요하다. 진짜 비용은 달러가 아니라 파이프라인 유지 시간이다
- '그냥 넣기'의 손익분기는 1M이 아니라 20K~200K토큰(50~500문서)이다. 캐시 TTL이 결정적 — 질의 간격이 5분 TTL을 넘으면 매번 쓰기(1.25×)라 캐시 없는 것보다 25% 비싸고, 1시간 안에 3회 이상 같은 슬라이스로 물어야 이익이다
- 비용 천장보다 품질 천장이 먼저 온다: NoLiMa는 32K 지점에서 13개 모델 중 11개가 단문 기준선의 50% 아래로 떨어졌다고 보고(GPT-4o 99.3%→69.7%). 최신 세대는 개선을 표방하므로 자기 코퍼스로 20K vs 200K A/B 실측이 필요하다
- 먼저 무너지는 것은 검색이 아니라 사람이다 — 검색은 완만하게(1만 문서에서도 수십 ms), 리뷰는 절벽으로 무너진다. 주 30분 리뷰 = 월 120문서 상한인데 에이전트 후보 생성은 월 300개. 미검토 문서는 없는 문서보다 나쁘다(검색에 걸리고 틀린다)
- 따라서 상위 티어 해금의 선결 조건은 검색 품질 지표가 아니라 '월 순증 문서 ≤ 월 리뷰 상한'이라는 부등식이다
개인 규모의 실제 숫자 — 당신의 위키는 생각보다 작다
"작다"는 형용사는 설계 판단에 못 쓴다. 숫자로 고정하자.
1. 공개된 볼트 통계: 문서 수와 문서당 길이
Obsidian 공식 포럼의 "How many notes do you have?" 스레드는 개인 볼트 규모를 자기 보고한 몇 안 되는 공개 데이터다. 단어 수까지 같이 보고한 사례만 추리면:
| 사용자 | 문서 수 | 총 단어 | 문서당 단어 |
|---|---|---|---|
| janpeeters | 2,809 | 1,047,273 | 373 |
| webinspect | 4,000 | 500,000 | 125 |
| JkNML | 18,413 | 3,200,000 | 174 |
같은 스레드의 문서 수 분포는 265 / 1,600 / 2,809 / 4,000 / 5,500 / 6,236 / 8,000 / 9,950 / 12,000 / 18,413 / 20,000 — 중앙값 약 6,000개다. 그리고 이건 "몇 개냐"는 질문에 굳이 답을 단 사람들, 즉 상위 자기선택 집단의 숫자다.
문서당 125~375단어, 실무 기준값 250단어. 이게 세컨드 브레인 문서의 실제 길이다. 블로그 글이 아니라 메모다.
월 증가분도 실측이 있다. 같은 사용자(a2jc4life)가 2022년 5월 7,000개 → 2023년 1월 8,000개를 보고했다. 8개월에 1,000개, 월 125개. 반대편 극단으로 ton은 2000년부터 5,500개 — 월 약 20개. 루만의 제텔카스텐은 45년간 90,000장, 월 약 167개(하루 5~6장)로 이 밴드의 상단이다.
개인 규모 정의(이 글에서 계속 씀): 문서 3,000~10,000개, 문서당 250단어, 월 증가 20~150개, 하루 질의 5~30회.
2. 1만 문서 = 몇 MB, 몇 토큰인가
영문 산문+마크다운 기준 대략 1단어 ≈ 1.33토큰 ≈ 6바이트로 잡고 계산한다.
| 문서 수 | 총 단어 | 총 토큰 | 마크다운 크기 | 1M 컨텍스트 대비 |
|---|---|---|---|---|
| 500 | 125K | ~170K | 0.8 MB | 17% |
| 1,000 | 250K | ~330K | 1.5 MB | 33% |
| 3,000 | 750K | ~1.0M | 4.5 MB | 100% |
| 10,000 | 2.5M | ~3.3M | 15 MB | 330% |
| 20,000 | 5.0M | ~6.6M | 30 MB | 660% |
검산: JkNML의 18,413문서/3.2M단어는 위 표의 20,000행과 거의 일치한다(4.3M토큰, 19MB).
여기서 두 가지가 동시에 참이다.
- 1만 문서는 한 프롬프트에 안 들어간다. 3.3M토큰은 1M 컨텍스트의 3배다. "전부 넣기"는 3,000문서 근처에서 끝난다.
- 1만 문서는 인프라 관점에서 우스울 만큼 작다. 15MB다.
ripgrep이 수십 ms에 훑고, Postgres FTS 인덱스는 수백 MB짜리 테이블도 아니다.
한국어는 다르다. 한국어는 같은 의미를 영어보다 많은 토큰으로 쪼갠다 — 배수는 모델 토크나이저마다 달라서 추정하지 말고 재야 한다. tiktoken은 OpenAI 토크나이저라 Claude 토큰을 15~20% 과소 집계하므로 쓰면 안 된다.
# 위키 규모 실측 — 추정 금지. 20줄이면 끝난다.
import pathlib, anthropic
VAULT = pathlib.Path.home() / "wiki"
client = anthropic.Anthropic()
docs = list(VAULT.rglob("*.md"))
text = "\n".join(p.read_text(encoding="utf-8", errors="ignore") for p in docs)
sample = text[:300_000] # count_tokens 요청 크기 제한 회피
tokens = client.messages.count_tokens( # tiktoken 아님. 모델별 실측치.
model="claude-opus-5",
messages=[{"role": "user", "content": sample}],
).input_tokens
ratio = tokens / len(sample) # 문자당 토큰
total = len(text) * ratio
print(f"문서 {len(docs):,}개 · {len(text.split()):,}단어 · {len(text.encode())/1e6:.1f}MB")
print(f"문자당 {ratio:.3f}토큰 → 전체 {total/1e6:.2f}M 토큰 (1M 창의 {total/1e4:.0f}%)")
3. 이 규모에서 벡터 인덱스의 한계효용
돈이 아니다. 1만 문서(3.3M토큰)를 text-embedding-3-small($0.02/1M)로 전량 임베딩하면 $0.07. 월 150개 신규분 재임베딩은 $0.0012, 사실상 0이다. 저장도 마찬가지 — 청크 20,000개 × 1536차원 × 4바이트 = 123MB로 RAM에 통째로 올라간다. 이 크기면 ANN 인덱스(HNSW/IVFFlat)조차 필요 없다. 20,000벡터 완전탐색은 SIMD 내적 3천만 회로 수십 ms 수준이다(하드웨어별 실측 필요). HNSW는 수백만 벡터에서 의미가 생기는 장치지, 2만 개짜리가 아니다.
그러니 Tier 1을 미루는 근거를 "비싸서"로 쓰면 틀린다. 7센트다. 진짜 비용은 파이프라인 — 청킹 규칙, 재색인 트리거, 임베딩 모델 버전 고정, 인덱스와 원본의 드리프트 감지. 이건 달러가 아니라 당신의 시간으로 계산된다. (해금 조건 자체는 → 다른 섹션에서 다룸.)
단, 반대인 경우: 다국어 혼용 볼트이거나 "그 개념을 뭐라고 불렀는지 기억 안 나는" 질의가 체감상 절반을 넘는다면, 어휘 검색의 실패가 규모와 무관하게 나타난다. 그때는 문서 수와 상관없이 Tier 1이 정당하다.
4. "관련 부분을 그냥 다 넣기"의 손익분기
Claude Opus 5는 입력 $5 / 출력 $25 per MTok, 1M 컨텍스트에 장문 할증이 없다. 프롬프트 캐시는 읽기 0.1×, 쓰기 1.25×(5분 TTL) 또는 2×(1시간 TTL). 하루 20질의 = 월 600질의로 놓고 계산하면:
| 주입량 | ≈문서 수 | 캐시 미적중 1회 | 캐시 적중 1회 | 월 600질의(적중) |
|---|---|---|---|---|
| 20K | 50 | $0.10 | $0.010 | $6 |
| 100K | 250 | $0.50 | $0.050 | $30 |
| 200K | 500 | $1.00 | $0.100 | $60 |
| 1M | 2,500 | $5.00 | $0.500 | $300 |
여기 함정이 있다. 캐시는 프리픽스 일치 + TTL이다. 5분 TTL에서 질의 간격이 5분을 넘으면 매번 쓰기(1.25×)라서 캐시 없는 것보다 25% 비싸다. 1시간 TTL은 쓰기 2×·읽기 0.1× → 같은 슬라이스로 1시간 안에 3회 이상 물어야 손익분기를 넘는다(5분 TTL은 2회). 하루 종일 띄엄띄엄 한 번씩 묻는 개인 사용 패턴은 캐시의 최악 케이스다.
그래서 정직한 결론은 이렇다.
- 캐시가 안 붙는 산발적 질의: 실효 상한은 20K~50K토큰(50~125문서). 그 위는 질의당 $0.5~5로 개인 예산을 넘긴다.
- 세션형(한 주제로 몰아서 질문): 1시간 TTL 캐시가 붙으면 **200K토큰(500문서)**까지 월 $60 선에서 성립한다. Anthropic 자신도 "지식베이스가 20만 토큰 미만이면 RAG 없이 전부 프롬프트에 넣으라"고 명시한다.
- 1M 전량 주입은 기술적으로 가능하지만 경제적으로 무의미하다. 월 $300은 개인 도구 예산이 아니다.
단, 반대인 경우: 하루 질의가 2~3회이거나 Haiku 4.5($1/$5, 200K 창)로 충분한 분류·라우팅 작업이라면 캐시 없이 200K를 그냥 밀어넣어도 월 $12 수준이다. 저빈도·저난도 구간에서는 "그냥 넣기"가 압도적으로 옳다.
5. 비용 천장보다 품질 천장이 먼저 온다
들어간다고 읽히는 게 아니다. NoLiMa 벤치마크(질문과 정답 사이 어휘 중복을 제거한 needle-in-a-haystack)는 128K+를 표방하는 13개 모델 중 32K 지점에서 11개가 단문 기준선의 50% 아래로 떨어졌다고 보고한다. GPT-4o는 99.3% → 69.7%. 즉 200K를 채워 넣는 것과 200K가 제대로 쓰이는 것은 별개다.
다만 이 측정은 2025년 초 모델 기준이고, 최신 세대는 전 구간 성능 유지를 표방한다. 당신의 코퍼스로 직접 재라 — 같은 질문 20개를 (a) 20K 슬라이스 (b) 200K 슬라이스로 던져 정답률을 비교하면 30분이면 끝난다. 표방과 실측이 다르면 실측이 이긴다.
6. 규모가 커질 때 처음 무너지는 것: 검색이 아니라 당신
숫자로 보자. 문서 250단어, 읽기 속도 250 wpm 기준.
| 소요 시간 | |
|---|---|
| 6,000문서 볼트 전체 1회 재독 | 100시간 |
| 월 신규 100개 리뷰 | 1.7시간/월 |
| 사람의 현실적 리뷰 상한(주 30분) | 월 120문서 |
| 에이전트가 하루 10개 후보 생성 시 | 월 300문서 |
검색은 규모에 대해 완만하게 나빠진다 — 1만 문서에서 BM25도 완전탐색 벡터도 여전히 수십 ms다. 리뷰 능력은 절벽으로 무너진다. 상한이 월 120개인데 생성이 월 300개면 초과분은 그냥 미검토 상태로 쌓인다.
그리고 미검토 문서는 없는 문서보다 나쁘다. 없는 문서는 검색에 안 걸리지만, 미검토 문서는 검색에 걸리고 틀린다. 인덱스는 그것이 검증됐는지 모른다. 지표는 전부 초록인데 답만 틀리는 상태 — 규모가 만드는 진짜 고장은 여기다.
그래서 사다리의 해금 조건에는 검색 품질 지표만 넣으면 안 된다. **"월 순증 문서 ≤ 월 리뷰 상한"**이 모든 상위 단계의 선결 조건이다. 이 부등식이 깨진 상태에서 Tier 2·3을 올리면, 관계층이 미검토 사실을 서로 연결해 오류를 증폭시킨다.
단, 반대인 경우: 볼트가 로그·클리핑 아카이브(정확성보다 재발견 가능성이 목적)라면 미검토 축적이 정상 운영이다. 이때는 리뷰 상한이 아니라 출처 표기와 신뢰도 태그가 통제 장치다.
이 섹션의 체크리스트
- [ ] 위 스크립트로 문서 수 / 총 토큰 / MB를 실측했다(추정치 아님).
- [ ] 총 토큰이 200K 미만이면 → 검색층을 만들지 마라. 전부 프롬프트에 넣어라.
- [ ] 총 토큰 200K~1M이면 → 슬라이스 단위로 넣어라. 벡터는 아직 이르다.
- [ ] 하루 질의 수와 질의 간격을 기록했다(간격 > TTL이면 캐시는 손해다).
- [ ] 질의당 예산 상한을 정했다(예 $0.10) → 주입 상한이 자동으로 결정된다.
- [ ] 월 순증 문서 수와 월 리뷰 가능 문서 수를 둘 다 숫자로 안다.
- [ ] 20K vs 200K 슬라이스로 동일 질문 20개 A/B를 돌려 품질 천장을 실측했다.
참고 출처
↗ Obsidian Forum — How many notes do you have? (볼트별 문서 수·단어 수 자기보고)↗ Obsidian Forum — Maximum Number of Notes in Vault (20k 파일 스트레스 테스트, 성능 한계)↗ Anthropic — Introducing Contextual Retrieval (20만 토큰 미만이면 RAG 없이 전부 프롬프트에; 실패율 35/49/67% 감소)↗ NoLiMa: Long-Context Evaluation Beyond Literal Matching (arXiv 2502.05167)↗ Anthropic — Pricing (Opus 5 $5/$25, Sonnet 5 $3/$15, Haiku 4.5 $1/$5, 1M 컨텍스트 할증 없음)↗ Anthropic — Prompt caching (읽기 0.1×, 쓰기 1.25×/2×, TTL 5분·1시간, 최소 캐시 프리픽스)↗ Anthropic — Token counting (tiktoken 금지, count_tokens API)↗ OpenAI — API Pricing (text-embedding-3-small $0.02 / 3-large $0.13 per 1M tokens)↗ Zettelkasten.de — Introduction to the Zettelkasten Method (루만 90,000장 / 45년)만들 것인가 — Obsidian + Claude Code 로 끝나는 경우
핵심 요점
- 개인 규모를 숫자로 고정하면(문서 300~3,000, 총 20만~350만 토큰, 1,000노트 ≈ 100만 토큰) 볼트 전체가 Opus 5/Sonnet 5의 1M 컨텍스트에 들어간다 — 풀어야 할 문제는 '대규모 검색'이 아니라 '100만 중 2만 고르기'이고, 이 규모에서 인프라 구축은 대개 과잉이다.
- Tier 0의 기능 목록은 이미 서버 0대로 채워진다: Claude Code의 Grep은 ripgrep 기반이고, Obsidian Local REST API 플러그인은 MCP 서버를 내장(14개 툴, JsonLogic 구조 질의)하며, CLAUDE.md 4계층 + .claude/rules/ paths 글롭 + AGENTS.md 심볼릭 링크로 여러 에이전트가 같은 지식 한 벌을 읽는다.
- Tier 0가 깨지는 조건은 문서 수가 아니라 어휘 불일치다 — 약 2,350노트 볼트 실측에서 비영어 질의 recall@5가 13%→63%(로컬 bge-m3 추가 시), 패러프레이즈 recall@10 77%. 노트는 영어인데 질의는 한국어인 경우가 진짜 승격 방아쇠다.
- 직접 구축의 정당화 후보 5가지는 모두 반박된다: 멀티 에이전트 공유=파일+심볼릭 링크, 자동 승격=스크립트+Hook, 팀 공유=git+Quartz/Publish($8/월), 데이터 소유=볼트가 이미 최대치(자작 pgvector가 오히려 모델 종속으로 덜 이식적), 산출물=검색층이 아니라 렌더 문제.
- 손익분기는 구축 100h + 유지 4h/월 기준 '하루 20질의 × 질의당 2분 절감'(6.2개월) 부근이며, 현실 사용량인 하루 5~10질의 × 1분 절감에서는 회수 불가~8.3년이다. 토큰비는 질의당 $0.05~0.2로 무시할 수준이고 구독 중이면 한계비용 0 — 비싼 것은 토큰이 아니라 100시간이다.
- 계산을 뒤집는 유일한 강력 변수는 사람 수다(팀 5인 × 하루 10질의 = 회수 2~3개월). 개인 1인이라면 '만들지 않는다'가 기본값이고, 그럼에도 만들 사람은 7개 최소 조건(파일이 원본, 8주 실패 로그 주 5건 이상, 300문서/월 20문서, 골든셋 30~50, 회수 12개월 이내, 전량 재색인 30분, 폐기 조건 명문화)을 전부 통과해야 한다.
개인 규모를 먼저 숫자로 못 박자
이 글에서 "개인 규모"는 다음을 뜻한다. 막연한 "작다"가 아니라 이 숫자로 판단한다.
- 문서 수 300~3,000개 (그 아래는 검색 문제가 아니라 문서가 안 느는 문제다)
- 노트당 600~1,200 토큰, 총 20만~350만 토큰 — 한국어 노트는 영어보다 토큰이 더 나오고, Claude 4.7 이후 모델은 같은 텍스트에 약 30% 더 많은 토큰을 쓴다. 자기 볼트로 token counting API를 돌려 실측하라.
- 하루 질의 3~15회, 월 증가 10~60 문서
이 숫자가 중요한 이유: 1,000노트 볼트 ≈ 100만 토큰이고, Opus 5 / Sonnet 5 / Sonnet 4.6의 컨텍스트 윈도우가 정확히 1M 토큰이다(베타 헤더 불필요, 표준 과금). 즉 개인 볼트 전체가 이론상 한 번의 요청에 들어간다. 실제로 다 넣으라는 뜻은 아니다 — Anthropic 문서 스스로 토큰이 늘면 정확도와 recall이 떨어지는 context rot을 명시한다. 요점은 문제의 크기다. 당신이 풀어야 할 건 "1억 토큰에서 찾기"가 아니라 "100만 중 필요한 2만을 고르기"다. 이 규모에서 인프라를 만드는 건 대개 과잉이다.
기성 조합이 실제로 어디까지 되는가
"확인해서 쓴다"는 원칙대로, 아래는 전부 공식 문서·리포지토리에서 확인한 현재 존재하는 기능이다.
| 필요 기능 | 기성 조합으로 되는가 | 확인된 근거 |
|---|---|---|
| 볼트 전문 검색 | 된다 | Claude Code의 Grep은 ripgrep 기반(ripgrep 정규식 문법). 인덱스·임베딩·재색인 지연 0 |
| 앱 밖에서 볼트 읽기/쓰기/부분 패치 | 된다 | Obsidian Local REST API 플러그인에 MCP 서버가 내장(/mcp/), 14개 툴(vault_read/write/append/patch/delete, search_query, search_simple, tag_list, command_execute 등). 대안으로 MarkusPfundstein/mcp-obsidian 7개 툴 |
| 메타데이터 구조 질의 | 된다 | 플러그인의 JsonLogic 구조 질의 + Obsidian Bases 코어 플러그인(프론트매터 기반 필터·수식·테이블/카드/맵 뷰) |
| 여러 에이전트가 같은 지식 공유 | 된다 | CLAUDE.md 4계층(managed/user/project/local) + @path import(최대 4홉) + .claude/rules/의 paths: 글롭 스코핑 + 심볼릭 링크. ln -s AGENTS.md CLAUDE.md 한 줄로 AGENTS.md(오픈소스 6만+ 프로젝트 채택, Codex·Cursor·Zed·Copilot·Gemini CLI 등이 읽음) 와 한 벌로 묶인다 |
| 프로그래밍 가능한 워크플로 | 된다 | Skill: 본문은 호출될 때만 로드(프론트매터 description+when_to_use는 1,536자에서 잘림), allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/x.sh *)로 번들 스크립트를 권한 프롬프트 없이 실행 |
| 백링크·그래프·발행 | 된다 | Obsidian 기본 백링크/그래프, Quartz(무료 OSS: 위키링크·백링크·그래프·전문검색 내장, GitHub Pages/CF 배포), Obsidian Publish($8/사이트/월 연간, 비밀번호 보호, 4GB) |
| 앱 자체 비용 | 거의 0 | Obsidian 앱 무료. 상업용 라이선스는 $50/user/년이지만 의무 아님(공식 FAQ: "you are not required to pay"). Sync $4/user/월(연간) |
즉 Tier 0의 기능 목록은 이미 서버 0대, 인덱스 0개, 스키마 0줄로 채워진다.
Tier 0가 실제로 깨지는 지점 — 문서 수가 아니라 어휘 불일치
키워드 검색의 한계는 "볼트가 커져서"가 아니라 질의어와 문서어가 다를 때 온다. 공개된 실측 하나가 이걸 정확히 보여준다. GitHub 4,000+ 스타의 obsidian-second-brain은 기본이 키워드 검색이고 임베딩은 옵션인데, 약 2,350노트 볼트에서 bge-m3(Ollama 로컬)를 얹었을 때 비영어 질의 recall@5가 13% → 63%(5배), 패러프레이즈 질의는 recall@10 77% 로 보고한다.
이 숫자를 그대로 해금 조건으로 쓰라.
- 노트를 한국어로 쓰고 한국어로 묻는다 → 어휘가 겹치므로 Tier 0로 오래 버틴다.
- 노트는 영어/코드/로그인데 질의는 한국어다 → recall이 10%대로 무너질 수 있다. 이게 Tier 1 승격의 진짜 방아쇠다.
- 단, 반대 조건: 이건 특정 볼트 1개의 자체 측정이다. 네 볼트에서 재현될지는 확인 필요 — 승격 전에 실패 질의 30건으로 직접 재보라(골든셋 구성법은 → 선행글에서 다룸).
직접 만들 "정당한 이유" 5개와 반박
| 만들어야 하는 이유(주장) | 반박 — 기성으로 되는 이유 | 반대 조건(이때는 만들어라) |
|---|---|---|
| 여러 에이전트가 같은 지식 공유 | 지식은 파일이고 파일은 이미 공유된다. AGENTS.md↔CLAUDE.md 심볼릭 링크 + .claude/rules/ 심볼릭 링크로 프로젝트 간 한 벌 유지 |
에이전트가 서로 다른 머신/컨테이너/클라우드에 산다. Claude Code 자동 메모리는 명시적으로 머신 로컬이고, 클라우드·Cowork 세션은 ~/.claude/skills/를 읽지 않는다. 단 이때 필요한 건 "위키"가 아니라 git 리모트 하나인 경우가 대부분 |
| 프로그래밍 가능한 검색·자동 승격 | 승격 규칙은 파이썬 30줄 + 크론이면 끝난다. Skill이 스크립트를 번들해 무프롬프트 실행하고, Hook이 세션 종료 시점에 건다 | 승격 판정이 인덱스 상태(임베딩 유사도, 그래프 중심성, 중복 클러스터)를 입력으로 요구한다. 파일만으로는 계산이 안 된다 → Tier 1/2 (→ 다른 섹션) |
| 팀 공유 | git 리포 + Quartz(무료) 또는 Publish($8/월, 비밀번호). 3~5명이면 이걸로 충분 | 문서 단위 접근 제어(부서별·고객사별), 감사 로그, 비개발자 편집자가 필요하다. 이때도 만드는 건 검색층이 아니라 인증 붙은 문서 서버 |
| 데이터 소유·오프라인 | 마크다운 볼트가 이미 소유권의 최대치다. ripgrep은 완전 오프라인. 오히려 자작 pgvector가 덜 이식적이다 — 임베딩은 모델에 종속돼 모델을 바꾸면 전량 재색인 | 망분리라 임베딩까지 로컬이어야 한다. 단 이것도 서버를 요구하지 않는다 — Ollama + bge-m3를 스크립트로 돌리는 방식이 이미 존재한다 |
| 산출물 생성(보고서·사이트·슬라이드) | 이건 검색층 문제가 아니라 렌더 문제다. Quartz/Publish/Pandoc의 영역 | 산출물이 매일 자동 발행되고 실패를 감지해야 한다. 그래도 만드는 건 "위키"가 아니라 발행 잡 + 상태 저장소 |
다섯 개 중 산술을 통과하는 건 사실상 사용자 수가 곱해지는 경우 하나뿐이다. 아래 계산이 그 이유다.
손익분기 — 몇 개월 써야 회수되는가
가정을 먼저 노출한다. 시니어 1인 + 에이전트 보조로 Tier 1(하이브리드 인덱스 + MCP 노출)을 만들 때:
| 항목 | 시간 |
|---|---|
| 인제스트·청킹·프론트매터 파서 | 8h |
| 스키마·마이그레이션 | 6h |
| 증분 색인(mtime/해시)·임베딩 파이프라인 | 10h |
| 하이브리드 검색 + 순위 융합 | 12h |
| API + MCP 래핑 | 10h |
| 평가 골든셋·회귀 하네스 | 12h |
| 배포·백업·재색인 크론 | 8h |
| 디버깅 버퍼(+40%) | 26h |
| 합계 | ≈ 92h (보수적으로 60~120h) |
유지비는 월 2~6시간(모델 교체 시 전량 재색인, 볼트 구조 변경, 색인 실패 복구). 구축 100h / 유지 4h·월로 잡고, 절감 시간이 회수되는 시점을 계산하면:
| 하루 질의 | 질의당 절감 | 월 절감 | 회수 시점 |
|---|---|---|---|
| 5 | 1분 | 2.5h | 회수 불가(유지비 미만) |
| 10 | 1분 | 5h | 100개월 = 8.3년 |
| 10 | 2분 | 10h | 17개월 |
| 20 | 2분 | 20h | 6.2개월 |
| 30 | 1분 | 15h | 9.1개월 |
| 30 | 2분 | 30h | 3.8개월 |
| 50 | 2분 | 50h | 2.2개월 |
손익분기선은 "하루 20질의 × 질의당 2분 절감" 부근이다. 그런데 개인 세컨드 브레인의 현실 사용량은 하루 3~15질의다. 표의 위쪽 두 줄이 대부분의 독자다 — 회수되지 않는다.
토큰 비용은 이 계산에 거의 영향이 없다. Sonnet 5 기준(2026-08-31까지 $2/$10 per MTok, 이후 $3/$15) grep 왕복 4~6회 + 파일 5~8개 읽기 질의 1건은 캐시 히트(0.1배)를 감안해 $0.05~0.10 수준, Opus 5($5/$25)로도 $0.2 안팎이다. 하루 10질의면 월 $20~60. 그리고 이미 Claude Pro($17~20/월)나 Max($100/월~) 구독 중이라면 한계 비용은 0이다. 즉 비싼 건 토큰이 아니라 당신의 100시간이다.
단, 반대 조건: 사용자 수는 질의 수에 곱해진다. 팀 5명 × 하루 10질의 = 하루 50질의 → 회수 2~3개월. 직접 구축을 정당화하는 유일한 강력 변수는 "사람 수"다. 개인 1인이면 만들지 마라, 팀 5인 이상이면 계산이 뒤집힌다.
"만들지 않는다"는 정식 선택지다
기본값을 이렇게 잡아라. 서버 0대, 인덱스 0개, 유지보수 0시간.
# Tier 0: 인프라 없이 볼트를 모든 에이전트에 붙인다
VAULT=~/vault && cd "$VAULT"
# 1) 지식 한 벌을 모든 에이전트가 읽게 (Claude Code는 CLAUDE.md만 읽는다)
ln -s AGENTS.md CLAUDE.md
# 2) 도메인 규칙은 경로 스코프로 — 매 세션 컨텍스트를 먹지 않게
mkdir -p .claude/rules
cat > .claude/rules/decisions.md <<'EOF'
---
paths: ["decisions/**/*.md"]
---
- 결정 노트에는 상태(제안/채택/폐기)와 "무엇이 관측되면 이 결정을 뒤집는가"를 남긴다.
- 결정을 뒤집을 땐 원본을 지우지 말고 supersedes 프론트매터로 연결한다.
EOF
# 3) 승격·정리는 인덱스가 아니라 스크립트로 (본문은 호출 시에만 로드된다)
mkdir -p .claude/skills/promote/scripts
cat > .claude/skills/promote/SKILL.md <<'EOF'
---
name: promote
description: 지난 7일 데일리 노트에서 3회 이상 반복 참조된 주제를 영구 노트 후보로 승격한다.
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/candidates.sh *), Read, Edit, Grep
---
1. `${CLAUDE_SKILL_DIR}/scripts/candidates.sh` 로 후보를 뽑는다.
2. 후보마다 기존 영구 노트와 중복인지 rg 로 확인한다.
3. 중복이면 기존 노트에 병합, 아니면 초안을 만들고 반증 조건을 채운다.
EOF
# 4) 실패 로그 — 이게 유일한 해금 근거다
echo "date,query,found,seconds" > .metrics/search-misses.csv
마지막 줄이 핵심이다. Tier 0를 운영하는 목적은 편하게 쓰는 것만이 아니라, 올라갈 근거를 모으는 것이다. 실패 로그 없이 올라간 사다리는 되돌릴 수 없다.
단, 반대 조건: 이미 볼트가 3,000문서를 넘고 매주 "분명히 썼는데 못 찾는" 일이 5건 이상이면 Tier 0 유지가 오히려 손해다. 그건 아래 최소 조건이 이미 충족된 상태다.
그럼에도 만들 사람을 위한 최소 조건
7개 전부 통과할 때만 시작하라. 하나라도 미달이면 그 항목을 먼저 채워라.
- 원본은 파일로 남긴다. 인덱스는 언제든 지우고 다시 만들 수 있는 파생물이어야 한다(→ 선행글의 결론).
- Tier 0를 최소 8주 운영했고 실패 질의 로그가 있다. 주당 실패 5건 미만이면 만들지 마라.
- 볼트 300문서 이상 + 월 증가 20문서 이상. 그 아래면 문제는 검색이 아니라 유입이다.
- 골든셋 30~50문항이 이미 있다. 개선을 측정할 수 없으면 개선하는 게 아니라 바꾸는 것뿐이다(구성법 → 선행글).
- 회수 계산이 12개월 이내로 나온다. 위 표에 네 실제 질의 수를 넣어 계산하고, 그 숫자를 문서에 적어라.
- 전량 재색인이 30분 이내다. 임베딩 모델은 반드시 바뀐다. 재색인이 하루 걸리면 그 인덱스는 영원히 낡은 채로 남는다.
- 그만둘 조건을 미리 적었다. "3개월 뒤 골든셋 점수가 Tier 0 대비 15%p 이상 안 오르면 폐기한다" 같은 문장이 없으면, 실패해도 폐기되지 않는다.
참고 출처
↗ Claude Code — How Claude remembers your project (CLAUDE.md hierarchy, @imports, .claude/rules/, auto memory)↗ Claude Code — Tools reference (Grep is built on ripgrep)↗ Claude Code — Extend Claude with skills (SKILL.md, ${CLAUDE_SKILL_DIR}, 1,536-char description cap)↗ Claude Code — Create custom subagents (.claude/agents/, what loads at startup)↗ AGENTS.md — open format for guiding coding agents (60k+ open-source projects)↗ coddingtonbear/obsidian-local-rest-api — REST API and built-in MCP server for your vault↗ MarkusPfundstein/mcp-obsidian — MCP server for Obsidian via the Local REST API plugin↗ eugeniughelbur/obsidian-second-brain — keyword-first vault memory with optional bge-m3 semantic layer (13%→63% recall@5 on non-English queries)↗ Obsidian pricing — Sync $4/user/mo, Publish $8/site/mo, commercial license not required↗ Obsidian Bases — database-like views over note properties (core plugin)↗ Quartz — free static-site generator with backlinks, graph view and full-text search for Obsidian vaults↗ Anthropic — Pricing (Opus 5 $5/$25, Sonnet 5 $2/$10 through 2026-08-31, cache hits 0.1x)↗ Anthropic — Context windows (1M tokens on Opus 5 / Sonnet 5 / Sonnet 4.6, context rot)↗ Anthropic — Introducing Contextual Retrieval ($1.02 per million document tokens)Tier 0 — 마크다운 + 파일 검색 + 에이전트, 인덱스 0개
핵심 요점
- Tier 0 의 스펙은 '만들지 않는 것' 목록이다 — 임베딩·벡터스토어·청킹·갱신 잡·검색 API·재랭커·골든셋 전부 0개이고, 만드는 것은 디렉터리·파일명 규약·진입점 문서·탐색 규약 파일 4개뿐. 개인 규모는 문서 100~500개 / 1.5~5MB / 월 증가 10~40건 / 하루 질의 5~30건으로 고정한다.
- 실측: 문서 171개 · 2.94MB · 23,653줄(한글 24.7%) 코퍼스에서 ripgrep 14.1.1 전수 스캔이 warm 기준 10.8~14.1ms. 한글 정규식 `[가-힣]{2,}장애` 도 10.8ms. 이 규모에서 ANN 인덱스가 이길 여지가 없다 — 단, 파일 100만 개 이상 트리에서는 rg 도 십수 초로 무너진다.
- grep 이 이기는 다섯 번째 축(가장 저평가됨)은 '실패가 조용하지 않음'이다. 벡터 검색은 답이 없어도 top-k 를 돌려주므로 '이 위키엔 없다'는 신호가 구조적으로 소실되는데, 개인 위키의 진짜 실패가 '문서가 안 늘어남'이라면 정직하게 0건을 반환하는 검색이 먼저 필요하다. 단, grep 0건은 부재의 증거가 아니므로 규약에 '어휘 2회 변경 후 재시도'를 명시해야 한다.
- 토큰 비대칭이 설계 전부다 — Anthropic 공식 컨텍스트 문서 기준값으로 파일 1건 Read 1,100~2,400 토큰 vs grep 1회 600~800 토큰. 진입점 문서 예산 200줄/25KB 도 임의 수치가 아니라 Claude Code auto memory 의 실제 로드 한계(MEMORY.md 첫 200줄 또는 25KB)와 CLAUDE.md 권고치에서 온다. `@path` import 는 최대 4홉이지만 총 컨텍스트는 줄지 않는다.
- 한국어는 교착어 + 접미 굴절이라 어근 부분문자열 하나가 굴절형을 흡수한다(`배포` → 배포를·배포에·배포했다·재배포). 공백 토크나이저 BM25 는 여기서 먼저 깨지고, 제대로 하려면 textsearch_ko(MeCab) 나 ES nori 를 별도 운영해야 한다 — Tier 1 의 첫 청구서. 다만 용언 어간 변화·한영 혼용·띄어쓰기 변이 4종에서는 grep 이 진다.
- 붕괴 증상 6종을 측정법과 함께 고정: ①한 패턴이 파일 절반 히트(실측 `배포` 897히트/84파일/171개 중 49%) ②답을 알면서 패턴을 못 만듦 ③질의당 grep→read 왕복 3회 초과 ④0건인데 실제로는 있었음 ⑤진입점 200줄 초과 ⑥요약·비교형 질의 비중 상승. 3개 이상이 2주 연속이면 Tier 0 종료.
Tier 0 의 정의 — "만들지 않는 것" 목록이 곧 스펙
Tier 0 은 인덱스를 하나도 만들지 않는 구성이다. 저장소는 마크다운 파일 트리 하나, 검색은 에이전트가 직접 부르는 glob/grep/read, 그 위에 사람이 손으로 쓴 진입점 문서 몇 개가 전부다. 이 단계에서 만들지 않는 것을 먼저 못 박는 편이 빠르다.
만들지 않는 것: 임베딩 파이프라인, 벡터 스토어, 청킹 규칙·청크 테이블, 인덱스 갱신 잡(cron/워커/큐), 검색 API 서버, 재랭커, 평가 골든셋. 만드는 것은 4개뿐: 디렉터리 구조 · 파일명 규약 · 진입점 문서 · 탐색 규약 파일.
이 글에서 "개인 규모"는 다음으로 고정한다 — 문서 100~500개, 원본 1.5~5MB, 월 증가 10~40건, 하루 질의 5~30건. 이 범위 안에서는 Tier 0 이 자주 이긴다.
소규모에서 grep 이 이기는 다섯 가지 축
Anthropic 은 Claude Code 를 "just-in-time" 검색으로 설계한 이유를 공식 엔지니어링 글에서 이렇게 쓴다 — glob 과 grep 이 "stale indexing 의 문제를 우회한다", 그리고 "폴더 계층·명명 규약·타임스탬프가 모두 중요한 신호"라고. 초기 Claude Code 는 RAG + 로컬 벡터DB 였고, 팀은 곧 에이전틱 검색으로 갈아탔다.
| 축 | Tier 0 (grep) | 인덱스 기반 | 개인 규모에서의 함의 |
|---|---|---|---|
| 정확 일치 | 리터럴/정규식 precision 사실상 1.0 | 임베딩은 근사 — 오탐 상수 존재 | 고유명사·에러코드·경로·플래그 질의가 개인 위키 질의의 다수 |
| 인덱싱 지연 | 0초 (저장 즉시 검색) | 청킹+임베딩 왕복 필요 | 방금 쓴 노트를 30초 뒤에나 찾는 건 캡처 습관을 죽인다 |
| 신선도 | 드리프트라는 상태가 없음 | 원본↔인덱스 불일치 감시 필요 | 감시 대상이 0개면 운영 부하도 0 |
| 검증 가능성 | 경로:줄번호 로 사람이 즉시 반증 |
"코사인 0.83" 은 반증 불가 | 위키의 신뢰는 반증 가능성에서 온다 |
| 실패 양상 | 0건이 명시적으로 나온다 | top-k 는 항상 무언가를 돌려준다 | "없음"과 "틀림"이 구분돼야 문서를 새로 쓴다 |
다섯 번째 축이 가장 저평가돼 있다. 벡터 검색은 코퍼스에 답이 없어도 유사도 상위 k개를 반환하므로, "이 위키엔 그 내용이 없다"는 신호가 구조적으로 소실된다. 이 글의 논지 — 개인 세컨드 브레인의 실패는 검색 품질이 아니라 문서가 안 늘어나는 데서 온다 — 를 받아들인다면, 초기에 필요한 검색은 정확한 검색이 아니라 정직하게 0건을 돌려주는 검색이다.
단, 반대 조건: grep 0건은 "부재의 증거"가 아니다. 표기가 달랐을 뿐일 수 있다. 0건을 곧바로 "없으니 새로 쓰자"로 읽는 순간 중복 문서가 쌓인다. 탐색 규약 파일에 "0건이면 어휘를 2회 바꿔 재시도한 뒤에만 부재로 판정"을 명시해야 이 축이 실제로 작동한다.
운영비 관점도 같은 방향이다. Tier 0 의 월 유지비는 0원 / 0시간이다 — 갱신할 인덱스가 없으니 장애도, 재인덱싱도, 스키마 마이그레이션도 없다. 반대로 인덱스를 하나라도 두는 순간 "원본이 바뀌었는데 인덱스가 안 따라왔다"는 상태가 생기고, 그걸 감지하는 장치와 그 장치를 검증하는 절차가 따라붙는다. 문서 300개 규모에서 이 운영 표면적은 검색 품질 개선분을 거의 확실히 상회한다.
실측 — 이 규모에서 grep 은 몇 ms 인가
로컬 마크다운 코퍼스 실측(macOS, ripgrep 14.1.1, warm cache, 5회 평균):
| 항목 | 값 |
|---|---|
| 문서 수 / 총량 | 171개 / 2.94 MB / 23,653줄 / 194만 자(한글 24.7%) |
전수 스캔 rg "배포" |
13.7 ms |
전수 스캔 rg "deploy" |
14.1 ms |
정규식 교대 rg "롤아웃|rollout" |
11.1 ms |
한글 클래스 정규식 rg "[가-힣]{2,}장애" |
10.8 ms |
즉 이 규모에서 전수 스캔이 15ms 미만이다. 임베딩 검색의 ANN 조회가 아무리 빨라도 여기서 이길 여지는 없고, 얻는 것도 없다. ripgrep 공식 벤치마크 기준 단일 대용량 파일 리터럴 검색 처리량은 약 3.7 GB/s(1GB 영문 0.268초), GNU grep 대비 약 2배다 — 3MB 짜리 개인 위키는 이 곡선의 원점 근처다. 반대 조건: 파일 100만 개 이상 트리에서는 rg 도 검색당 십수 초로 무너진다. 개인 위키는 그 근처에도 못 간다.
토큰 쪽 기준값은 Anthropic 공식 컨텍스트 시뮬레이터에서 가져올 수 있다 — 세션 시작 시 시스템 프롬프트 4,200 / 프로젝트 CLAUDE.md 1,800 / auto memory 680 토큰, 파일 1건 Read 1,100~2,400 토큰, grep 호출 1건 600~800 토큰. 파일 하나 읽는 비용이 grep 두 번보다 비싸다는 이 비대칭이 Tier 0 설계의 전부다: grep 으로 후보를 좁히고 Read 는 최소로.
구성 요소 4개
| 요소 | 규칙 | 안 지켰을 때의 증상 |
|---|---|---|
| 디렉터리 | 깊이 3층 이내, 폴더 = grep 스코프 단위(에이전트가 -g 로 자를 수 있는 축) |
스코프를 못 잘라 매 질의가 전수 스캔 → 히트 폭발 |
| 파일명 | YYYY-MM-DD-<타입>-<한글슬러그>-<en-slug>.md. 한/영 키워드 병기 |
Glob 단계에서 못 걸러 전부 Read → 토큰 폭증 |
| 진입점 문서 | index.md 1개 + 도메인별 MOC. 200줄 / 25KB 상한 |
진입점이 길어지면 매 세션 컨텍스트를 갉아먹고 준수율도 떨어짐 |
| 탐색 규약 | CLAUDE.md(또는 AGENTS.md)에 검색 순서·중단 조건 명시 |
에이전트가 Glob 없이 Read 부터 시작 |
200줄/25KB 는 임의 숫자가 아니다. Claude Code 의 auto memory 는 MEMORY.md 를 **"첫 200줄 또는 25KB 중 먼저 오는 것"**까지만 로드하고 나머지는 토픽 파일로 밀어낸다. CLAUDE.md 자체 가이드도 파일당 200줄 미만을 권고한다 — "긴 파일은 컨텍스트를 더 쓰고 준수율을 낮춘다". 진입점 문서를 이 예산에 맞추면, 인덱스를 안 만들고도 "항상 로드되는 목차"를 공짜로 얻는다.
폴더=수명주기 / 태그=상태 / 링크=의미의 축 분리 자체는 선행 글에서 다뤘으므로 생략한다. Tier 0 에서 추가로 필요한 건 하나뿐이다 — 폴더가 rg -g 로 자를 수 있는 경계와 일치할 것.
탐색 규약 파일을 진입점 문서와 분리하는 이유는 독자가 다르기 때문이다. 진입점 문서(index.md)는 사람과 에이전트가 같이 읽는 목차이고, 탐색 규약(CLAUDE.md)은 에이전트만 읽는 절차다. 둘을 합치면 목차가 절차 문장으로 오염돼 200줄 예산을 금방 넘긴다. 규약이 길어지면 @path import 로 쪼갤 수 있지만(최대 4홉), import 된 파일도 시작 시 전부 컨텍스트에 들어가므로 총량은 줄지 않는다 — 조직 목적일 뿐 예산 절감이 아니다.
<!-- ~/wiki/CLAUDE.md — 탐색 규약 (진입점 문서와 분리해서 관리) -->
## 이 볼트를 검색하는 순서
1. `index.md` 를 먼저 읽어 도메인 MOC 경로를 확인한다. (전수 스캔 금지)
2. 파일명으로 좁힌다: `rg --files -g '**/*<키워드>*.md'`
3. 내용 검색: `rg -n -i -g '!archive/**' '<패턴1>|<패턴2>'`
- 한국어는 **어근만** 넣는다. `배포` (O) / `배포했다` (X)
- 한/영 혼용 개념은 반드시 교대로: `배포|deploy`
4. 히트가 30건 초과면 스코프를 좁힌다: `-g 'projects/**'` 또는 `-g '2026-*'`
5. Read 는 상위 3개 파일까지만. 그 이상 필요하면 사람에게 되묻는다.
## 0건일 때
- **0건 = 부재 아님.** 어휘를 2회 바꿔 재시도한다
(동의어 1회, 영문 표기 1회). 그래도 0건이면 부재로 판정하고
`inbox/` 에 새 노트 생성을 제안한다.
## 금지
- 볼트 전체를 Read 하지 않는다. `cat **/*.md` 금지.
- 파일을 옮기거나 이름을 바꾸지 않는다(링크가 깨진다). 제안만 한다.
한국어에서 grep 이 오히려 유리한 지점
한국어는 교착어이고 굴절이 접미로 붙는다. 어근이 앞에 오고 조사·어미가 뒤에 붙으므로, 어근 부분문자열 하나가 굴절형 전체를 흡수한다. rg '배포' 는 배포를·배포에·배포했다·재배포를 한 번에 잡는다. 형태소 분석기 없이.
반대로 토크나이저 기반 BM25 는 이 지점에서 먼저 깨진다. Postgres 기본 FTS 에는 한국어 설정이 없어 공백 토크나이저를 쓰면 "배포에서는"이 통째로 한 토큰이 되고 "배포" 질의와 매칭되지 않는다. 제대로 하려면 textsearch_ko(MeCab + mecab-ko-dic) 나 Elasticsearch nori(mecab-ko-dic 기반 형태소 분석) 를 별도로 설치·운영해야 한다. 이것이 Tier 1 의 첫 청구서다.
| 한국어 질의 유형 | rg 부분문자열 | 공백 BM25 | 형태소 BM25 |
|---|---|---|---|
명사 + 조사 (배포를, 배포에서) |
✅ 어근으로 흡수 | ❌ 미스 | ✅ |
복합명사 (재배포, 자동배포) |
✅ 부분일치 | ❌ | △ 분해 모드 의존 |
용언 어간 변화 (듣는↔들었다) |
❌ | ❌ | ✅ |
한/영 혼용 (배포↔deploy) |
△ 수동 교대 필요 | ❌ | ❌ (동의어 사전 필요) |
띄어쓰기 변이 (세컨드브레인↔세컨드 브레인) |
△ 패턴 2개 | ❌ | △ |
규모 감각을 위해: MIRACL 한국어 서브셋은 1,486,752 패시지이고, Lucene 언어별 분석기를 쓴 BM25 의 nDCG@10 은 0.419(Recall@100 0.783)다. 즉 형태소 분석기를 제대로 붙여도 백만 패시지 규모에서는 0.42 수준이다. 문서 200개 규모에서는 이 지표 자체가 의미를 갖지 못한다 — 후보 집합이 애초에 좁아 정확 일치가 지배하기 때문이다. 어휘 검색의 품질 논쟁은 코퍼스가 커진 뒤의 문제다.
단, 반대 조건: 위키가 영문 논문 노트·영문 코드 주석 위주라면 위 이점은 사라진다. 영어는 굴절이 어미 변화(run/ran/running)라 어근 부분문자열이 안 먹고, 이 경우 Tier 0 의 한국어 프리미엄은 0 이다.
Tier 0 이 무너질 때 나타나는 증상
해금 조건의 정량 트리거는 다른 섹션에서 다루므로(→ Tier 1 섹션), 여기서는 관측되는 증상과 그 측정법만 고정한다. 세 개 이상이 2주 연속 관측되면 Tier 0 은 끝난 것이다.
| # | 증상 | 측정법 | 해석 |
|---|---|---|---|
| 1 | 한 패턴이 파일의 절반을 때린다 | rg -l <패턴> | wc -l ÷ 전체 문서 수 |
실측 예: 배포 → 897 히트 / 84 파일 / 171개 중 49%. 이 지점에서 grep 은 필터가 아니다 |
| 2 | 답을 아는데 패턴을 못 만든다 | "정답 문서를 알면서 검색 실패"한 횟수를 주간 기록 | 동의어·개념 질의 비중이 넘어섰다는 신호 |
| 3 | 질의당 grep→read→grep 왕복 3회 초과 | 세션 로그의 도구 호출 수 | 토큰 비용이 인덱스 유지비를 추월하는 지점 |
| 4 | 0건인데 실제로는 있었다 | 재시도 후 발견된 건수 / 전체 0건 응답 | 표기 변이 누적. 어휘 정규화가 필요 |
| 5 | 진입점 문서가 200줄을 넘어 계속 자란다 | wc -l index.md |
사람이 만든 인덱스가 한계에 도달 |
| 6 | "요약해줘/비교해줘"류 질의 비중 상승 | 질의 로그 분류 | 문자열 검색으로는 원리적으로 답이 안 나오는 유형 |
증상 3의 반대편 근거도 적어둔다: 벡터 인덱스 진영에서는 grep 전용 검색 대비 토큰 사용량 40% 감소, recall 손실 없음을 주장한다(Milvus/Claude Context). 다만 저장소 규모·질의 셋이 공개돼 있지 않아 개인 규모로 외삽할 수 없다. 본인 위키에서 직접 측정해서 정하라 — 증상 3의 임계를 정하는 유일한 방법이다.
Tier 0 을 아예 건너뛰어야 하는 경우
| 조건 | 이유 |
|---|---|
| 시작 시점에 이미 문서 2,000개+ / 30MB+ 를 이관해온다 | 파일명 규약이 없는 기존 자산은 Glob 이 안 먹는다 |
| 원본이 PDF·스캔·이미지 중심 | grep 대상이 없다. 텍스트 추출층이 먼저다 |
| 여러 사람이 동시에 쓰고 표기가 통일되지 않는다 | 어휘 변이가 선형이 아니라 사용자 수만큼 곱해진다 |
| 질의의 다수가 "무엇과 무엇이 관련있나" 형태 | 문자열 검색의 표현력 밖이다 |
| 에이전트가 파일시스템에 직접 닿지 못한다(웹 전용 UI) | Tier 0 의 전제 자체가 성립하지 않는다 |
반대로 위 다섯이 전부 아니라면, 첫 3~6개월은 인덱스를 만들 이유가 없다. 이 기간에 벌어야 하는 것은 검색 품질이 아니라 문서 수와 파일명 규약의 일관성이고, 그 둘이 다음 단계의 입력값이 된다.
참고 출처
↗ Effective context engineering for AI agents — Anthropic Engineering↗ How Claude remembers your project (CLAUDE.md · auto memory 200 lines/25KB) — Claude Code Docs↗ Explore the context window (startup token budget, Read/grep token costs) — Claude Code Docs↗ ripgrep is faster than {grep, ag, git grep, ucg, pt, sift} — Andrew Gallant↗ MIRACL: A Multilingual Retrieval Dataset Covering 18 Diverse Languages (Korean BM25 nDCG@10 0.419)↗ Korean (nori) Analysis Plugin — Elasticsearch Reference↗ textsearch_ko — MeCab 형태소 분석기 기반 PostgreSQL 한국어 전문검색 확장모듈↗ Why I'm Against Claude Code's Grep-Only Retrieval: It Just Burns Too Many Tokens — Milvus Blog↗ Claude Code Doesn't Index Your Codebase. Here's What It Does Instead. (Boris Cherny 인용)Tier 1 — 어휘 + 벡터 하이브리드, 대부분은 여기서 멈춘다
핵심 요점
- 개인 규모를 숫자로 고정(문서 300~3,000편 / 청크 2,000~12,000개 / 하루 질의 10~50회 / 월 20~60편)하고 모든 판단을 이 분모 위에서 함
- sqlite-vec 전수 스캔 KNN 을 직접 측정(M4 Pro, 1024차원): 5k=4.6ms, 20k=14.5ms, 100k=54.7ms — 1,000벡터당 0.55ms 선형. 개인 규모 상한 12,000청크에서 약 7ms 라 ANN 부재는 병목이 아니고, 100ms 를 넘기려면 15배 성장이 필요하다
- 핵심 반전 — 로컬 임베딩의 근거는 비용이 아니다. 12,000청크 전량을 OpenAI text-embedding-3-small 로 임베딩해도 $0.144(10회 재색인 $1.44). 로컬을 고르는 실제 이유는 프라이버시·재색인 자유(bge-m3 실측 30.2청크/s → 전량 1.7~6.6분)·벤더 모델 폐기 리스크다
- 한국어 어휘 검색은 SQLite 와 PostgreSQL 이 똑같이 못 한다(실측: `임베딩` 이 `임베딩을` 을 둘 다 놓침; PG 16 기본 텍스트검색 설정 29개에 한국어 없음). 따라서 DB 선택이 한국어 품질을 결정하지 않으며, 어휘 채널의 실제 효용은 식별자·버전·에러코드·영문약어 같은 정확 토큰에 한정된다. 가장 싼 우회는 FTS5 trigram 추가 인덱스(+29% 크기, 단 2글자 질의는 구조적 불가)
- 한국어 임베딩 모델 근거: NDCG@10 KURE-v1 0.6947 > bge-m3 0.6872 > multilingual-e5-large 0.6637 > OpenAI text-embedding-3-large 0.6167. 영어 중심 모델의 실패는 극적이어서 초소형 한국어 패러프레이즈 프로브에서 bge-m3 4/4 vs nomic-embed-text 0/4(n=4 스모크 테스트임을 명시)
- 구축 8~14시간(주말 하나)·유지 월 0~30분. 멈춰야 하는 근거 3개(작은 분모, 인덱싱 비용 구조 역전으로 인한 '동결', 실패 원인이 검색이 아님)와 멈추면 안 되는 정량 조건 5개(다홉 질의 30%↑, 청크 50k↑ & 재현율 70%↓, 별칭 5개↑, 상시 시간축 질의, 다인 사용) 중 2개 이상 충족을 트리거로 제시
구성은 사실상 하나뿐이다
Tier 1 은 설계 여지가 넓지 않다. 어휘 인덱스 1개 + 벡터 인덱스 1개 + RRF 순위 병합, 이게 전부다. 리랭커·쿼리 재작성·HyDE 는 Tier 1 이 실제로 부족하다는 걸 측정한 뒤의 이야기지 초기 구성이 아니다.
# 두 결과를 점수가 아니라 '순위'로만 합친다 (RRF, k=60 은 관례값)
def rrf(bm25_ids, vec_ids, k=60, w_lex=1.0, w_vec=1.0, top=8):
score = {}
for rank, doc in enumerate(bm25_ids, 1):
score[doc] = score.get(doc, 0.0) + w_lex / (k + rank)
for rank, doc in enumerate(vec_ids, 1):
score[doc] = score.get(doc, 0.0) + w_vec / (k + rank)
return sorted(score, key=score.get, reverse=True)[:top]
# 호출부: 각 채널에서 넉넉히 뽑고(보통 30~50) 병합 후 top-8 만 LLM 에 넘긴다
hits = rrf(fts_search(q, limit=40), knn_search(embed(q), limit=40))
RRF 가 개인 규모에 맞는 이유는 정확도가 아니라 튜닝 표면적이다. BM25 점수와 코사인 거리를 정규화해 섞을 필요가 없어서 만질 손잡이가 k와 가중치 2개뿐이다. 단, 두 채널의 품질 차이가 큰 도메인(예: 코드 검색)에선 가중치를 1:1 로 두면 좋은 채널이 나쁜 채널에 끌려 내려간다 — 그땐 반대로 점수 기반 가중합이 낫다.
"개인 규모" 를 숫자로 고정한다
이 글에서 아래 숫자를 벗어나면 결론도 달라진다.
| 항목 | 값 |
|---|---|
| 문서 수 | 300 ~ 3,000편 |
| 편당 길이 | 800 ~ 2,500 토큰 |
| 총 코퍼스 | 1M ~ 6M 토큰 |
| 청크 수(600토큰 기준) | 2,000 ~ 12,000개 |
| 하루 질의 | 10 ~ 50회 |
| 월 증가분 | 문서 20 ~ 60편 |
SQLite(FTS5 + sqlite-vec) vs PostgreSQL(FTS + pgvector)
먼저 "브루트포스로 버틸 수 있나" 부터. sqlite-vec 의 기본 vec0 테이블은 전수 스캔이다 — ANN 인덱스(IVF/DiskANN)는 트래킹 이슈에 열려 있는 계획 단계고, 저자 스스로 "대규모(>1M, 고차원)에서 느려진다" 고 적어 뒀다. 그래서 직접 쟀다 — M4 Pro, sqlite-vec v0.1.9, SQLite 3.51.0, 1024차원 float32:
| 벡터 수 | 적재 | KNN p50 | KNN p95 | DB 크기 |
|---|---|---|---|---|
| 5,000 | 0.5s | 4.6 ms | 8.3 ms | 21 MB |
| 20,000 | 2.5s | 14.5 ms | 20.5 ms | 85 MB |
| 100,000 | 8.8s | 54.7 ms | 56.1 ms | 414 MB |
1,000벡터당 약 0.55ms 로 깔끔하게 선형이고, 이 선형성 자체가 전수 스캔의 증거다. 위 표의 개인 규모 상한(12,000청크)이면 7ms 안팎이고, 100ms 를 넘기려면 청크가 지금의 15배는 되어야 한다. 즉 개인 규모에서 ANN 부재는 병목이 아니다. 인덱스 없는 전수 스캔이라 재현율이 정의상 100% 라는 건 오히려 장점이다(HNSW 는 ef_search 를 잘못 잡으면 조용히 놓친다).
| SQLite + FTS5 + sqlite-vec | PostgreSQL + FTS + pgvector | |
|---|---|---|
| 설치 | pip install sqlite-vec 한 줄, 서버 0개 |
서버 프로세스 + 확장 설치 |
| ANN | 없음(전수 스캔) | HNSW / IVFFlat |
| 인덱스 차원 상한 | 사실상 없음(전수 스캔) | vector 2,000 / halfvec 4,000 |
| 저장 | 파일 1개 → cp 가 곧 백업 |
pg_dump + 서버 운영 |
| 동시 쓰기 | 단일 라이터(WAL 로도 한계) | 문제 없음 |
| 벡터 저장량 | 4·d+8 바이트 (실측 4.1KB/벡터, d=1024) | 동일 공식, halfvec 는 2·d+8 |
| 적합 | 1인·로컬·단일 프로세스 | 이미 Postgres 를 굴리는 경우 |
권고: 위 규모라면 SQLite 로 시작하라. 서버가 0개면 "고쳐야 유지되는 것" 도 0개다. 단, 이미 FastAPI+PostgreSQL 스택을 운영 중이거나, 웹앱·MCP 서버·인덱서가 동시에 쓰거나, 노트북 밖에서 접근해야 한다면 반대다 — 그땐 pgvector 가 추가 운영비 0 이다. 이미 있는 Postgres 를 피하려고 SQLite 를 고르는 건 절약이 아니라 백업·모니터링 체계를 하나 더 만드는 일이다.
로컬 임베딩: 되는 구성, 그리고 이유는 비용이 아니다
Ollama 로 실측했다(M4 Pro, 한국어 약 400토큰 청크, /api/embed):
| 모델 | 차원 | 디스크 | 컨텍스트 | 단건 p50 | 배치32 환산 | 처리량 |
|---|---|---|---|---|---|---|
bge-m3 |
1024 | 1.2 GB | 8K | 86.7 ms | 33.2 ms/청크 | 30.2 청크/s |
nomic-embed-text |
768 | 274 MB | 2K | 16.2 ms | 9.8 ms/청크 | 102 청크/s |
embeddinggemma (300M) |
768(→512/256/128 절단) | 622 MB | 2K | 미측정 | 미측정 | 측정해서 정하라 |
한국어 위키라면 bge-m3(또는 그 한국어 파인튜닝인 KURE-v1) 이 기본값이다. 한국어 검색 벤치마크 NDCG@10 은 KURE-v1 0.6947 > bge-m3 0.6872 > multilingual-e5-large 0.6637 > OpenAI text-embedding-3-large 0.6167 순이다. 영어 중심 모델의 위험은 벤치마크 수치보다 극적인데, 6문서·4질의짜리 초소형 프로브(패러프레이즈 질의, 어휘 겹침 제거)에서 bge-m3 는 top-1 4/4, nomic-embed-text 는 0/4 였고 후자는 네 질의 모두 같은 무관 문서를 뱉었다. n=4 라 벤치마크가 아니라 스모크 테스트지만, "영어 모델도 다국어니까 되겠지" 를 배포 전에 깨기엔 충분하다.
그런데 비용 논리는 정직하게 말하면 약하다. 12,000청크×600토큰 = 7.2M 토큰을 text-embedding-3-small($0.02/1M)로 전량 임베딩하면 $0.144, 전체 재색인을 10번 돌려도 $1.44 다. 로컬을 고르는 진짜 이유는 셋이다: ① 개인 노트를 외부로 안 보냄 ② 전량 재색인이 1.7~6.6분(bge-m3 30.2청크/s 실측, 3k~12k청크)이라 청킹 규약을 언제든 갈아엎을 수 있음 ③ 벤더 모델 폐기 시 인덱스가 통째로 재현 불가능해지지 않음. 단, 노트북이 항상 켜져 있지 않거나 임베딩 지연이 인입 파이프라인을 막는다면 반대다 — 그땐 API 가 맞고, 실제 비용은 커피 한 잔 값이다.
최소 청킹 규약 — 규칙 4개
- 마크다운 헤딩(
##) 경계로 먼저 자르고, 400~800토큰을 넘으면 문단 단위로 재분할한다. - 겹침은 0 또는 1문단. 개인 규모에서 겹침 튜닝의 수익은 측정된 적이 없다시피 하니 0 으로 시작하라.
- 각 청크 앞에 문서 제목 + 헤딩 경로 한 줄을 붙여 임베딩한다(원문은 따로 보관). 공짜에 가까운 유일한 개선이다.
- 청크 ID 는 파일경로 + 헤딩 앵커로 결정론적으로 만든다. 그래야 재색인이 전량 삭제-재삽입이 아니라 변경분만으로 끝난다.
단, 문서가 표·코드 블록 위주라면 반대다 — 헤딩 분할이 표를 반토막 낸다. 그땐 블록 단위 파서를 먼저 만들어라. (문서 앞에 LLM 요약 문맥을 붙이는 contextual retrieval 계열은 → 다른 섹션에서 다룸)
한국어에서 어휘 검색이 실제로 필요한 순간
여기서 흔한 오해 하나를 깬다. 한국어 어휘 검색은 SQLite 든 PostgreSQL 이든 기본 설정으로는 똑같이 못 한다. 같은 문서에 같은 질의를 넣어 실측한 결과다:
문서: pgvector 를 도입한 뒤 임베딩을 1024차원으로 바꿨다. HNSW 인덱스 빌드가 느려졌다.
| 질의 | FTS5 unicode61(기본) |
FTS5 trigram |
PG to_tsvector('simple') |
|---|---|---|---|
임베딩 |
❌ | ✅ | ❌ |
임베딩을 |
✅ | ✅ | ✅ |
차원 |
❌ | ❌ (2글자) | ❌ |
1024차원 |
❌ | ✅ | ❌ |
pgvector |
✅ | ✅ | ✅ |
HNSW |
✅ | ✅ | ✅ |
원인은 같다. 둘 다 공백·구두점 기준 어절 토크나이저라 조사가 붙은 임베딩을 은 임베딩 으로 안 잡힌다. PostgreSQL 16 이 기본 제공하는 텍스트 검색 설정은 29개인데(arabic…yiddish) 한국어·일본어·중국어는 없다. 즉 DB 선택이 한국어 검색 품질을 결정하지 않는다 — 둘 다 우회가 필요하다.
- 가장 싼 우회: FTS5
trigram토크나이저를 추가 인덱스로 하나 더 둔다(기존 것을 대체하지 말 것). 실측 색인 크기는 2,000문서 한국어 코퍼스에서 8.9MB → 11.5MB, +29%. 대신 2글자 질의는 구조적으로 못 잡는다(차원실패). - PostgreSQL 이면
pg_bigm(2-gram GIN) 또는 형태소 분석기 연동이 대응물이다 — 설치 부담은 확인 필요. - 형태소 분석기(mecab-ko 등)는 개인 규모에선 대체로 과잉이다. 설치·사전·재색인 파이프라인이 붙는데 아래 표의 실패 유형은 trigram 으로 이미 대부분 잡힌다.
그래서 어휘 채널은 한국어 자연어 질의를 위해 있는 게 아니다. 그건 벡터가 더 잘한다. 어휘 채널이 실제로 값을 하는 순간은 좁고 분명하다:
| 실제로 필요한 순간 | 예 |
|---|---|
| 식별자·에러코드 | ValidationError, ERR_1042, 커밋 SHA |
| 버전·수치 | v2.6.53, 0.15, 8012 포트 |
| 영문 약어·고유명사 | HNSW, pgvector, RRF |
| 명령어·경로 | pytest -k, ~/.config/... |
| "그 단어 그대로" 를 아는데 문맥은 기억 안 날 때 | — |
단, 위키가 순수 한국어 산문(회의록·일기·독서노트)뿐이라면 반대다 — 어휘 채널을 아예 빼고 벡터만으로 시작한 뒤, 놓친 질의를 기록해서 필요해지면 붙여라.
공수와 유지비
| 작업 | 시간(숙련 기준) |
|---|---|
| 인입 파이프라인(파일 워커 → 청킹 → 해시 기반 증분) | 3 ~ 5h |
로컬 임베딩 배선(/api/embed 배치 + 재시도) |
1 ~ 2h |
| 어휘 인덱스 2종(기본 + trigram) | 1 ~ 2h |
| RRF 병합 + top-k 반환 | 1h |
| MCP 툴로 노출 | 1 ~ 2h |
| 합계 | 8 ~ 14h (주말 하나) |
유지비는 월 0~30분이다. 실제로 손이 가는 건 임베딩 모델을 바꿀 때뿐이고, 그건 전량 재색인 7분 안쪽으로 끝난다. 이 숫자들은 저자 환경 기준 추정이니 네 인입 파이프라인의 복잡도로 다시 재라.
왜 대부분 여기서 멈춰야 하는가
숫자로 보면 명확하다.
- 분모가 작다. 하루 질의 10~50회에서 Tier 1 이 top-5 재현율 80%대를 낸다면 실패는 하루 2~10건이다. Tier 2 가 그중 절반을 살려도 하루 1~5건 개선인데, 그 대가로 관계 추출 파이프라인 하나를 영구히 유지해야 한다.
- 인덱싱 비용 구조가 뒤집힌다. Tier 1 의 인덱싱은 청크당 임베딩 1회(33ms)다. Tier 2/3 는 청크당 LLM 호출이라 재색인이 분에서 시간으로 간다. 여기서 진짜 손실은 시간이 아니라 동결이다 — 재색인이 비싸지면 청킹 규약을 못 바꾸고, 규약을 못 바꾸면 실험을 안 하고, 결국 위키가 고인다.
- 실패의 원인이 검색이 아니다. 개인 위키에서 "못 찾았다" 의 다수는 애초에 그 문서를 안 썼기 때문이다. 이건 어떤 검색 층으로도 못 고친다. Tier 1 을 8시간에 끝내고 남은 시간을 문서 쓰는 데 쓰는 쪽이 기대값이 높다.
단, 아래 중 2개 이상이 측정으로 확인되면 멈추면 안 된다:
- 질의의 30% 이상이 다홉·집계형이다("A와 B가 처음 충돌한 게 언제였지", "이 결정이 뒤집힌 적 있나")
- 청크가 50,000개를 넘고 골든셋 top-5 재현율이 70% 미만이다(→ 골든셋 구성법은 선행글B)
- 같은 개념이 5개 이상 문서에 서로 다른 이름으로 흩어져 있다(별칭 문제 — RRF 로는 안 풀린다)
- 시간축 질의가 상시다("그때 결론이 뭐였지" → Graphiti 계열, → 다른 섹션에서 다룸)
- 사용자가 1인이 아니다(합의된 어휘가 없어 어휘 채널의 명중률이 구조적으로 떨어진다)
하나만 걸렸다면 아직 아니다. Tier 1 의 파라미터(RRF 가중치, top-k, 청크 크기)를 먼저 흔들어 보는 게 훨씬 싸다.
참고 출처
↗ sqlite-vec — a vector search SQLite extension that runs anywhere↗ sqlite-vec issue #25: ANN indexes (IVF, HNSW, DiskANN) tracking issue↗ Hybrid full-text search and vector search with SQLite (Alex Garcia)↗ SQLite FTS5 Extension — tokenizers (unicode61, trigram) and bm25()↗ pgvector — open-source vector similarity search for Postgres↗ PostgreSQL Documentation: Full Text Search — Introduction↗ BAAI/bge-m3 — multilingual, multi-functionality, multi-granularity embeddings↗ nlpai-lab/KURE-v1 — Korean retrieval embedding model (NDCG@10 benchmarks)↗ su-park/mteb_ko_leaderboard — 한글 텍스트 임베딩 모델 리더보드↗ google/embeddinggemma-300m — 768-dim with Matryoshka 512/256/128↗ OpenAI API Pricing — text-embedding-3-small / -large↗ Ollama library: nomic-embed-textTier 1 의 결정적 한 수 — contextual retrieval 과 계층 요약
핵심 요점
- Anthropic 실측: contextual embeddings 단독으로 top-20 검색 실패율 5.7%→3.7%(−35%), contextual BM25 결합 시 2.9%(−49%), 리랭킹까지 1.9%(−67%). BM25 한 줄 추가가 −35%를 −49%로 만든다.
- 원 발표의 '문서 100만 토큰당 $1.02' 는 2024년 Claude 3 Haiku 단가 기준. 현행 claude-haiku-4-5($1/$5 per MTok)로 재계산하면 1.5M 토큰 볼트가 청크당 1콜 $9.5 → 문서당 일괄 생성 $3.1 → 배치 API 50% 할인 $1.55. 개인 규모에서 비용은 논점이 아니다.
- 🔴 claude-haiku-4-5 의 최소 캐시 가능 프리픽스는 4,096토큰 — 1,500토큰짜리 개인 노트는 프롬프트 캐시가 아예 안 붙는데 에러도 안 난다(cache_read 0 이 조용히 찍힘). 짧은 문서 볼트는 캐시 대신 '문서당 1콜로 맥락 N개 일괄 생성' 으로 가야 한다.
- RAPTOR(UMAP+GMM 소프트 클러스터링, collapsed tree 검색)는 QuALITY 에서 GPT-4 결합 시 절대 +20%p(82.6%), BM25 대비 +5.1%. 그래프 없이 '전역 요약' 을 흉내내는 가장 가까운 근사지만, 진짜 비용은 구축비($2~5)가 아니라 문서 한 편 수정이 상위 요약을 연쇄 무효화하는 재빌드다 — 해금 조건에 월 수정률 10% 이하를 넣어야 한다.
- 흉내낼 수 없는 잔여 영역은 한 문장으로 요약된다: 클러스터는 유사도지 관계가 아니다. 임베딩 공간에서 멀지만 같은 엔티티로 이어진 두 문서는 어느 클러스터에도 함께 담기지 않아, 어떤 요약 노드도 그 연결을 담지 못한다. 명명된 관계·시점 질의·전수 열거도 전부 Tier 1 밖이다.
- 반대 근거도 존재한다 — ECIR 2025 워크샵 논문은 어느 고급 청킹 기법도 모든 축에서 베이스라인을 결정적으로 이기지 않으며, contextual retrieval 은 일관성을 얻는 대신 연산비를 치르고 late chunking 은 효율을 얻는 대신 관련성·완전성을 잃는다고 결론짓는다.
Tier 1 의 값어치는 "그래프 없이 어디까지 가능한가" 를 정확히 아는 데 있다. 결론부터: 개인 규모에서 이 셋의 구축 비용은 전부 커피 두 잔 값이다. 비용은 장벽이 아니다. 장벽은 재색인 유발률과, 유사도 클러스터가 관계를 표현하지 못한다는 원리적 한계다.
먼저 게이트 하나. Anthropic 은 20만 토큰(약 500페이지) 미만 지식베이스라면 RAG 자체를 쓰지 말고 통째로 프롬프트에 넣으라고 명시한다. 문서 500편 × 1,200토큰 = 60만 토큰쯤 돼야 이 절의 논의가 시작된다. 이 글의 "개인 규모" 는 문서 300~2,000편 / 총 0.5M~5M 토큰 / 하루 질의 5~50건 / 월 증가 20~80편 으로 고정한다.
① contextual retrieval — 실측 개선치는 크고, 원가는 이제 무시할 수준이다
청크마다 "이 청크가 문서 전체 어디에 놓이는지" 를 LLM 이 50~100토큰으로 써서 앞에 붙이고, 그 합본을 임베딩·BM25 색인한다. Anthropic 의 코드베이스 9종 / 737청크 / 248질의 평가:
| 구성 | Pass@5 | Pass@10 | Pass@20 | top-20 실패율 |
|---|---|---|---|---|
| 베이스라인 RAG | 80.92% | 87.15% | 90.06% | 5.7% |
| + contextual embeddings | 88.12% | 92.34% | 94.29% | 3.7% (−35%) |
| + contextual BM25 하이브리드 | 88.86% | 92.31% | 95.23% | 2.9% (−49%) |
| + 리랭킹(150→20) | 92.15% | 95.26% | 97.45% | 1.9% (−67%) |
주목할 것은 BM25 한 줄이 −35% 를 −49% 로 만든다는 점이다. Postgres 를 이미 쓰고 있다면 어휘 검색은 사실상 공짜다.
원가. 원 발표의 "문서 100만 토큰당 $1.02" 는 2024년 Claude 3 Haiku 단가 기준이다. 현행 claude-haiku-4-5 는 $1/$5 per MTok 이므로 재계산이 필요하다 — 아래는 공개 단가로 내가 계산한 값이고 실측이 아니다(문서 1,000편 × 1,500토큰 = 1.5M, 청크 400토큰, 맥락 출력 75토큰 가정).
| 구현 방식 | 입력 | 출력 | 총액 | M 토큰당 |
|---|---|---|---|---|
| 청크마다 1콜 (원본 레시피) | 8.0M | 0.30M | $9.50 | $6.3 |
| 문서마다 1콜, 맥락 N개 일괄 생성 | 1.6M | 0.30M | $3.10 | $2.1 |
| 위 + Message Batches (50% 할인) | — | — | $1.55 | $1.0 |
월 증분 50편 재색인은 $0.08. 비용은 논점이 아니다.
🔴 조용한 함정 하나. 원 레시피는 문서를 프롬프트 캐시에 올려 절감한다(쿡북 실측: 입력 토큰의 61.83% 캐시 히트, $9.20→$2.85). 그런데 claude-haiku-4-5 의 최소 캐시 가능 프리픽스는 4,096토큰이다. 개인 위키의 1,500토큰짜리 노트는 캐시가 아예 안 붙는데 에러도 안 난다 — cache_creation_input_tokens: 0 이 조용히 찍힐 뿐이다. 짧은 문서 위주라면 캐시를 기대하지 말고 위 표의 2행(문서당 1콜)으로 가라. 이게 개인 규모용 변형이다.
import json, anthropic
client = anthropic.Anthropic()
def contextualize(doc: str, chunks: list[str]) -> list[str]:
numbered = "\n\n".join(f"[{i}]\n{c}" for i, c in enumerate(chunks))
r = client.messages.create(
model="claude-haiku-4-5", # 벌크 전처리 전용 선택 ($1/$5 per MTok)
max_tokens=2000,
system=[{"type": "text",
"text": f"<document>\n{doc}\n</document>",
"cache_control": {"type": "ephemeral"}}], # 4,096토큰 미만이면 무효
output_config={"format": {"type": "json_schema", "schema": {
"type": "object",
"properties": {"contexts": {"type": "array", "items": {"type": "string"}}},
"required": ["contexts"], "additionalProperties": False}}},
messages=[{"role": "user", "content":
f"각 청크를 문서 전체 안에 위치시키는 1~2문장 맥락을 순서대로 써라.\n{numbered}"}],
)
# 캐시가 실제로 붙었는지 매 실행 확인 — 0이 계속 나오면 문서가 최소 길이 미만이다
print("cache_read:", r.usage.cache_read_input_tokens)
return json.loads(next(b.text for b in r.content if b.type == "text"))["contexts"]
문서당 일괄 생성이 청크당 개별 생성과 동등한 품질인지는 공개 검증이 없다 — 측정해서 정하라.
해금 조건: 문서 평균이 3청크(≈1,200토큰) 이상이고, 최근 질의 실패 30건 중 "맞는 문서는 찾았는데 틀린 청크를 물어옴" 이 30% 이상일 때. 단, 노트 대부분이 1~2청크로 끝나는 경우엔 반대다 — 맥락 프리픽스가 제목 재진술에 그쳐 임베딩만 흐려진다. 또한 문서를 고칠 때마다 그 문서 전체 청크의 맥락과 임베딩을 다시 만들어야 하므로, 월 수정률이 30% 를 넘는 활성 볼트라면 파이프라인 유지비가 이득을 잠식한다.
반대 근거도 있다. ECIR 2025 워크샵 논문(Reconstructing Context)은 contextual retrieval 이 의미 일관성은 더 잘 보존하지만 연산 비용이 크고, 어느 기법도 모든 축에서 베이스라인을 결정적으로 이기지는 않는다고 결론짓는다. 더 싼 대안인 late chunking(문서를 먼저 임베딩하고 나중에 자르기, LLM 호출 0회)도 후보다.
② 문서 단위 요약 인덱스 — 싸지만 전제가 까다롭다
문서마다 요약 1건을 만들어 색인하고, 질의 시 ①요약으로 문서를 고르고 ②그 문서 안에서 청크를 고른다. LlamaIndex DocumentSummaryIndex 가 LLM 라우팅·임베딩 라우팅 두 방식을 제공한다. 구축비는 문서당 1콜이므로 위 표 2행보다 싸다(1,000편 ≈ $2 미만).
⚠️ 원 구현은 선택된 문서의 모든 노드를 반환한다. 개인 위키에선 그대로 쓰면 컨텍스트가 터진다 — 2단계를 "문서 내 재검색" 으로 바꿔야 한다.
해금 조건: 문서 300편 이상 + 질의의 20% 이상이 "특정 문서 안에서" 형(사용자가 이미 어떤 글인지 알고 묻는 경우). 단, 문서 한 편이 여러 주제를 섞는 형태(일일 로그, 회의록 묶음)라면 반대다 — 요약이 문서를 대표하지 못해 1단계 라우팅이 정답 문서를 통째로 탈락시키고, 실패가 청크 단위가 아니라 문서 단위로 커진다. 이때는 요약 인덱스 대신 메타데이터 필터를 써라.
③ RAPTOR — 전역 요약을 그래프 없이 흉내내는 유일한 수단
청크를 임베딩→UMAP 차원축소→GMM 소프트 클러스터링(BIC 로 클러스터 수 선택)→클러스터별 요약, 이걸 재귀해 요약 트리를 만든다. 검색 시엔 트리 순회가 아니라 collapsed tree(모든 레벨 노드를 한 풀에 넣고 평평하게 검색)가 일관되게 더 좋았다.
| 지표 | 값 |
|---|---|
| QuALITY (GPT-4 결합) | 82.6%, 절대 +20%p |
| QuALITY vs DPR / BM25 | +2.0% / +5.1% |
| QASPER (GPT-4) | 55.7 F1 |
| NarrativeQA (UnifiedQA) | 19.1 METEOR |
| 구축 비용 | 토큰·시간 선형 스케일 (논문 주장) |
이기는 질의는 명확하다: 문서 전체를 가로지르는 추론이 필요한 중간 길이 지문 — "이 주제에 대해 내가 반복해 말해온 것", "이 프로젝트의 결론이 뭐였나". 요약 노드 자체가 답이기 때문에, 평평한 청크 top-k 로는 원리적으로 못 만드는 답을 만든다. GraphRAG 의 '전역 요약' 모드에 가장 가까운 비-그래프 근사가 이것이다.
원가(내 계산, 1.5M 토큰 기준): 레벨을 올라가며 읽는 총량 ≈ 2M, 쓰는 총량 ≈ 0.5M → Haiku 4.5 로 $4~5, 배치 API 로 $2~3. 여기에 요약 노드 임베딩이 추가된다. 역시 비용은 논점이 아니다.
진짜 비용은 재구축이다. 문서 한 편을 고치면 그 청크가 속한 클러스터의 요약과, 그 위 레벨 요약이 연쇄로 무효화된다. 증분 갱신이 어려워 실무에선 전체 재빌드로 도망가게 된다.
해금 조건: 총 1M 토큰 이상 + 전역/주제형 질의 비율 15% 이상 + 월 문서 수정률 10% 이하. 단, 볼트가 활발히 편집되는 작업 노트라면 반대다 — 매주 전체 재빌드하며 $2씩 태우고 인덱스는 늘 며칠 낡아 있다. 그런 볼트엔 ①만 적용하는 게 낫다.
어디까지 흉내내고, 무엇은 못 하는가
| 질의 유형 | ①맥락 | ②요약 | ③RAPTOR | 잔여 |
|---|---|---|---|---|
| 지역 사실 조회 ("X 설정값?") | ✅ | — | — | 그래프 불필요 |
| 문서 라우팅 ("그 글에서 뭐라 했더라") | △ | ✅ | — | — |
| 전역 주제 요약 ("반복해 말한 것") | ❌ | △ | ✅ | 근사로 충분 |
| 유사한 문서 2편 종합 | ❌ | ❌ | ✅ | — |
| 비유사 문서 간 엔티티 2홉 | ❌ | ❌ | ❌ | 엔티티 색인/그래프 (→ 다른 섹션) |
| 명명된 관계 ("무엇이 무엇을 대체했나") | ❌ | ❌ | ❌ | 타입 있는 엣지 |
| 시점·버전 ("작년 기준으로는?") | ❌ | ❌ | ❌ | 시간 인식 층 (→ 다른 섹션) |
| 전수 열거 ("X 언급 전부") | ❌ | ❌ | ❌ | SQL·메타데이터 필터 |
정직하게 말하면 잔여 영역의 정체는 한 문장이다. 클러스터는 유사도지 관계가 아니다. RAPTOR 요약 노드는 "이 청크들은 서로 비슷하다" 를 인코딩할 뿐 "A 가 B 를 폐기했다" 를 인코딩하지 않는다. 그래서 임베딩 공간에서 멀지만 같은 엔티티로 이어진 두 문서(같은 인물이 등장하는 회의록과 그 결정이 바꾼 설계 문서)는 어느 클러스터에도 함께 담기지 않고, 따라서 어떤 요약 노드도 그 연결을 담지 못한다.
그리고 마지막 행은 검색 문제가 아니다. "X 를 언급한 것 전부" 는 top-k 근사로 답할 수 없다 — Postgres 에 이미 있는 필터·집계로 풀어야 하는 질의를 RAG 로 밀어 넣는 것이 개인 위키 과잉설계의 가장 흔한 형태다.
체크리스트
- [ ] 총 토큰 20만 미만 → 아무것도 만들지 마라. 통째로 넣어라
- [ ] BM25(Postgres FTS) 하이브리드 먼저 — 맥락 생성 없이도 실패율이 크게 준다
- [ ] 실패 30건을 유형 분류: "문서는 맞고 청크가 틀림" 이 30% 이상일 때만 ①
- [ ] 캐시 히트를 매 실행 출력하라(
cache_read_input_tokens) — 0 이면 캐시는 처음부터 안 붙었다 - [ ] 색인 구축은 전부 오프라인 → Message Batches 50% 할인
- [ ] 월 수정률을 먼저 재라. 30% 초과면 ①까지, 10% 이하일 때만 ③
- [ ] 전수 열거·시점 질의가 상위에 올라오면 그건 Tier 1 로 못 푼다 (→ 다른 섹션)
참고 출처
↗ Introducing Contextual Retrieval — Anthropic Engineering↗ Enhancing RAG with contextual retrieval — Claude Cookbook↗ RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval (ICLR 2024)↗ A New Document Summary Index for LLM-powered QA Systems — LlamaIndex↗ Reconstructing Context: Evaluating Advanced Chunking Strategies for Retrieval-Augmented Generation (ECIR 2025 Workshop)↗ Prompt caching — Claude Docs (write/read pricing, TTL, minimum cacheable prefix)↗ Late Chunking in Long-Context Embedding Models — Jina AI↗ Message Batches API (50% discount for offline jobs) — Claude Docs관계는 대부분 손으로 만드는 게 낫다 — 위키링크와 MOC
핵심 요점
- LLM 자동 관계추출은 출발점이 낮다: GPT-4 one-shot 관계추출 F1이 Re-TACRED 22.5(파인튠 SOTA 91.4), DuIE2.0 41.91(69.42), SciERC 9.1(53.2) — 손으로 건 링크는 정의상 정밀도 100%이고 토큰 비용 0이다.
- 개인 규모를 숫자로 고정: 문서 300개·60만 토큰·하루 질의 5~15건·월 +20문서. 이때 손 링크 총비용은 초기 900개(2.5~5시간) + 월 20분에 불과해 그래프 파이프라인과 비용 비교가 성립하지 않는다.
- 링크 규약 3줄(아웃링크 2개+이유 한 줄 / 인바운드 ≥1 / 20단어당 1링크 상한)과 안 지켜질 때의 대비(경고만 하는 커밋 훅, 주간 15분 상위 10건 청소, 에이전트는 제안자로만).
- 자동 제안은 L0 표시(unlinked mentions)·L1 점수화(Adamic Adar/Co-Citations)·L2 임베딩 제안까지만 허용하고 L3 자동 삽입은 금지. 예외는 리네임 시 링크 자동 갱신과 통제된 어휘(제품명·에러코드·티켓 ID)뿐.
- 건강 지표 목표값: 고아율 <5%(영문 위키 실측 0.73%), 평균 아웃링크 ≥3, 링크 밀도 20~80단어/링크, MOC 2홉 도달률 ≥90%, MOC 항목 10~30개. 표준 라이브러리 30줄 스크립트로 전부 측정 가능.
- 자동 그래프 해금은 5개 신호 중 2개 이상이 2주 연속 참일 때만 — 특히 '작성자 2명 이상'이 결정적이며, 그 경우에도 그래프보다 별칭 사전·제목 규약을 먼저 시도하고 고아율 변화를 측정하라.
자동 추출 엣지의 정확도부터 확인하고 시작하자
관계층 자동화는 늘 "LLM이 문서에서 엔티티와 관계를 뽑아준다"로 시작한다. 그런데 공개 벤치마크는 낙관을 허락하지 않는다. 지식그래프 구축 태스크에서 GPT-4를 파인튠 SOTA와 비교한 평가 결과다.
| 데이터셋 (태스크) | 파인튠 SOTA F1 | GPT-4 zero-shot | GPT-4 one-shot |
|---|---|---|---|
| Re-TACRED (관계추출) | 91.4 | 15.5 | 22.5 |
| DuIE2.0 (관계추출) | 69.42 | 31.03 | 41.91 |
| SciERC (관계추출, 도메인) | 53.2 | 7.2 | 9.1 |
| MAVEN (이벤트추출) | 68.8 | 34.2 | 30.4 |
논문의 결론은 "LLM은 few-shot 정보 추출기라기보다 추론 보조자에 가깝다"였다. 주의할 점: 2023년 평가, 고정 스키마, zero/one-shot 조건이다. 최신 모델 + 도메인 프롬프트 + 다단계 검증을 얹으면 확실히 올라간다 — 얼마나 올라가는지는 네 코퍼스에서 측정해야 한다(확인 필요). 하지만 출발점이 F1 20~40대라면 "자동 엣지의 상당수는 틀렸다"를 기본값으로 두는 게 맞다.
반면 손으로 건 링크의 정밀도는 정의상 100%다. 내가 그렇게 의도했기 때문이다.
| 축 | 손으로 건 [[링크]] |
LLM 자동 추출 엣지 |
|---|---|---|
| 정밀도 | 의도가 곧 정답 | 위 표 기준, 검증 없이는 신뢰 불가 |
| 의도 반영 | "왜 연결했나"를 한 줄로 같이 씀 | 표면 공기(共起)만 남음 |
| 유지보수 | 파일 리네임 시 링크 자동 갱신 (Obsidian: Files and links → Automatically update internal links) | 원문이 바뀌면 재추출, 엣지가 조용히 뒤바뀜 |
| 비용 | 0 토큰 / 0원 | 증분 업데이트만으로도 수백만 토큰 규모 (GraphRAG 커뮤니티 재구축 사례 ~1,399만 토큰) |
| 설명가능성 | git diff / git blame에 남음 |
재실행하면 결과가 달라지고 근거 추적 불가 |
단, 반대 조건: 내가 읽지 않은 코퍼스(수집만 해둔 논문 PDF 200편, 자동 전사된 회의록)에는 손 링크가 애초에 불가능하다. 이런 코퍼스에만 자동 추출을 붙이고, 읽고 쓴 문서와는 다른 네임스페이스로 분리해라.
"개인 규모"를 숫자로 못 박고 손 링크 총비용을 계산한다
이 글에서 개인 규모의 기준선은 문서 300개 / 총 60만 토큰 / 하루 질의 5~15건 / 월 +20문서다. 이 규모에서 필요한 손 링크 총량은 이렇게 계산된다.
- 문서당 아웃링크 3개 × 300문서 = 900개
- 링크 1개당 소요 10초(이유 한 줄 포함 20초) = 2.5~5시간, 그것도 몇 달에 나눠서
- 월 유지비: 신규 20문서 × 3링크 × 20초 = 월 20분
참고로 지식베이스가 20만 토큰(약 500쪽) 미만이면 검색 파이프라인 없이 통째로 프롬프트에 넣으라는 게 Anthropic의 권고다. 60만 토큰이면 그 선은 넘었지만, 월 20분짜리 관계층을 두고 그래프 인덱싱 파이프라인을 짓는 건 비용 비교가 성립하지 않는다.
단, 반대 조건: 월 증가분이 100문서를 넘어가면 유지비가 월 100분이 되고, 이때부터는 자동화 논의가 정당해진다.
링크 습관을 규약으로 고정한다 (그리고 안 지켜질 것을 전제한다)
세 줄이면 충분하다.
| 규약 | 내용 | 근거 |
|---|---|---|
| R1. 아웃링크 2개 + 이유 한 줄 | 새 문서를 닫기 전 기존 문서 2개 이상으로 링크, 각 링크 옆에 왜 연결했는지 한 줄 | Zettelkasten: "설명 없이 링크만 추가하면 지식이 생기지 않는다" |
| R2. 인바운드 ≥1 보장 | 새 문서는 반드시 MOC 한 곳 이상에서 링크된다 | Wikipedia: 링크 1개면 orphan 해제, 3개 이상이면 도달성 확보 |
| R3. 과링크 상한 | 장문 기준 대략 20단어당 1링크, 같은 대상은 섹션당 첫 등장 1회만 | Wikipedia MOS/Linking |
R1과 R3은 서로 반대 방향의 가드다. 링크는 많을수록 좋은 게 아니라, 밀도가 20단어당 1개를 넘으면 백링크 패널이 노이즈로 변한다.
지켜지지 않을 때의 대비가 규약보다 중요하다. 사람은 규약을 안 지킨다는 전제로 설계해라.
- 게이트를 자동화한다: 커밋 훅에서
inbound == 0인 신규 파일을 경고(차단은 하지 마라 — 초안 작성이 막히면 문서가 안 늘고, 그게 진짜 실패 모드다). - 주간 15분 청소 슬롯: 고아 목록 상위 10건만 처리. 전수 정리를 목표로 잡으면 한 번도 안 하게 된다.
- 에이전트를 제안자로 쓴다: 초안을 쓸 때 "이 문서에서 링크할 후보 5개와 이유"를 뽑게 하고, 채택은 사람이 한다.
단, 반대 조건: 하루 30건씩 쏟아지는 로그성 문서(빌드 결과, 일일 메모)에는 R1·R2를 적용하지 마라. 그건 링크 대상이 아니라 소스다. 요약된 결론 문서 하나만 링크 대상으로 승격시켜라.
자동 링크 제안은 어디까지 허용하나
| 단계 | 예시 도구 | 허용 여부 |
|---|---|---|
| L0 표시만 | Obsidian Unlinked mentions (제목이 링크 없이 언급된 곳) | ✅ 항상 켜라. 비용 0, 오탐이 화면에만 머문다 |
| L1 점수화 제안 | Graph Analysis (Adamic Adar, Common Neighbours, Jaccard, Co-Citations) — 링크를 만들지 않고 점수 테이블만 보여줌 | ✅ 주간 청소 때만 열어라 |
| L2 의미 제안 | Smart Connections (로컬 임베딩, 제안만·자동 삽입 없음) | ⚠️ 채택률을 세라. 채택률 30% 미만이면 끄는 게 낫다 |
| L3 자동 삽입 | LLM이 본문에 [[ ]]를 직접 써 넣음 |
❌ 금지 |
| L3-예외 | 리네임/이동 시 기존 링크 자동 갱신 | ✅ 의미를 새로 만들지 않는 보존 작업이므로 자동으로 |
L3 금지의 이유는 정확도가 아니라 되돌릴 수 없음이다. 자동 삽입된 링크는 다음 사람(=3개월 뒤의 나)에게 "내가 의도한 연결"과 구분되지 않는다. 백링크 패널의 신뢰가 한 번 깨지면 그 뒤로 아무도 안 본다.
단, 반대 조건: 통제된 어휘(제품명, 에러코드, 티켓 ID처럼 문자열이 유일 식별자인 것)에 대해서는 L3 자동 삽입을 허용해도 된다. 오탐이 구조적으로 낮고, 정규식으로 검증 가능하다.
백링크만으로 실제로 커버되는 질의
| 질의 유형 | 백링크로 커버 | 대안 |
|---|---|---|
| "X를 참조하는 결정/사건 전부" | ✅ linked mentions 그대로 | — |
| "X와 Y를 함께 언급한 문서" | ✅ co-citation(2차 백링크) | — |
| "X 관련인데 X를 안 쓴 문서" | ⚠️ unlinked mentions가 표기 변형까지는 못 잡음 | 별칭(alias) 등록, 임베딩 (→ 다른 섹션에서 다룸) |
| "X가 막힌 원인의 원인" (2홉) | ⚠️ local graph depth 2로 눈으로만 | 그래프 질의 (→ 다른 섹션에서 다룸) |
| "이 주제 전체를 요약" | ❌ | MOC 본문이 곧 요약 |
| "말은 다른데 같은 얘기" | ❌ | 하이브리드 검색 (→ 다른 섹션에서 다룸) |
| "시간순으로 무슨 일이 있었나" | ❌ | frontmatter 날짜 정렬 |
커버율의 절대 수치는 코퍼스마다 다르다. 최근 질의 30건을 이 표로 직접 라벨링해서 정하라. ✅ 비중이 60%를 넘으면 관계층은 이미 충분하다.
건강 지표와 목표값
| 지표 | 정의 | 목표 | 경고선 | 근거 |
|---|---|---|---|---|
| 고아율 | inbound = 0인 문서 비율 | < 5% | 15% 2주 연속 | 영문 위키 53,085 / 7,222,633 ≈ 0.73% |
| 평균 아웃링크 | 총 링크 / 문서 | ≥ 3 | < 1.5 | Wikipedia: 3개 이상이면 도달성 확보 |
| 링크 밀도 | 단어 수 / 링크 수 | 20~80 | < 20 (과링크) | Wikipedia MOS |
| MOC 2홉 도달률 | 어떤 MOC에서 2홉 내 도달 가능한 문서 비율 | ≥ 90% | < 70% | 자체 기준 |
| 미해결 링크 | 존재하지 않는 문서를 가리키는 링크 | < 5% | — | 단, 의도적 stub 링크는 정상 |
| MOC 크기 | MOC 한 개의 항목 수 | 10~30 | > 40 → 분할 | 자체 기준 |
미해결 링크를 0으로 몰지 마라. "아직 없는 노트로 링크를 걸어두는" 것은 다음 글의 씨앗을 심는 정상적인 관행이다. 링크 대상 파일이 생겼을 때 자동으로 이어지도록 이름 규칙만 지켜라.
# vault_health.py — 마크다운 볼트 링크 건강 지표 (표준 라이브러리만, 실행 0.1초/300문서)
import re, sys, pathlib, collections
VAULT = pathlib.Path(sys.argv[1])
IS_MOC = re.compile(r"^MOC[-_ ]") # MOC 문서 식별 규칙(네 규약에 맞게 수정)
LINK = re.compile(r"\[\[([^\]|#^]+)") # [[Note|alias]], [[Note#H]] 모두 대응
docs = {p.stem: p for p in VAULT.rglob("*.md")}
out, words = {}, {}
for name, p in docs.items():
text = p.read_text(encoding="utf-8", errors="ignore")
words[name] = len(text.split())
out[name] = {t.strip() for t in LINK.findall(text)} - {name}
inb, unresolved = collections.Counter(), 0
for src, tgts in out.items():
for t in tgts:
inb[t] += 1 if t in docs else 0
unresolved += 0 if t in docs else 1
n, total = len(docs), sum(len(t) for t in out.values())
orphans = [d for d in docs if inb[d] == 0]
reach = frontier = {d for d in docs if IS_MOC.search(d)}
for _ in range(2): # MOC 기준 2홉 확장
frontier = {t for s in frontier for t in out[s] if t in docs} - reach
reach |= frontier
print(f"문서 {n} / 총링크 {total} / 문서당 {total/n:.2f}")
print(f"고아율 {len(orphans)/n:.1%} ({len(orphans)}건) 미해결 {unresolved/max(total,1):.1%}")
print(f"링크밀도 {sum(words.values())/max(total,1):.0f} 단어/링크")
print(f"MOC 2홉 도달률 {len(reach & set(docs))/n:.1%}")
print("고아 상위:", ", ".join(sorted(orphans)[:10]))
수작업이 무너지는 조건 = 자동 그래프 해금 신호
아래 중 2개 이상이 2주 연속 참이면 자동 관계층을 검토해라. 1개만이면 하지 마라.
- 문서 수 > 1,000 또는 월 증가분 > 100 (손 링크 유지비 > 월 100분)
- 고아율이 청소 후에도 15% 이상으로 재발
- 질의 로그에서 "다중 홉 / 전역 요약" 비중 > 20%
- 작성자가 2명 이상 — 링크 규약을 합의·강제하는 비용이 자동화 비용을 넘는 지점이 여기다
- 손으로 안 읽은 코퍼스(외부 논문, 전사록) 비중 > 30%
4번을 과소평가하지 마라. 혼자일 때 링크는 "내 의도의 기록"이지만, 둘이 되는 순간 어휘가 갈라지고(같은 개념을 서로 다른 제목으로) 백링크는 두 조각으로 쪼개진다. 이때 필요한 건 그래프가 아니라 별칭 사전과 문서 제목 규약일 수도 있으니, 그래프부터 짓기 전에 별칭 통합을 먼저 시도하고 고아율 변화를 측정해라.
그리고 자동 그래프를 도입하더라도 결론은 하나다 — 손 링크를 대체하는 게 아니라 읽지 않은 코퍼스 위에 파생 인덱스로 얹는다. 원본 저장소는 여전히 파일과 사람이 건 링크다.
참고 출처
↗ Backlinks — Obsidian Help (linked mentions / unlinked mentions)↗ Internal links — Obsidian Help (wikilink 문법, alias, 리네임 시 자동 갱신)↗ Graph view — Obsidian Help (Orphans / Existing files only 필터, local graph depth)↗ Wikipedia:Orphan (고아 문서 정의, 53,085건, 인바운드 3개 권고)↗ Wikipedia:Manual of Style/Linking (과링크·저링크, 20단어당 1링크)↗ Wikipedia:Special:Statistics (영문 위키 콘텐츠 문서 7,222,633건)↗ LLMs for Knowledge Graph Construction and Reasoning (arXiv:2305.13168) — GPT-4 vs 파인튠 SOTA F1↗ Introducing Contextual Retrieval — Anthropic (20만 토큰 미만이면 RAG 불필요, 실패율 35/49/67% 감소)↗ LightRAG (arXiv:2410.05779) — GraphRAG 증분 업데이트 ~1,399만 토큰 비교↗ Graph Analysis — Obsidian 플러그인 (Adamic Adar, Common Neighbours, Jaccard, Co-Citations, 제안만)↗ Smart Connections — Obsidian 플러그인 (로컬 임베딩 기반 제안, 자동 삽입 없음)↗ Evergreen notes should be densely linked — Andy Matuschak↗ Zettelkasten 소개 — 링크 컨텍스트와 구조 노트(hub note)↗ MOCs Overview — Linking Your Thinking자동 관계층 대안 지형 — GraphRAG·RAPTOR·HippoRAG·Graphiti·LightRAG
핵심 요점
- 다섯 후보는 인덱싱 시점 LLM 산출물이 다르다 — RAPTOR=계층 요약 트리, GraphRAG=커뮤니티 리포트(최고 비용), HippoRAG 2=관계↔청크 매핑+PPR, Graphiti=시간 유효구간 붙은 사실 트리플. 이 차이가 비용 차수와 삭제 가능성을 결정한다.
- 개인 위키에서 성능보다 치명적인 칸은 삭제다. GraphRAG는 삭제 명령이 없고 커뮤니티 리포트 산문에 죽은 사실이 남으며, Graphiti는 설계상 삭제가 아니라 무효화다. LightRAG·HippoRAG 2만 삭제 경로를 명시한다.
- 그래프가 항상 이기지 않는다 — GraphRAG-Bench에서 DALK 69.30%, G-Retriever 69.84%로 무보조 GPT-4o-mini 베이스라인 70.68%보다 낮았고, 같은 벤치마크의 그래프 구축은 7,900만~8,400만 토큰, 구축 시간은 93초~20,396초였다.
- 인용한 벤치마크는 균질한 타인 코퍼스·고정 정답·정적 스냅샷을 전제하므로 개인 위키(내 어휘, 나만 아는 정답, 주간 단위 변경)에 그대로 적용되지 않는다.
- 선택 매핑: 시간축·변화 추적=Graphiti(단 사실 번복 문서 10% 미만이면 반대), 계층 요약=RAPTOR(단 문서 500편 미만이면 반대), 다중 홉=HippoRAG 2(단 실패 질의의 다중홉 비율 15% 미만이면 반대), 전역 요약=GraphRAG이지만 질의당 토큰 210배라 개인 규모에선 거의 항상 반대.
- 개인 규모(총 100만 토큰 안팎) 손익분기: 전면 재인덱싱 1회 $10(저가 모델)~$120(프런티어), 월 1회 재빌드 시 연 $100~$1,200. 규모(문서 800편+)·필요(다중홉 15%+)·경제성(재인덱싱 연비용 ≤ 질의 비용의 30%) 세 조건을 모두 통과해야 착수한다.
이 글에서 개인 규모는 항상 다음 숫자를 뜻한다: 문서 500~3,000편, 총 30만~200만 토큰, 하루 질의 10~30회, 월 신규 20~50편, 월 수정 50~200회. 아래 모든 판단은 이 범위에 대한 것이고, 벤치마크 논문들이 다루는 규모는 여기서 최소 1~2자릿수 위다.
다섯 후보가 실제로 만드는 것
관계층 후보들은 "그래프를 만든다"는 말로 뭉뚱그려지지만, 인덱싱 시점에 LLM에게 무엇을 쓰게 하느냐가 전부 다르다. 그 차이가 비용 차수와 삭제 가능성을 결정한다.
| 인덱싱 산출물 | 인덱싱 비용 차수 | 이기는 질의 | |
|---|---|---|---|
| RAPTOR | 청크 재귀 클러스터링 → 계층 요약 트리 (엔티티 없음) | 중. 요약 LLM 호출이 트리 노드 수만큼 | "이 뭉치 전체가 무슨 얘기냐" 류 요약·주제 질의 |
| MS GraphRAG | 엔티티·관계·클레임 + 커뮤니티 탐지 + 다층 커뮤니티 리포트 | 최고. 커뮤니티 요약이 비용 대부분 | 코퍼스 전역 요약(abstract QA) |
| HippoRAG 2 | 엔티티-관계 KG + 관계↔청크 매핑, 질의 시 Personalized PageRank | 상. 매핑 구축 탓에 인덱싱 최장 계열 | 다중 홉·연상(associative) 질의 |
| Graphiti (Zep) | 시간 유효구간이 붙은 사실 트리플 + 에피소드 출처, bi-temporal | 중~상. 에피소드마다 추출+중복해소 LLM | "그때는 뭐라고 했었지", 사실이 뒤집힌 이력 |
| LightRAG | 엔티티·관계 + 벡터 (커뮤니티 리포트 없음) | 중. GraphRAG보다 LLM 호출 적음 | (→ 선행글 B 소관, 여기선 비교축으로만) |
운영 축: 개인 위키에서 진짜로 갈리는 지점
| 증분 | 삭제 | 운영 난이도 | 라이선스·성숙도 | |
|---|---|---|---|---|
| RAPTOR | 사실상 재빌드(README에 증분 경로 명시 없음 — 확인 필요) | 트리 재구축 | 낮음(파일 저장) | MIT, 연구 코드 |
| MS GraphRAG | update 명령 있음(standard-update/fast-update) |
삭제 명령 없음 | 높음(파이프라인·프롬프트 튠) | MIT, "공식 지원 제품 아님" 명시 |
| HippoRAG 2 | 지원(리포지토리에 증분/삭제 언급) | 지원 | 높음(임베딩+OpenIE+PPR 3스택) | MIT, 연구 코드 |
| Graphiti | 실시간 에피소드 주입(배치 재계산 불필요) | 무효화이지 삭제가 아님 | 중~높음(그래프 DB 필수) | Apache-2.0, 제품 뒷받침 |
| LightRAG | 지원 | 지원(LLM 캐시로 영향 엔티티 재구성) | 중 | MIT |
여기서 개인 위키에 가장 치명적인 칸은 성능이 아니라 삭제다. 오답 메모를 지우고 결론을 뒤집는 게 월 50~200회인데, GraphRAG의 커뮤니티 리포트는 LLM이 쓴 산문이라 원문을 지워도 요약문 안에 죽은 사실이 남는다. Graphiti는 설계상 지우지 않고 무효화한다 — 이력 추적엔 장점이지만, 개인 위키에서 "이건 정말 없애고 싶다"는 요구와는 정면으로 어긋난다.
벤치마크 수치, 그리고 그것이 당신에게 적용되지 않는 이유
- RAPTOR: QuALITY에서 GPT-4와 결합해 절대 정확도 +20%p (arXiv 2401.18059).
- HippoRAG: 다중 홉 QA에서 최대 20% 향상, 반복 검색(IRCoT) 대비 10~30배 저렴·6~13배 빠름. HippoRAG 2는 SOTA 임베딩 모델 대비 연상 기억 +7% (2405.14831, 2502.14802).
- Zep/Graphiti: DMR 94.8% vs MemGPT 93.4%, LongMemEval 정확도 최대 +18.5%, 지연 -90% (2501.13956).
- 통합 프레임워크 비교(12종): 질의당 비용이 RAPTOR 3.18초/3,210토큰, LGraphRAG 2.98초/6,155토큰, HippoRAG 3.46초/3,261토큰. 반면 abstract QA 1위인 GGraphRAG는 vanilla RAG 대비 질의당 시간 57배·토큰 210배 (2503.04338).
경고 — 이 수치들의 조건은 개인 위키와 다르다. 벤치마크는 (a) 위키피디아·소설처럼 타인이 쓴 균질한 코퍼스, (b) 정답이 고정된 질의셋, (c) 삭제·수정이 없는 정적 스냅샷을 전제한다. 개인 위키는 셋 다 틀리다. 문서는 내 어휘로 쓰여 엔티티 추출기가 오히려 헛돌고, 질의는 "그때 그 결정 왜 그렇게 했더라"처럼 정답이 나 자신에게만 있으며, 코퍼스는 매주 변한다.
더 결정적으로, GraphRAG-Bench는 그래프가 항상 이기지 않는다고 보고한다. DALK 69.30%, G-Retriever 69.84% 로 무보조 GPT-4o-mini 베이스라인 70.68% 보다 오히려 낮았고, 원인은 "구조 정보 과의존에 따른 의미 정보 손실"로 지목됐다. 같은 벤치마크에서 LightRAG·GraphRAG의 그래프 구축은 7,900만~8,400만 토큰을 썼고 구축 시간은 93초(GFM-RAG)에서 20,396초(RAPTOR) 까지 벌어졌다 (2506.02404). 즉 최악의 경우 돈과 시간을 쓰고 정확도를 잃는다.
선택 매핑: 필요 → 후보
- 시간축·변화 추적이 중요하다 ("이 결정이 언제 뒤집혔나") → Graphiti. bi-temporal 유효구간과 에피소드 출처가 이걸 위해 설계된 유일한 후보다. 단, 사실이 뒤집히는 문서가 전체의 10% 미만이면 반대다 — 그래프 DB(Neo4j 5.26+/FalkorDB/Neptune) 운영비만 늘고, 기본
SEMAPHORE_LIMIT=10에 걸려 주입도 느리다. - 계층 요약이 목적이다 ("볼트 전체가 무슨 주제냐") → RAPTOR. 엔티티 추출을 건너뛰어 가장 싸고 검색이 빠르다. 단, 문서 500편 미만이면 반대다 — 전체 제목+첫 문단을 그대로 컨텍스트에 넣는 편이 트리보다 싸고 정확하다.
- 다중 홉이 목적이다 → HippoRAG 2. 단, 실패 질의 로그에서 다중홉 비율을 먼저 세라. 15% 미만이면 반대다. 임베딩+OpenIE+PPR 3스택을 혼자 운영하는 비용이 이득을 넘는다.
- 전역 요약(abstract QA)이 목적이다 → GraphRAG가 1위지만 질의당 210배 토큰이다. 개인 규모에서는 거의 항상 반대다.
개인 규모 손익분기 숫자
공개된 실사용 보고는 GraphRAG 인덱싱에서 약 120만 토큰 코퍼스 → $8.20(저가 모델), 1,000페이지 PDF 1건 → $120(GPT-4-Turbo), 데모 기본값 → $5 수준이다 (microsoft/graphrag #440). 리포지토리 README 자체가 "인덱싱은 비싼 작업이니 작게 시작하라"고 경고한다.
이걸 개인 규모(총 100만 토큰 안팎)에 대입하면 전면 재인덱싱 1회에 $10(저가 모델)~$100+(프런티어) 이고, 월 1회 재빌드 시 연 $100~$1,200이다. 반면 임베딩 전용 인덱스는 같은 코퍼스에서 2~3자릿수 싸다(정확한 단가는 쓰는 모델 가격표로 직접 계산하라). 연 100달러를 그래프에 태울 만한지는 이 세 조건을 측정해서 정하라.
# 관계층 해금 트리거 — 세 조건 전부 참이어야 올라간다
docs = 1_200 # 볼트 문서 수
corpus_tok = 780_000 # 총 토큰 (tiktoken 등으로 실측)
multihop_r = 0.11 # 실패 질의 로그에서 "2개 이상 문서 엮기" 비율
reindex_usd = 9.0 # 전면 재빌드 1회 실측 비용 (샘플 10%로 재보고 ×10)
query_usd_m = 22.0 # 월 질의 LLM 비용 실측
gate = {
"규모": docs >= 800 and corpus_tok >= 500_000,
"필요": multihop_r >= 0.15, # 15% 미만이면 검색 문제가 아니다
"경제성": reindex_usd * 12 <= query_usd_m * 12 * 0.30,
}
print(gate, "→", "관계층 착수" if all(gate.values()) else "Tier 유지")
# {'규모': True, '필요': False, '경제성': True} → Tier 유지
반대 조건 총괄: 위 표에서 무엇을 고르든, 실패 질의 로그가 없으면 전부 반대다. 벤치마크가 측정한 것은 남의 코퍼스에서의 다중홉·요약 능력이고, 당신이 겪는 실패는 대개 문서가 존재하지 않는 것이기 때문이다. 관계층은 없는 문서를 만들어내지 않는다.
참고 출처
↗ In-depth Analysis of Graph-based RAG in a Unified Framework (arXiv:2503.04338)↗ GraphRAG-Bench: Challenging Domain-Specific Reasoning for Evaluating Graph Retrieval-Augmented Generation (arXiv:2506.02404)↗ RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval (arXiv:2401.18059)↗ HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models (arXiv:2405.14831)↗ From RAG to Memory: Non-Parametric Continual Learning for Large Language Models (HippoRAG 2, arXiv:2502.14802)↗ Zep: A Temporal Knowledge Graph Architecture for Agent Memory (arXiv:2501.13956)↗ getzep/graphiti — Real-Time Knowledge Graphs for AI Agents (GitHub)↗ microsoft/graphrag — A modular graph-based RAG system (GitHub)↗ microsoft/graphrag Discussion #440 — How much did each run cost you?↗ GraphRAG CLI Reference (index / update commands)↗ HKUDS/LightRAG — Simple and Fast Retrieval-Augmented Generation (GitHub)↗ OSU-NLP-Group/HippoRAG (GitHub)그래프가 실제로 이기는 질의 — 그리고 그건 전체의 몇 %인가
핵심 요점
- 그래프층 정당화는 '이기는 질의가 있다'가 아니라 '그 질의가 전체의 몇 %인가'로 해야 하며, 질의 로그가 없어도 소급+전향+기존 로그로 2주 100건을 손 라벨링하면 ±9%p 안에서 분모를 잴 수 있다 (30건으로는 ±16%p라 판정 불가).
- 그래프가 이기는 질의 7종을 하나씩 판정하면 완전 대체 가능 2종(백링크·전수집계는 결정적 인덱스가 더 정확), 계층요약으로 대체 가능 1종, 부분 대체 3종이고, 그래프만 이기는 건 3홉 이상 브리지와 무효화 추적 두 부류뿐이다. 단순 사실 조회는 그래프를 켜면 오히려 나빠진다(NQ 61.9→46.9).
- 공개 벤치마크 수치로 손익분기를 풀면 라우팅형 그래프는 관계형·전역 질의 비중 s≈19%가 본전, s≥30%부터 유의미한 이득이다. 반대로 라우팅 없이 보조 채널로 병합하는 설계는 오라우팅 손실 항이 사라져 임계가 s≈15%로 내려간다.
- 도입 체크리스트는 6개 조건 AND: 라벨 질의 ≥100건, G1+G4 비중 ≥30%(보조채널형 15%), Tier 1 상한 확인 후 잔여 실패율 ≥30%, 코퍼스 ≥800편/300만 토큰, 하루 ≥10질의 30일 지속, 파생 인덱스 배선. 하나라도 빠지면 도입하지 않는다.
- 비용은 초기 인덱싱이 아니라 질의당 변동비가 지배한다 — 전역 검색은 커뮤니티 레벨에 따라 질의당 컨텍스트가 26,657→745,740 토큰으로 약 28배 흔들린다. 그래프 인덱싱 달러 단가는 공개값이 없으므로 50편 파일럿으로 실측해 외삽해야 한다.
- 도입 후에는 라우팅률·기여율(그래프 근거가 최종 인용에 실제 포함된 비율)·A/B 승률을 30일 창으로 재고, 각각 15%/30%/55% 미만이면 그래프 경로를 off 한다(인덱스는 보존). 지표는 반드시 질의 유형별로 쪼개야 한다 — 총합이 60.2→71.2%로 오르는 동안 특정 부류가 94.6→80.4%로 무너진 실측 사례가 있다.
분모를 먼저 고정한다
"그래프가 이 질의를 더 잘 푼다"는 참일 수 있다. 문제는 그다음 문장이다 — 그 질의가 네 전체 질의의 몇 %인가. 그래프층의 이득은 (이기는 질의 비중) × (이기는 폭)인데, 대부분의 도입 논의는 두 번째 항만 놓고 벌어진다.
이 섹션이 쓰는 기준 규모(개인 세컨드 브레인 1인분): 문서 1,500편 / 총 600만 토큰(편당 ~4,000) / 하루 질의 12건(월 ~360건) / 월 증가 40편(+16만 토큰). 네 숫자가 이것의 1/3이면 아래 임계값은 전부 더 보수적으로 잡아야 한다.
질의 로그가 없을 때 분포를 재는 법 (2주, n=100)
로그가 없다는 건 측정을 미룰 이유가 아니라 손으로 100건을 모을 이유다. 비율 추정의 95% 신뢰구간은 p≈0.3일 때 n=30이면 ±16%p, n=100이면 ±9%p, n=300이면 ±5%p다. 30건짜리 감(感)으로는 "관계형 질의가 20%냐 45%냐"를 구분조차 못 한다.
수집은 세 갈래를 섞는다. (a) 소급 — 최근 4주 코딩 세션/채팅에서 "내 위키에 물었을 법한 질문"을 추출. (b) 전향 — 2주간 떠오른 질문을 답을 못 찾아도 그대로 적기. (c) 기존 로그 — 파일 검색/grep 히스토리, 브라우저 검색 기록. 소급만 쓰면 이미 답을 아는 질문으로 편향되어 단순 조회가 과대 계상된다(전향 수집이 최소 50%). 단, 위키를 만든 지 1개월 미만이라 전향 표본이 20건도 안 나온다면 반대다 — 그때 필요한 건 그래프가 아니라 문서다.
라벨은 유형만 붙인다. 정답·관련 문서 라벨은 붙이지 않는다(평가 골든셋 구성은 선행 글에서 다뤘다).
{"q":"트롤리 캘리브 기본 게인 값 뭐였지","type":"S","substitute":"grep"}
{"q":"이 결정 뒤집은 이유가 뭐였고 그때 무슨 사건이 있었나","type":"G4","substitute":"date-filter"}
{"q":"작년에 반복해서 실패한 패턴 3가지 뽑아줘","type":"G2","substitute":"MOC"}
{"q":"X를 참조하는 문서 전부","type":"G5","substitute":"index-scan"}
그래프가 이기는 질의 카탈로그 — 하나씩 대체 판정
| 코드 | 질의 유형 | 예시 | Tier 1 대체 수단 | 판정 | 근거 수치 |
|---|---|---|---|---|---|
| G1 | 다중 홉 브리지(중간 엔티티가 질문에 안 나옴) | "그 장애를 만든 커밋을 리뷰한 사람이 그 전에 뭘 고쳤나" | 2단 검색(1차 결과에서 엔티티 추출 → 재검색) | 부분 대체(3홉 이상만 그래프) | 2Wiki F1 61.5→71.0(+9.5), MuSiQue 45.7→48.6(+2.9) |
| G2 | 전역 요약/테마 도출 | "내 위키 전체에서 반복되는 실패 패턴" | 계층 요약(RAPTOR형) 또는 수작업 MOC | 대체 가능(요약 트리로 충분) | RAPTOR QuALITY 62.3→82.6; GraphRAG 포괄성 승률 72~83% |
| G3 | 역참조/백링크 | "이 노트를 인용하는 문서" | 수작업 백링크 + 링크 인덱스 | 완전 대체(그래프 불필요) | — |
| G4 | 시간적 변화·지식 갱신 | "이 방침이 언제 어떻게 바뀌었나" | 프론트매터 날짜 + 메타데이터 필터 | 부분 대체(무효화 추적은 그래프) | 시간 추론 +38.4%, 지식갱신 +6.5% |
| G5 | 전수 집계/부재 증명 | "X를 쓰는 문서 전부, 빠짐없이" | SQL·grep 결정적 인덱스 | 대체 가능(그래프가 오히려 열세) | 확률적 검색은 전수성 보장 못 함 |
| G6 | 다중 엔티티 비교 | "A안과 B안 결정 근거 대조" | 엔티티별 병렬 검색 후 합치기 | 부분 대체 | LV-Eval 9.8→12.9 |
| G7 | 단순 사실 조회 | "그 상수 기본값" | 하이브리드 검색 | 그래프가 손해 | NQ 61.9→46.9(GraphRAG), PopQA 55.7→48.1 |
7개 중 그래프만 이기는 건 G1의 깊은 홉과 G4의 무효화 추적, 사실상 두 부류다. G2는 계층 요약이 가져가고, G3·G5는 결정적 인덱스가 더 잘한다. 그리고 G7은 그래프를 켜면 나빠진다 — 이게 손익분기 계산의 마이너스 항이다.
손익분기: 몇 %면 본전인가
순이득 = s·Δ·r − (1−s)·m·L (s=그래프 전용 질의 비중, Δ=그 부류 개선폭 +10%p, r=라우터 적중률 0.65, m=오라우팅률 0.10, L=오라우팅 손실 15%p). Δ·L·r은 위 표의 공개 수치에서 보수적으로 가져온 값이다.
| s (G1+G4 비중) | 이득 | 손실 | 순이득 |
|---|---|---|---|
| 15% | +0.98%p | −1.28%p | −0.30%p |
| 20% | +1.30 | −1.20 | +0.10 |
| 30% | +1.95 | −1.05 | +0.90 |
| 40% | +2.60 | −0.90 | +1.70%p |
라우팅형 그래프의 손익분기는 s ≈ 19%, 유의미한 이득은 **s ≥ 30%**부터다. 단, 그래프를 라우팅이 아니라 보조 채널로만 쓰면 반대다 — 벡터 결과에 그래프 근거를 병합하는 설계는 오라우팅 손실 항이 사라져(단순 QA에서 NQ 63.3 ≥ 순수 임베딩 61.9) 임계가 **s ≈ 15%**로 내려간다. 비용은 그대로 들지만.
도입 정당화 체크리스트 (전부 AND)
| # | 조건 | 임계값 | 반대 조건 |
|---|---|---|---|
| C1 | 손 라벨 질의 수 | ≥100건, 전향 수집 ≥50% | 위키 3개월 미만이면 무의미 |
| C2 | G1+G4 비중 | 라우팅형 ≥30% / 보조채널형 ≥15% | 팀 공유 위키면 타인 질의가 다른 분포 |
| C3 | Tier 1 상한 확인 | 하이브리드+리랭크+2단 검색 붙인 상태에서 해당 부류 실패율 ≥30% | 리랭커 미도입이면 C3 미충족 |
| C4 | 코퍼스 규모 | ≥800편 / ≥300만 토큰 | 300편 이하면 전문을 컨텍스트에 넣는 게 싸다 |
| C5 | 질의량 지속 | 하루 ≥10건이 30일 연속 | 주 3회면 문서 증가가 병목 |
| C6 | 되돌릴 수 있는 배선 | 그래프는 파생 인덱스로만(→ 다른 섹션에서 다룸) | 원본 저장소면 즉시 탈락 |
비용 기준선(기준 규모, 600만 토큰): 컨텍스트 부여형 전처리는 문서 토큰 100만당 $1.02 → 초기 $6.1, 월 증분 $0.16. 그래프 추출은 문서 토큰당 LLM 호출이 여러 번이라 배수가 다르고, 공개된 달러 단가는 없다 — 100만 토큰 코퍼스의 그래프 인덱싱이 281분 / 8,564 노드 / 20,691 엣지였다는 기록만 있다. 50편 파일럿으로 직접 재고 600만 토큰으로 외삽하라. 질의 비용도 고정이 아니다: 전역 검색은 커뮤니티 레벨에 따라 질의당 컨텍스트가 26,657 → 745,740 토큰으로 약 28배 흔들린다. "전역 요약 하루 2건"이 월 1.6M 토큰일 수도, 44M 토큰일 수도 있다.
도입 후: 그래프 경로가 실제로 쓰이는가
도입은 가설이고, 계측이 판정이다. 세 지표를 30일 창으로 본다.
- 라우팅률 — 전체 질의 중 그래프 경로가 호출된 비율
- 기여율 — 그래프 경로가 낸 근거가 최종 답의 인용에 실제로 포함된 비율 (호출됐지만 안 쓰인 건 실패다)
- 승률 — 같은 질의를 벡터 단독과 A/B 했을 때 그래프 포함이 이긴 비율
-- 30일 롤백 판정 (n>=100 이어야 ±9%p 안에서 판단 가능)
SELECT count(*) AS n,
avg((graph_called)::int) AS routing_rate,
avg((graph_cited)::int) FILTER (WHERE graph_called) AS contribution_rate,
avg((winner = 'graph')::int) FILTER (WHERE ab_run) AS win_rate
FROM query_events
WHERE ts > now() - interval '30 days';
-- 롤백 트리거(하나라도 걸리면 그래프 경로 off, 인덱스는 보존):
-- n >= 100 AND (routing_rate < 0.15 OR contribution_rate < 0.30 OR win_rate < 0.55)
-- 또는 월 재인덱싱 비용 > 월 검색 비용 x 3
되돌리기는 그래프 삭제가 아니라 경로 off다. 인덱스는 남기고 호출만 끊어야 재판정이 싸다. 단, 재인덱싱이 월 예산을 먹고 있다면 인덱스도 얼려라(증분 중단).
흔한 자기기만 3종
- 데모 질의로 정당화하기. "이 질문 보세요, 벡터는 못 풀죠"는 그래프가 이기도록 고른 질의다. 처방은 사전 등록: 100건 라벨링을 먼저 끝내고 파일을 커밋한 뒤에 구현을 시작한다. 구현 중에 떠오른 질의는 다음 라운드 표본으로만 쓴다.
- 벤치마크 분포를 자기 분포로 착각하기. 질의 복잡도 분류 연구의 관측 분포(단순 8.6% / 단일단계 53.3% / 다단계 38.1%)는 단일홉·다중홉 데이터셋을 의도적으로 반반 섞어 만든 혼합이다. 개인 위키의 자연 분포가 아니다. 같은 연구에서 분류기 정확도가 약 54%였다는 점도 함께 봐야 한다 — 자동 라벨링으로 분모를 재려는 시도부터 신뢰구간 밖이다.
- 총합 개선 뒤에 숨은 부류 악화. 구조층은 어떤 부류를 반드시 나쁘게 만든다. 실제로 한 시간 지식그래프 메모리는 전체 정확도를 60.2→71.2%로 올리면서 특정 질의 부류에서는 94.6→80.4%(−17.7%)로 떨어뜨렸다. 유형별로 쪼개 보지 않으면 이 손실은 평균에 묻힌다. 롤백 지표는 반드시 유형별로 집계하라.
(각 기법의 정면 비교와 Tier 1 구현 상세는 → 다른 섹션에서 다룸)
참고 출처
↗ From Local to Global: A Graph RAG Approach to Query-Focused Summarization (Microsoft GraphRAG)↗ From RAG to Memory: Non-Parametric Continual Learning for Large Language Models (HippoRAG 2)↗ RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval↗ Zep: A Temporal Knowledge Graph Architecture for Agent Memory (Graphiti)↗ Adaptive-RAG: Learning to Adapt Retrieval-Augmented LLMs through Question Complexity↗ Introducing Contextual Retrieval (Anthropic)↗ RAG vs. GraphRAG: A Systematic Evaluation and Key Insights↗ GraphRAG Query Overview (local / global / DRIFT / basic search)↗ LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory캡처가 전부다 — 검색보다 유입이 먼저 무너진다
핵심 요점
- 실패 원인을 최근 질의 20건에 대해 A(문서 부재)/B(검색 실패)/C(낡음)로 라벨링하는 5분 진단을 제시 — 300~800문서·1~3M 토큰 구간에서는 A가 대개 과반이고, A≥50%면 검색층 투자는 낭비. 반대 조건: 주 5건 유입이 4주 연속이고 A<20%면 이 섹션을 건너뛰라.
- 캡처 마찰을 '인식→저장소 존재'까지의 벽시계 시간으로 정의하고 평균이 아닌 p50/p90으로 측정. 경로별 목표: 모바일 p50 8초, 브라우저 5초, 터미널 3초, p90 경고선 20초 초과 경로는 '없는 경로'로 간주.
- Claude Code 자동 캡처는 Stop이 아니라 SessionEnd + matcher + 턴 수 게이트로. 공식 문서 근거로 트랜스크립트는 비동기 기록이라 현재 턴 텍스트는 last_assistant_message를 써야 하고, 캡처 경로에 LLM 호출(요약)을 넣으면 안 된다는 원칙.
- 링크만 저장이 유일하게 후회하는 선택 — Pew 2024 실측(2013년 페이지 38% 소멸, 2013~2023 수집분 25% 소멸, 1년차도 8%, 위키백과 54%가 죽은 참조 보유). 반면 원문 마크다운은 연 2.9MB·임베딩 월 $0.012 수준으로 비용이 사실상 없음.
- 인박스/정본 물리 분리 + 소화율=(승격+폐기)/유입 ≥ 0.8, 30일 자동 아카이브(삭제 아님), 잔량이 주간 유입 4배 초과 시 인박스를 낮은 가중치로 검색 편입.
- 노이즈의 진짜 위험은 무관한 문서가 아니라 '관련 있는데 정답 없는' 조각 — Power of Noise(SIGIR 2024)는 무작위 문서가 정확도를 최대 35% 올리기도 하지만 고득점 무정답 문서는 성능을 떨어뜨린다고 보고. Lost in the Middle 때문에 k 증가로 덮는 것도 역효과.
분모가 0이면 검색 품질은 정의되지 않는다
이 글에서 "개인 규모"는 늘 같은 숫자를 가리킨다: 문서 300~800건, 총 1~3M 토큰, 하루 질의 3~15회, 월 신규 20~60건. 이 범위에서 세컨드 브레인이 죽는 방식은 검색 실패가 아니라 유입 정지다.
실패 원인을 섞어 보지 말고 최근 질의 20건을 세 가지로 라벨링하라. 5분이면 끝난다.
- A. 문서 자체가 없었다 (애초에 캡처 안 됨)
- B. 문서는 있는데 못 찾았다 (검색층 문제)
- C. 찾았는데 낡거나 틀렸다 (정본화·신선도 문제)
300~800문서 구간에서 전량은 1~3M 토큰이고, 이건 ripgrep + 에이전트가 파일을 직접 읽어 훑을 수 있는 크기다. 그래서 B는 잘 안 생긴다(→ 사다리 섹션에서 정량 해금 조건으로 다룸). 실측하면 대개 A가 절반을 넘는다. A ≥ 50%면 임베딩·리랭커·그래프에 쓰는 시간은 전부 낭비다.
단, 반대 조건: 4주 연속 주 5건 이상 유입이 유지되는데 A 비율이 20% 미만이라면 이 섹션은 당신 문제가 아니다. 바로 검색층으로 가라. 캡처 최적화는 그 시점부터 수익률이 0에 수렴한다.
마찰은 "초"로 재라
측정 정의를 못 박아야 개선을 증명할 수 있다. 캡처 마찰 = "저장해야겠다"고 인식한 순간부터 그 내용이 저장소에 실제로 존재하는 순간까지의 벽시계 시간. 앱 실행·로그인·제목 입력·태그 고민이 전부 포함된다. 10회 스톱워치로 p50과 p90을 재라. 평균은 쓰지 마라 — 캡처를 죽이는 건 평균이 아니라 꼬리다.
| 경로 | 목표 p50 | 경고선 p90 | 최대 탭/키 입력 | 구현 |
|---|---|---|---|---|
| 모바일(공유시트) | ≤ 8초 | 20초 | 3탭, 텍스트 입력 0 | iOS 단축어 → 인박스 파일 append 또는 POST /capture |
| 브라우저 | ≤ 5초 | 15초 | 단축키 1 + 확인 1 | Obsidian Web Clipper(공식·무료·오픈소스, Chromium/Firefox/Safari/Edge) |
| 터미널/에이전트 | ≤ 3초 | 10초 | 명령 1줄 | MCP 툴 1개(wiki_capture) 또는 wcap "..." 앨리어스 |
| 대화 종료 자동 요약 | 0초(무인) | — | 0 | Claude Code SessionEnd 훅 |
p90이 경고선을 넘는 경로는 "가끔 귀찮은" 게 아니라 사실상 없는 경로다. 미룬 캡처는 돌아오지 않는다. 이건 검증 가능한 주장이니 그렇게 검증하라 — 마찰 개선 전 4주와 후 4주의 주당 신규 문서 수를 비교하면 된다. 안 늘면 마찰이 원인이 아니었던 것이고, 그때 진짜 병목은 "무엇을 캡처할지 모름"이다. 그건 마찰 최적화로 안 고쳐진다.
자동 캡처: 훅은 축복이자 노이즈 발생기
Claude Code는 30종 가까운 훅 이벤트를 노출한다. 캡처에 쓸 만한 건 사실상 둘이다. Stop(응답 1턴 종료)과 SessionEnd(세션 종료). Stop을 그냥 물리면 하루 200턴이 200개 문서가 된다 — 이게 다음 절의 노이즈 문제를 직접 만든다. SessionEnd에 matcher를 걸고 턴 수 게이트를 두는 쪽이 맞다.
공식 문서에 명시된 함정 하나: 트랜스크립트 파일은 비동기로 기록되어 현재 턴의 최신 메시지가 아직 없을 수 있다. 현재 턴의 최종 텍스트가 필요하면 Stop이 주는 last_assistant_message 필드를 쓰고, 파일을 파싱하지 마라.
#!/usr/bin/env bash
# ~/.claude/settings.json 의 SessionEnd 훅 (matcher: "clear|logout|prompt_input_exit")
# stdin: {"session_id","transcript_path","cwd","hook_event_name","reason"}
set -euo pipefail
payload=$(cat)
tp=$(jq -r .transcript_path <<<"$payload")
cwd=$(jq -r .cwd <<<"$payload")
sid=$(jq -r .session_id <<<"$payload")
# 게이트 1: 짧은 세션은 버린다. 인박스 노이즈의 최대 공급원이다.
turns=$(jq -rs '[.[]|select(.type=="user")]|length' "$tp" 2>/dev/null || echo 0)
[ "$turns" -lt 6 ] && exit 0
out=~/wiki/inbox/$(date +%Y%m%dT%H%M%S)-cc-$sid.md
{
printf -- '---\nstatus: inbox\nsource: claude-code\ncwd: %s\nturns: %s\n---\n' "$cwd" "$turns"
jq -rs '.[]|select(.type=="user")|.message.content
|if type=="string" then . else (.[]?|.text? // empty) end' "$tp"
} > "$out"
# 게이트 2: 요약은 여기서 하지 마라. 캡처 경로에 LLM 호출을 넣으면
# 지연·실패·과금이 캡처 자체를 죽인다. 요약은 주간 배치로 분리.
캡처 경로에 LLM 호출을 넣지 마라는 원칙이 핵심이다. 요약은 파생물이고 언제든 재생성되지만, 놓친 캡처는 재생성되지 않는다. 단, 반대 조건: 팀 위키처럼 쓰는 사람과 읽는 사람이 다르면 원문 그대로의 세션 덤프는 읽히지 않으므로 캡처 시점 요약이 정당화된다.
원문 vs 요약 vs 링크 — 후회는 링크 쪽에서 온다
| 저장 방식 | 개인 규모 연간 용량 | 복구 가능성 | 검색 품질 | 후회 시점 |
|---|---|---|---|---|
| 링크만 | ~0 | 불가 (원본 소멸 시 영구 손실) | 최악(제목만 인덱싱) | 3~5년 뒤 |
| 요약만 | 월 40건×0.5KB ≈ 0.24MB | 불가(원문 세부 소실) | 좋음(밀도 높음) | 세부가 필요해진 날 |
| 원문 마크다운 | 월 40건×6KB ≈ 2.9MB/년 | 가능 | 좋음(청킹 필요) | 거의 없음 |
| 바이트 아카이브(ArchiveBox 등) | URL당 5~30MB → 0.2~1.2GB/년 | 완전 | 별도 파이프라인 필요 | 용량·운영 부담 |
링크만 저장이 왜 최악인지에는 실측 근거가 있다. Pew Research Center의 2024년 조사(Common Crawl 표본 약 100만 페이지)는 2013년에 존재하던 웹페이지의 38%가 10년 뒤 접근 불가, 2013~2023년 수집분 전체의 25%가 2023년 10월 기준 소멸, 심지어 생성 1년 된 페이지도 8%가 이미 죽었다고 보고한다. 위키백과 문서의 54%는 참고문헌에 죽은 링크를 최소 하나 갖고 있다. 즉 링크 북마크는 연 3~4% 복리로 증발하는 자산이다.
반면 원문 마크다운의 비용은 계산해 보면 존재하지 않는다. 월 40건, 문서당 6KB면 연 2.9MB, 10년 29MB다. 임베딩도 월 40건×3k토큰 = 120k토큰/월이고, 1M 토큰당 $0.1을 가정하면 월 $0.012다(모델별 단가는 확인 필요 — 자기가 쓰는 모델 가격표로 다시 계산하라).
권고: 원문 마크다운 + 요약을 함께 저장하고, 링크는 부속 메타로만 둔다. 요약은 파생 인덱스이므로 언제든 재생성되지만 원문은 재생성 불가다.
단, 반대 조건 3가지: ① 저작권 있는 유료 콘텐츠 전문 — 링크+발췌만. ② 원문이 이미 불변 정본인 경우(자기 git 저장소, 커밋 SHA로 고정 가능한 코드) — 링크가 정답이고 복사는 드리프트만 만든다. ③ 바이트 아카이브는 기본값으로 켜지 마라. 연 1GB는 개인 규모에서 백업·이관 부담이 캡처 습관보다 먼저 무너진다.
인박스와 정본을 갈라라 — 요구사항이 서로 충돌하니까
한 곳에 두면 반드시 한쪽이 진다. 캡처는 "판단 0초"를 요구하고 정본은 "제목·태그·링크·중복 확인"을 요구하기 때문이다. 물리적으로 분리하라.
inbox/— append-only, 파일명은 타임스탬프, 프론트매터는status: inbox한 줄이면 충분. 제목 없어도 통과.notes/— 정본. 사람이 한 번은 읽은 것만 들어온다.
인박스 소화율 = (주간 승격 건수 + 주간 폐기 건수) / 주간 유입 건수. 목표 ≥ 0.8. 폐기가 분자에 들어가는 게 중요하다 — 버리는 것도 처리다.
인박스가 쌓이기만 할 때의 처방은 "더 열심히 처리하기"가 아니다. 그건 이미 실패한 전략이다.
- 배치 고정: 주 1회 20분, 20건. 건당 60초. 에이전트가 승급 초안을 만들고 사람은 y/n만 누른다.
- 만료: 30일 지난 인박스 항목은 자동으로
archive/로 이동(삭제 아님). 30일간 안 본 건 이미 안 중요했다는 증거다. 단, 반대 조건:contract/incident/legal태그는 만료 대상에서 제외. - 잔량 상한: 인박스 잔량이 주간 유입의 4배를 넘고 소화율 < 1이 4주 지속되면, 승격을 포기하고 인박스를 낮은 가중치로 검색 대상에 편입하라. 안 찾아지는 정본보다 찾아지는 인박스가 낫다.
캡처 노이즈가 검색을 망치는 정확한 지점
3번 처방은 다음 위험과 정면으로 충돌하므로 조건부다. 위험한 건 무관한 쓰레기가 아니라 비슷한 조각이다.
Cuconasu 등의 "The Power of Noise"(SIGIR 2024)는 반직관적 결과를 보고한다: 무작위 문서를 프롬프트에 넣으면 정확도가 최대 35%까지 오르기도 하지만, retriever가 높은 점수를 매겼는데 정답을 담고 있지 않은 문서는 성능을 떨어뜨린다. 자동 세션 덤프·중복 클리핑·같은 주제 미완성 메모가 만드는 게 정확히 이 유형이다. 게다가 Liu 등의 "Lost in the Middle"(TACL 2023)에 따르면 관련 정보가 컨텍스트 중간에 놓이면 성능이 뚜렷이 저하되므로, k를 키워 노이즈를 덮으려는 시도는 역효과다.
| 방어 | 구현 | 판정 기준 |
|---|---|---|
| 상태 필터 | status: inbox|canonical 필드로 1차 필터 |
정본 검색이 0건일 때만 인박스 폴백 |
| 근사 중복 제거 | 정규화 URL upsert + SimHash/MinHash | 임계값은 자기 코퍼스로 측정해서 정하라(고정값 복붙 금지) |
| 세션 덤프 게이트 | 턴 수 < 6 폐기, 도구 호출만 있는 세션 폐기 | 주간 자동 캡처 중 승격률 < 10%면 게이트를 조여라 |
| k 억제 | k를 늘리지 말고 정본 우선 2단 검색 | k 증가가 정답률을 못 올리면 문제는 k가 아니라 코퍼스 |
성공 지표 — 이 네 개만 본다
| 지표 | 정의 | 개인 규모 목표 | 경고선 | 조치 |
|---|---|---|---|---|
| 주당 신규 문서 | 정본+인박스 신규 | ≥ 5건/주 (월 20~60) | 2주 연속 < 3 | 마찰 재측정, 경로 추가 |
| 인박스 소화율 | (승격+폐기)/유입 | ≥ 0.8 | 4주 연속 < 0.6 | 배치 고정 또는 만료 도입 |
| 캡처 마찰 p90 | 인식→저장 벽시계 | ≤ 20초 | > 30초 | 그 경로는 없는 것으로 간주 |
| 경로별 기여도 | 경로당 주간 신규 | 각 ≥ 1건 | 4주 연속 0 | 그 경로를 삭제하라 |
마지막 줄이 제일 자주 무시된다. 만들어 놓고 안 쓰는 캡처 경로는 중립이 아니라 부채다 — 유지보수 대상이고, 무엇보다 "나는 캡처 시스템이 있다"는 착각을 유지시켜 A 비율 진단을 흐린다. 주당 신규 5건이 4주 연속 유지되기 전에는 검색층에 한 줄도 쓰지 마라. 그 조건을 넘겼을 때 무엇이 다음 계단인지는 (→ 사다리 섹션)에서 정량 트리거로 다룬다.
참고 출처
↗ When Online Content Disappears: Link Rot and Digital Decay on Government, News and Other Webpages — Pew Research Center (2024)↗ Hooks reference — Claude Code Docs (SessionEnd / Stop payload fields, transcript async caveat)↗ The Power of Noise: Redefining Retrieval for RAG Systems — Cuconasu et al. (SIGIR 2024, arXiv:2401.14887)↗ Lost in the Middle: How Language Models Use Long Contexts — Liu et al. (TACL 2023, arXiv:2307.03172)↗ Obsidian Web Clipper — 공식 무료 브라우저 확장 (Chromium/Firefox/Safari/Edge)↗ ArchiveBox — self-hosted web archiving (SingleFile/PDF/screenshot/WARC 등 다중 포맷)에이전트가 쓰는 위키 — 자동 캡처의 안전한 형태
핵심 요점
- 자동 캡처는 Tier 사다리와 직교한 별도 스위치다 — 해금 조건은 '수동 누락률 ≥80% AND 시범 승격률 ≥40% → 정본 증가율 +50%'이며, 미달이면 배선 0시간 대안(에이전트가 초안 출력, 사람이 붙여넣기)이 개인 규모에서 자주 이긴다
- 불변식 4개(append-only 인박스 / 정본은 사람 승인 / 출처 필수 fail-closed / 생성 출처 표기)는 프롬프트가 아니라 PreToolUse 훅과 MCP 서버 경로 화이트리스트로 강제해야 한다 — MCP 스펙상 ToolAnnotations는 힌트일 뿐이고 destructiveHint 기본값은 true다
- 툴은 3개(wiki_search / wiki_read / inbox_append)로 시작하고, 재인용 방어의 8할은 'wiki_search 기본 스코프 = canon, 인박스는 옵트인 + 결과마다 tier 동반 반환'이라는 한 줄에서 나온다
- PoisonedRAG는 수백만 문서 DB에 질문당 5건 주입으로 ASR 90%를 얻는다 — top-k 점거에 필요한 문서 수는 코퍼스 크기와 거의 무관하므로 300~3,000편짜리 개인 볼트는 잘못된 노트 1건으로도 오염된다
- 충돌은 해결하지 말고 없애라: 파일 1개 = 캡처 1건, 파일명 <ULID>--<agent-id>.md. 단일 로그 파일만 .gitattributes merge=union으로 허용하고 정본엔 금지하며, Obsidian Sync는 기기마다 '충돌 파일 생성'으로 바꿔야 한다(기본 자동 병합은 문장을 조용히 섞는다)
- 리뷰 예산은 평균 90초/건으로 환산 — 10분/일이면 하루 캡처 상한 8건. 상한을 서버가 강제하고, 승격률 30% 미만이면 트리거를 좁히고 70% 초과면 상한을 올려라. '미리뷰 인박스 / 정본 > 0.2'가 최초 경보 지표다
자동 쓰기는 사다리의 층이 아니라 직교한 스위치다
캡처 자동화는 Tier 0에서도 켤 수 있고 Tier 3에서도 끌 수 있다. "Tier 1까지 왔으니 이제 에이전트가 쓰게 하자"는 잘못된 추론이다. 이 글이 고정한 개인 규모 — 정본 300~3,000편, 편당 300~1,200 토큰(총 0.1M~3.6M), 하루 질의 3~30건, 월 정본 증가 10~40편 — 에서 자동 쓰기의 유일한 정당화는 "수동으로는 문서가 안 늘어난다"이다. 그 증거가 없으면 이 스위치는 끄고 시작하라.
최소 안전 설계: 불변식 4개, 그리고 강제 지점
이 4개를 프롬프트나 CLAUDE.md 규약으로 부탁하면 안 된다. 경로 레벨에서 거부해야 한다. MCP 스펙은 ToolAnnotations의 모든 속성이 "hints"이며 "신뢰할 수 없는 서버에서 온 어노테이션에 근거해 툴 사용을 결정하지 말라"고 명시한다. 즉 destructiveHint: false는 선언이지 보증이 아니다(참고로 기본값은 readOnlyHint: false, destructiveHint: true).
| 불변식 | 강제 지점 | 깨졌을 때의 증상 |
|---|---|---|
① 쓰기는 inbox/ 신규 파일만 |
PreToolUse 훅 + MCP 서버 경로 화이트리스트 | 정본 문단이 조용히 바뀜. git diff 안 보면 영영 모름 |
| ② 정본 수정은 사람 승인 | 정본 디렉토리를 에이전트에게 read-only | 리뷰 없는 문장이 검색 top-k를 점거 |
| ③ 출처 필수(fail-closed) | source: 없는 본문은 쓰기 거부 |
6개월 뒤 재검증 불가. 폐기밖에 답이 없음 |
| ④ 생성 출처 표기 | generated_by: 필수 + 검색 결과에 tier 동반 반환 |
에이전트가 자기 문장을 근거로 재인용 |
③의 출처는 URL이 아니라 재현 가능한 좌표여야 한다. 에이전트 관찰의 대부분은 웹이 아니라 내 저장소에서 나오므로 repo@sha:path#L120-140 또는 세션 ID가 실제로 쓸모 있다. 단, 웹 리서치 캡처라면 URL + 접속일자가 맞다.
#!/usr/bin/env bash
# .claude/hooks/wiki-write-guard.sh ← PreToolUse matcher: Write|Edit|MultiEdit
set -euo pipefail
IN=$(cat); VAULT="$HOME/wiki"
P=$(jq -r '.tool_input.file_path // ""' <<<"$IN")
C=$(jq -r '.tool_input.content // ""' <<<"$IN")
deny() { jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",
permissionDecision:"deny", permissionDecisionReason:$r}}'; exit 0; }
[[ "$P" == "$VAULT"/* ]] || exit 0 # 볼트 밖은 이 훅의 관심사가 아님
[[ "$P" == "$VAULT"/inbox/* ]] || deny "정본은 읽기 전용. inbox/ 에 새 파일로 제안하세요."
[[ -e "$P" ]] && deny "인박스는 append-only. 기존 파일 수정 불가."
[[ "$(basename "$P")" =~ ^[0-9A-HJKMNP-TV-Z]{26}--[a-z0-9-]+\.md$ ]] \
|| deny "파일명은 <ULID>--<agent-id>.md"
grep -q '^source: ' <<<"$C" || deny "출처 없는 캡처는 거부."
grep -q '^generated_by: ' <<<"$C" || deny "생성 출처 표기 필요."
exit 0
가드는 스스로를 증명해야 한다. 배선 직후 뮤테이션 3발 — 정본 경로 쓰기, 인박스 기존 파일 수정, source: 누락 — 을 실제로 던져 전부 deny가 나오는지 확인하라. 훅이 조용히 미등록된 채 "안전하다고 믿는" 상태가 최악이다.
반대 조건: 볼트를 혼자 쓰고, 정본이 전부 git에 있고, 매일
git diff를 본다면 훅 없이 규약만으로 2주 시범 운영해도 된다. 단 정본이 한 번이라도 오염되면 그날 훅으로 승격하라.
MCP 툴 3개와 역할 경계
Anthropic의 툴 설계 가이드는 "툴이 많다고 결과가 좋아지지 않는다"며 소수의 고임팩트 툴에서 시작하라고 권한다. 위키 쓰기 배선의 최소 집합은 3개다. (스키마 상세는 → 선행글B)
| 툴 | 하는 일 | 절대 안 하는 일 | 어노테이션 |
|---|---|---|---|
wiki_search |
질의 → 문서 후보 + 각 결과의 tier(canon/inbox) |
본문 전문 반환, 요약 생성 | readOnlyHint: true |
wiki_read |
문서 ID → 본문 + frontmatter | 경로 추측 접근, 인박스 기본 노출 | readOnlyHint: true |
inbox_append |
새 캡처 1건을 inbox/에 파일로 생성 |
기존 파일 수정, 정본 접근, 삭제 | destructiveHint: false (additive-only) |
경계의 핵심은 wiki_search가 기본 스코프를 canon으로 고정한다는 것이다. 인박스는 include_unreviewed: true를 명시해야만 들어오고, 들어와도 결과마다 tier가 붙는다. 이 한 줄이 재인용 방어의 8할이다.
반대 조건: 에이전트가 읽기만 하면 되면 2개로 끝난다. 반대로 주당 20건 이상을 승격 처리한다면 리뷰 큐 조회(
inbox_list)를 CLI가 아닌 4번째 툴로 노출할 값어치가 생긴다. 그 이상은 늘리지 마라.
재인용 오염: 개인 볼트는 corpus가 얇아서 더 위험하다
PoisonedRAG는 수백만 문서의 지식 DB에 질문당 악성 텍스트 5건만 주입해도 공격 성공률 **90%**를 얻는다(USENIX Security 2025). 악의가 없어도 결론은 같다: top-k를 점거하는 데 필요한 문서 수는 코퍼스 크기와 거의 무관하다. 정본 300~3,000편짜리 개인 볼트에서는 에이전트가 잘못 쓴 노트 1건이면 충분하다.
Shumailov et al.의 모델 붕괴 정의 — "모델이 생성한 데이터가 다음 세대의 학습 데이터를 오염시키는 퇴행 과정"(Nature 631:755–759, 2024) — 는 학습 시점 현상이지 검색 시점 현상이 아니다. 유추로만 쓰라. 검색 계층에서 같은 퇴행이 몇 세대 만에 나타나는지는 개인 규모 실측 데이터가 없다 — 네 볼트에서 측정해서 정하라.
최소 방어는 3개면 족하다.
| 방어 | 구현 | 비용 |
|---|---|---|
| tier 동반 반환 | 검색 결과 각 항목에 canon / inbox-unreviewed |
필드 1개 |
| 기본 스코프 = canon | include_unreviewed 옵트인 |
파라미터 1개 |
| 미리뷰 TTL | 30일 경과 인박스는 아카이브(검색 제외) | cron 1줄 |
경보 지표: 미리뷰 인박스 수 / 정본 수 > 0.2면 이미 리뷰 예산을 넘긴 것이다. 이 비율은 검색 품질보다 먼저 무너진다.
반대 조건: 인박스가 "오늘 작업 로그"를 겸한다면 당일 질의는 인박스 포함이 맞다. 대신 7일 넘은 미리뷰는 반드시 제외하라.
다중 에이전트·다중 기기: 충돌은 해결하지 말고 없애라
파일 1개 = 캡처 1건. 파일명을 <ULID>--<agent-id>.md로 하면 충돌이 구조적으로 발생하지 않는다. ULID는 128비트(48비트 ms 타임스탬프 + 80비트 랜덤), 26자, 사전식 정렬 가능이며 같은 밀리초 안에서도 단조 증가를 보장한다 — 즉 파일명이 곧 시간순 정렬 키다. 3개 기기 × 4개 에이전트가 동시에 써도 이름이 겹칠 확률은 실무상 0이다.
| 동기화 방식 | 같은 파일 충돌 시 | 에이전트 쓰기에 적합? |
|---|---|---|
| git + 파일당 1캡처 | 발생 안 함 | ✅ 기본값 |
git + 단일 로그 파일 + merge=union |
양쪽 줄을 모두 채택 | △ 인박스 로그에만 |
| Obsidian Sync (기본) | md는 diff-match-patch 자동 병합 | ❌ 문장이 조용히 섞임 |
| Obsidian Sync 1.9.7+ "충돌 파일 생성" | 별도 파일로 분리 | ✅ 이 설정으로 바꿔라 |
# 인박스 로그류만 union 허용. git 문서 경고: 병합 결과의 줄 순서가
# 뒤섞일 수 있고 "사용자가 결과를 검증해야 한다".
inbox/_log.md merge=union
# 정본은 절대 금지 — 두 문장이 섞이면 사실이 조용히 바뀐다.
canon/**/*.md merge=text
Obsidian Sync의 충돌 설정은 기기별로 따로 걸어야 한다는 점을 놓치기 쉽다. 한 대만 자동 병합으로 남아 있으면 방어가 없는 것과 같다.
반대 조건: 사람만 쓰는 볼트라면 자동 병합이 실용적으로 낫다. 에이전트가 쓰기 시작하는 순간에만 바꿔라.
사람 리뷰 예산을 숫자로 잡기
코드 리뷰 연구의 고전적 수치 — 1회 200~400 LOC, 60분 이내, 그 범위에서 결함 발견율 70~90% — 를 노트 리뷰로 옮기면 병목은 읽기가 아니라 판정이다. 건당 실측 분해(내 추정치이므로 20건 타이머로 직접 재라):
- 읽기 15~30초 + 판정(정본 어디에 붙일지/버릴지) 20~40초 + 승격 시 편집 60~180초
- 승격률 40% 가정 → 평균 90초/건
| 리뷰 예산 | 처리량 | 캡처 상한(서버 강제) |
|---|---|---|
| 10분/일 | 6~7건/일 | 하루 8건 |
| 20분/일 | 13~15건/일 | 하루 15건 |
| 주 1회 30분 | 20건/주 | 하루 3건 |
inbox_append에 일일 상한을 서버가 강제하라. 상한을 넘으면 툴이 거부하고 이유를 반환한다. 리뷰 안 된 인박스가 쌓이면 앞 절의 오염 위험과 볼트 신뢰가 동시에 무너진다. 조절 손잡이는 승격률이다: 30% 미만이면 캡처 트리거가 너무 넓은 것이고, 70% 초과면 상한을 올려라.
반대 조건: 배치 리뷰를 하고 승격률이 이미 70%를 넘는다면 상한이 아니라 필터를 푸는 게 맞다.
그리고 — 아예 하지 않는 선택지
이 글의 논지는 "실패는 검색 품질이 아니라 문서가 안 늘어나는 데서 온다"였다. 그런데 자동 캡처가 늘리는 것은 인박스 수지 정본 수가 아니다. 월 30건의 캡처 가치가 있는 순간을 가정한 정직한 계산:
| 월 기준 | 수동만 | 자동 캡처 + 리뷰 |
|---|---|---|
| 인박스 진입 | 10건 (누락률 67%) | 30건 |
| 정본 승격 | 10편 | 12편 (승격률 40%) |
| 사람 시간 | 15분 | 45분 |
| 배선 비용 | 0 | 8~16시간(1회) |
정본 +2편/월을 얻자고 월 30분과 초기 12시간을 쓰는 셈이다. 시간으로는 회수되지 않는다. 자동 쓰기의 해금 조건은 시간이 아니라 문서 수로 잡아야 한다.
- 해금: 2주 실측에서 수동 캡처 누락률 ≥ 80% 그리고 시범 자동 캡처의 승격률 ≥ 40% → 정본 증가율 +50% 이상이 나올 때만.
- 미해금 시 대안(배선 0시간): 세션 끝에 "이거 인박스 형식으로 뽑아줘" → 에이전트가 frontmatter 포함 초안을 출력 → 사람이 붙여넣기. 불변식 4개가 자동으로 지켜지고, 리뷰와 캡처가 같은 동작으로 합쳐진다. 개인 규모에서 이 방식이 이기는 경우가 생각보다 많다.
반대 조건: 하루에 3개 이상 저장소를 오가는 멀티 에이전트 환경이라면 실제 누락률이 90%대로 측정되는 일이 흔하다. 그 구간에서는 자동 캡처가 명확히 이긴다 — 다만 그때도 정본 쓰기는 끝까지 사람 손에 두라.
켜기 전 체크리스트: ① 정본 경로 쓰기가 실제로 deny되는가(뮤테이션으로 증명) ② source: 누락이 거부되는가 ③ 검색 기본 스코프가 canon인가 ④ 파일명이 ULID라 두 기기 동시 쓰기에 충돌이 없는가 ⑤ 일일 캡처 상한이 서버에 있는가 ⑥ 2주 뒤 승격률을 볼 수 있게 기록되는가.
참고 출처
↗ Model Context Protocol — Server Tools (human-in-the-loop, annotations untrusted)↗ MCP schema.ts 2025-06-18 — ToolAnnotations (readOnlyHint / destructiveHint defaults)↗ Claude Code — Hooks reference (PreToolUse, permissionDecision: deny)↗ PoisonedRAG: Knowledge Corruption Attacks to Retrieval-Augmented Generation (USENIX Security 2025)↗ Shumailov et al., AI models collapse when trained on recursively generated data, Nature 631:755–759 (2024)↗ git — gitattributes: built-in merge drivers (union)↗ Obsidian Help — Sync troubleshooting / conflict handling↗ Anthropic Engineering — Writing effective tools for agents↗ ULID Specification↗ SmartBear — Best Practices for Peer Code Review (Cisco study: 200–400 LOC, <60 min, 70–90% defect discovery)리뷰와 감쇠 — 안 쓰는 지식은 어떻게 처리하나
핵심 요점
- 개인 위키가 망가지는 시점은 문서 수가 아니라 오염 확률이다 — 낡은 문서 비율 10% 면 top-5 에 1건 이상 섞일 확률이 41%(1−(1−p)⁵)이고, HoH 벤치마크는 현재 정보가 정상 검색된 상황에서도 낡은 passage 1건이 주요 LLM 성능을 20% 이상, Llama-70B 종합 점수를 24% 떨어뜨린다고 보고한다.
- 오염은 기존 품질지표로 안 보인다 — 모순 조건에서 R₁ 은 18.2% 떨어지는데 BERTcosine 은 8.3% 밖에 안 떨어지고, 코사인 유사도로 모순과 중복을 가르는 AUROC 는 0.59(≈동전던지기)라 결정적 (subject, relation) 키 매칭이나 LLM 배치 판정이 필요하다.
- 리뷰 큐는 간격반복이 아니라 결정적 규칙 4종(stale 180일/open_loop 30일/orphan=인바운드0 AND 90일 무조회/conflict 즉시)으로 만들고, 우선순위만 노출빈도×낡음으로 정렬한다. FSRS 는 잘 검증됐지만 목표변수가 '내 회상 확률'이지 '문서가 아직 참일 확률'이 아니다.
- 틀린 문서는 삭제가 아니라 supersede 가 기본값 — 삭제하면 반증 이력·링크·타 문서의 낡은 인용을 동시에 잃는다. bi-temporal ledger(valid_to + superseded_by)는 stale-fact 오답률을 일반 RAG 의 15~40% 에서 ~0% 로 낮췄고 개인 규모에선 컬럼 두 개다.
- 지식 부채 지표는 SR<10%, UR<15%, OR<20%, CC 0~2건 + 결과지표 RSE(top-5 stale 노출률)<10%. 하나만 고른다면 RSE — SR 이 높아도 아무도 안 부르는 문서면 실질 피해는 0 이다. OR 0% 목표는 금물(Wikipedia 도 53,085편 백로그 유지).
- 배치는 주 1회 25분·최대 12건 하드 상한에 '나중에' 버튼 없는 3액션. 월 증가 40편이면 주 9.2편으로 산수가 맞지만 월 100편을 넘으면 LLM 트리아지 없이는 파산한다. 무리뷰+recency decay 전략은 낡은 문서 top-5 hit rate 를 88.37%→49.50% 로 절반만 줄이므로 대체재가 아니라 감쇠재다.
쓰레기장이 되는 건 문서가 많아져서가 아니다
세컨드 브레인이 무너지는 지점은 "문서가 3,000편을 넘어서"가 아니라 틀리거나 낡은 문서가 top-k 에 끼어들 확률이 임계를 넘는 순간이다. 이 글이 고정한 개인 규모(문서 500~3,000편, 총 300만~2,000만 토큰, 하루 질의 10~40건, 월 증가 30~60편)에서 산수는 단순하고 잔인하다. 낡은 문서 비율이 p 일 때 top-5 에 최소 1건이 섞일 확률은 1−(1−p)⁵ 다.
| 낡은 문서 비율 p | top-5 에 1건 이상 섞일 확률 |
|---|---|
| 2% | 9.6% |
| 5% | 22.6% |
| 10% | 41.0% |
| 20% | 67.2% |
그리고 그 1건이 실제로 답을 망친다. HoH 벤치마크(96,124 QA·219,463 문서, ACL 2025)는 현재 정보가 정상적으로 검색된 상황에서도 낡은 문서가 함께 들어오면 주요 LLM 성능이 최소 20% 떨어진다고 보고한다. Llama-70B 는 낡은 passage 를 단 1건 넣었을 때 perfect score 가 10%p 이상 하락하고 유해 출력이 최대 11%p 증가하며 종합 점수는 24% 이상 떨어졌다. 모순 문서도 같은 방향이다 — 의료 RAG 실험(8,856 질의·약 40만 초록)에서 모순 조건은 R₁ 을 평균 18.2% 떨어뜨렸다(Mixtral 은 20%).
여기서 진짜 무서운 건 탐지가 안 된다는 점이다. 같은 실험에서 BERTcosine 같은 의미 유사도 지표는 8.3% 밖에 안 떨어졌다. 즉 임베딩 기반 품질 지표를 보고 있으면 위키가 썩는 걸 못 본다. 미검증 문서 비율이 33% 수준으로만 올라가도 GPT-4o 의 parametric override rate 는 37.8% → 41.9% 로 올라가고, 해당 연구는 안전한 하한선이 없다고 명시한다.
결론: 개인 위키의 실패는 "검색이 나빠지는" 사건이 아니라 "검색이 멀쩡해 보이면서 틀린 답을 주는" 사건이다. 그래서 리뷰는 검색 품질 개선이 아니라 오염원 제거 작업으로 설계해야 한다.
리뷰는 암기가 아니다 — 목표변수가 다르다
간격반복(SM-2/FSRS)을 그대로 옮기고 싶은 유혹이 있는데, 하지 마라. FSRS 는 약 1만 사용자·7.27억 리뷰로 벤치마크된 잘 검증된 스케줄러지만(FSRS-7 recency: logloss 0.3414, RMSE 0.0627, AUC 0.7097), 목표변수가 "내가 회상할 확률"이지 "문서가 아직 참일 확률"이 아니다. 위키 문서의 유효기간은 내 기억이 아니라 바깥 세계의 변화 속도가 결정한다. 간격은 결정적 규칙으로 정하고, 순위만 노출빈도로 매겨라.
단, 반대인 경우: 위키에 어학·시험·용어 암기 노트가 30% 이상 섞여 있다면 그 하위 집합엔 FSRS 를 쓰는 게 맞다. 두 스케줄러를 한 큐에 섞지만 마라.
자동 큐 4종과 임계값
| 큐 | 트리거 규칙 | 개인 규모 기본 임계 | 반대 조건 |
|---|---|---|---|
| stale | doc_kind='stateful' 이고 verified_at 경과 |
180일 (설정값·엔드포인트·가격·정책은 90일) | 사건 기록·회의록·실험 로그(timeless)는 영원히 큐에 넣지 마라. 사건은 낡지 않는다 |
| open_loop | 본문에 미해결 질문 마커가 있고 30일 경과 | 30일 | 의도적 장기 리서치 노트는 defer_until 로 예외 처리 |
| orphan | 인바운드 링크 0 AND 90일간 검색 히트 0 | 두 조건 AND | 인박스·데일리 노트는 원래 고아다. 제외 |
| conflict | 같은 (주제, 속성) 키에 서로 다른 값 | 1건이라도 = 즉시 최상위 | 없음. 이건 무조건 최우선 |
conflict 탐지를 임베딩으로 하려는 시도는 실패한다. 최근 연구는 코사인 유사도가 모순과 중복을 구분하는 AUROC 가 0.59(동전던지기 0.5)라고 보고한다. 결정적 (subject, relation) 키 매칭이나 LLM 배치 판정을 써라.
-- 리뷰 큐 뷰. 임계값은 상수가 아니라 튜닝 대상이다.
CREATE VIEW review_queue AS
WITH sig AS (
SELECT d.id, d.title, d.doc_kind, -- 'stateful' | 'timeless'
now() - d.verified_at AS age,
d.open_question_count AS open_q,
(SELECT count(*) FROM links l WHERE l.dst = d.id) AS inbound,
COALESCE(q.hits_90d, 0) AS hits_90d,
(SELECT count(*) FROM conflicts c
WHERE c.a = d.id OR c.b = d.id) AS conflicts
FROM docs d LEFT JOIN query_hits_90d q ON q.doc_id = d.id
WHERE d.status = 'active'
)
SELECT id, title,
CASE
WHEN conflicts > 0 THEN 'conflict'
WHEN doc_kind = 'stateful' AND age > interval '180 days' THEN 'stale'
WHEN open_q > 0 AND age > interval '30 days' THEN 'open_loop'
ELSE 'orphan'
END AS queue,
-- 우선순위 = 노출빈도 x 낡음. 아무도 안 읽는 문서는 뒤로 민다.
(hits_90d + 1) * EXTRACT(epoch FROM age) / 86400 AS priority
FROM sig
WHERE conflicts > 0
OR (doc_kind = 'stateful' AND age > interval '180 days')
OR (open_q > 0 AND age > interval '30 days')
OR (inbound = 0 AND hits_90d = 0)
ORDER BY (conflicts > 0) DESC, priority DESC
LIMIT 12; -- 주간 배치 상한. 큐가 40건이어도 12건만 나온다.
삭제 vs 아카이브 vs 감쇠 표시
| 처리 | 인덱스 | 검색 영향 | 링크 | 언제 쓰나 | 반대 조건 |
|---|---|---|---|---|---|
| 삭제 | 제거 | 오염 0, 대신 반증 이력도 0 | 깨짐 | 중복·오타·개인정보 | 판단이 틀렸다고 밝혀진 문서엔 쓰지 마라 |
| 아카이브 | 제거(원문 보존) | 기본 검색에서 사라짐, 명시 요청 시만 조회 | 유지 | 끝난 프로젝트, 폐기된 제품 | 아직 인용되는 개념 정의는 아카이브하면 답이 비어버린다 |
| 감쇠 표시(supersede) | 유지하되 랭킹 페널티 + 정정 헤더 | top-k 진입 확률만 낮춤. 질의하면 "예전엔 X 였으나 지금은 Y" 로 답함 | 유지 | 틀렸음이 밝혀진 문서의 기본값 | 오염이 심하고 정정본이 확실하면 본문 청크만 인덱스에서 빼라 |
틀린 문서를 지우면 왜 손해인가. 세 가지를 동시에 잃는다. ① 왜 그렇게 믿었는지가 사라져 같은 오진을 6개월 뒤 반복한다 ② 그 문서를 인용하던 다른 문서들의 링크가 깨져 고아가 늘어난다 ③ 지워도 다른 문서에 남은 낡은 인용은 그대로 검색된다. 최근의 bi-temporal 접근(MemStrata)은 "retire, not delete" — 같은 (subject, relation) 에 다른 값이 들어오면 옛 행의 valid_to 를 닫고 superseded_by 를 건다 — 로 stale-fact 오답률을 일반 RAG 의 15~40% 에서 ~0% 로 낮췄고, 검색 지연은 2.1초를 유지했다(LLM 검증 방식은 16~18초). 개인 규모에서 이건 컬럼 두 개다.
지식 부채 지표 4+1
| 지표 | 정의 | 목표 | 경보 |
|---|---|---|---|
| SR Stale Ratio | stateful 중 임계 초과 비율 |
< 10% | > 20% |
| UR Unresolved Ratio | 미해결 질문 보유 문서 비율 | < 15% | > 30% |
| OR Orphan Ratio | 인바운드 0 & 90일 무조회 | < 20% | > 35% |
| CC Conflict Count | 미해결 모순 쌍 (절대값) | 0~2건 | ≥ 5건 |
| RSE Retrieval Staleness Exposure | 최근 100회 질의의 top-5 에 stale/superseded 문서가 1건 이상 포함된 비율 | < 10% | > 25% |
앞 4개는 선행지표고 RSE 만 결과지표다. 하나만 고르라면 RSE 다 — SR 이 15% 여도 그 문서들이 아무도 안 부르는 구석에 있으면 실질 피해는 0 이다. OR 목표를 0% 로 잡지 마라: Wikipedia 도 53,085편·128개월치 orphan 백로그를 안고 굴러간다. 큐는 비우는 게 아니라 늘지 않게 하는 것이다.
지치지 않는 배치 설계
- 주 1회 25분, 최대 12건 하드 상한. 큐가 40건이어도 12건만 뜬다(위 SQL 의
LIMIT 12). - 액션 3개만, "나중에" 버튼 없음:
확인(verified_at 갱신)/정정/감쇠표시. - 산수로 검증하라: 월 증가 40편 × 12개월 = 연 480편, ÷52주 ≈ 주 9.2편. 주 12건 상한 안에 들어온다. 월 증가가 100편을 넘으면 주 23편이 되어 25분에 불가능 → 그 시점부터 LLM 1차 트리아지 없이는 산술적으로 파산이다.
- 단, 반대 조건: 위키가 순수 로그형(회의록·실험기록)이면
stateful이 거의 없어 주간 배치 자체가 낭비다. 분기 1회로 낮춰라.
리뷰를 안 하고 버티는 전략의 한계
"리뷰 대신 검색에 recency decay 를 걸겠다"는 선택지는 실재하고, 실제로 절반은 듣는다. HoH 실험에서 검색기에 시간 감쇠를 넣자 낡은 passage 의 top-5 hit rate 가 88.37% → 49.50% 로 떨어졌다. 그러나 절반은 여전히 올라온다. recency decay 는 리뷰의 대체재가 아니라 감쇠재다. 위 확률표에서 p 를 절반으로 줄이는 효과일 뿐, 20% → 10% 는 여전히 top-5 오염 확률 41%다.
무리뷰 전략이 실제로 통하는 조건은 좁다: 문서 300편 미만 + stateful 비율 30% 미만 + 하루 질의 10건 미만. 셋 중 하나라도 깨지면 파산한다. 그리고 리뷰를 정말 안 할 생각이라면, 최소한 문서에 stateful/timeless 플래그 하나만은 붙여라. 이건 작성 시점에 2초 걸리고, 나중에 리뷰를 시작하기로 마음먹었을 때 큐를 만들 수 있는 유일한 재료다. 이 플래그가 없으면 3년 뒤 2,000편 앞에서 어디부터 볼지 정할 방법이 없다. (큐를 어느 Tier 에서 도입할지, 그래프층이 conflict 탐지를 얼마나 도와주는지는 → 다른 섹션에서 다룸)
마지막 반대 조건: 위 임계값은 전부 출발점이지 정답이 아니다. 특히 180일/90일은 어떤 논문도 개인 위키에 대해 검증한 바 없다 — RSE 를 4주 측정한 뒤 자기 데이터로 다시 정하라. 공개 자료 중 KB 리뷰 주기·중복 임계·모순 해소 케이던스에 대한 수치 권고는 사실상 존재하지 않는다(확인 필요 영역).
참고 출처
↗ HoH: A Dynamic Benchmark for Evaluating the Impact of Outdated Information on Retrieval-Augmented Generation (ACL 2025)↗ HoH (arXiv:2503.04800) — full text with per-model degradation figures↗ Contradictions in Context: Challenges for Retrieval-Augmented Generation in Healthcare (arXiv:2511.06668)↗ Temporal Validity in Retrieval Memory: Eliminating Stale-Fact Errors for AI Agents over Evolving Knowledge (MemStrata, arXiv:2606.26511)↗ Evaluating RAG Reliability under Clean, Misleading, and Mixed Retrieval (arXiv:2606.07783)↗ ConflictRAG: Detecting and Resolving Knowledge Conflicts in Retrieval Augmented Generation (arXiv:2605.17301)↗ Wikipedia:Orphan — orphan definition, backlog size, de-orphaning workflow↗ open-spaced-repetition/srs-benchmark — FSRS vs SM-2 benchmark (10k users, 727M reviews)↗ RAG Knowledge Base Freshness: The Staleness Problem Teams Solve Last규모별 스택 레시피 3종 — 500 / 5,000 / 팀
핵심 요점
- 세 레시피의 좌표를 숫자로 고정: A=문서 500 이하·75만 토큰·하루 5~15질의·월비용 $0·초기 2~4시간, B=문서 5,000·750만 토큰·하루 20~60질의·월 $0.2~$25·초기 16~30시간, C=문서 10,000+·동시 쓰기 주체 2+·상시 소유자 필요
- 손익분기 핵심 수치: 5,000문서(750만 토큰) 전량 임베딩이 text-embedding-3-small 기준 $0.15(Batch $0.075), 월 증분 200문서는 $0.006. Contextual Retrieval 전처리를 얹어도 1회 $7.65. 즉 개인 규모에서 API 비용은 의사결정 변수가 아니며 병목은 초기 공수와 주 1~2시간의 유지 공수다
- A의 구성은 .md+git+Claude Code 내장 ripgrep 검색+CLAUDE.md 동의어 사전이 전부이고 새로 짤 코드가 0줄이다. A→B 해금은 30일 연속 4개 조건 중 3개(문서 1,200건/총 200만 토큰, 1왕복 30k 토큰 또는 20초, 미검출률 20%, 월 증가 80건 3개월) 충족 시에만
- B의 실체는 SQLite FTS5(BM25)+sqlite-vec+RRF(k=60)+로컬 bge-reranker-v2-m3(0.6B, Apache-2.0)+MCP 툴 3개. 관리형 리랭커 전용 인스턴스는 시간당 $5=월 $3,250이라 개인 규모에서 논외이며, sqlite-vec가 pre-v1 breaking change 경고를 유지 중인 점이 이 스택의 실제 리스크다
- 각 레시피의 고유 사인: A는 검색 실패가 아니라 문서가 안 느는 것, B는 재인덱싱 실패가 예외 없이 지나가는 인덱스 드리프트(false green)와 유지 공수 부재, C는 실제 동시 쓰기 주체가 1명인데 지어버린 순수 부채
- C의 판단 기준은 사람 수가 아니라 동시 쓰기 주체 수다. 팀 5명이어도 읽기 전용 소비자가 4명이면 B+읽기 전용 뷰로 충분하고, 혼자여도 자율 에이전트 3개가 24시간 쓰기를 하면 그 순간부터 C다. 추가로 필요한 것은 Postgres+pgvector+ParadeDB pg_search, 쿼리 내장 ACL, 잡 큐, 골든셋 CI 회귀, 소유자 1명
이 섹션의 "개인 규모"는 항상 네 숫자로 고정한다: 문서 수 / 총 토큰 / 하루 질의 수 / 월 증가분. 아래 세 레시피는 이 네 좌표계 위의 세 점이며, 각각 정지 상태(steady state)로 기술한다. 점 사이를 옮기는 비용은 (→ 다음 섹션에서 다룸).
| A. 파일+에이전트 | B. 하이브리드+MCP | C. 팀/다중 에이전트 | |
|---|---|---|---|
| 문서 수 | ≤ 500 | 2,000 ~ 8,000 | 10,000+ |
| 총 토큰 | ≤ 75만 | 300만 ~ 1,200만 | 1,500만+ |
| 하루 질의 | 5 ~ 15 | 20 ~ 60 | 100+ (에이전트 자동 질의 500+) |
| 월 증가분 | 20 ~ 40건 | 100 ~ 300건 | 300건+ |
| 쓰기 주체 | 1 | 1 (+에이전트 1) | 2+ 동시 |
| 초기 공수 | 2 ~ 4시간 | 16 ~ 30시간 | B + 40 ~ 80시간 |
| 유지 공수 | 월 0 ~ 1시간 | 주 1 ~ 2시간 | 상시 소유자 필요 |
| 추가 월비용 | $0 | $0.2 ~ $25 | 인프라 + 인당 구독 |
레시피 A — 문서 500 이하, 1인: 만들지 않는 선택지
구성요소 (전부 실재 도구, 새로 짤 코드 0줄)
| 역할 | 도구 | 비고 |
|---|---|---|
| 원본 | .md 파일 + git |
단일 진실원본, 백업·이력 무료 |
| 편집 | 아무 에디터 (Obsidian/VS Code) | 볼트=그냥 폴더 |
| 검색 | Claude Code 내장 Grep/Glob/Read (ripgrep 기반) |
인덱스 0, 지연 0, 드리프트 0 |
| 검색 규약 | 리포 루트 CLAUDE.md 20줄 |
디렉토리 지도 + 파일명 규칙 + 동의어 사전 |
월비용 $0. Claude Pro는 연간 결제 시 $17/월(월납 $20), Max는 $100/월부터이며 세 티어 모두 Claude Code를 포함한다 — 이미 쓰고 있는 지출이고, 여기에 더할 것이 없다. 참고로 500문서(75만 토큰)를 전량 임베딩해도 text-embedding-3-small 기준 $0.015다. 즉 A에 머무는 이유는 돈이 아니라 인덱스를 유지할 공수가 0이기 때문이다.
한계 (측정 가능한 형태로)
- 어휘 불일치에 약하다. "롤백"으로 쓴 노트를 "revert"로 못 찾는다 →
CLAUDE.md에 동의어 5~15쌍을 손으로 박아 넣는 것이 이 계층의 유일한 방어책이다. - 후보가 넓어지면 에이전트가 파일 30~50개를 통째로 읽으며 컨텍스트를 태운다.
- PDF·이미지·스캔본은 grep이 못 읽는다. LlamaIndex 진영도 같은 지점을 한계로 지목한다(비정형 포맷 + 백만 단위 코퍼스).
이 레시피가 실패하는 방식
- 조용한 미검출. grep 0건이 "없음"이 아니라 "다르게 썼음"인데, 에이전트가 "관련 문서 없음"이라 단정한다. → 판정은 "0건"이 아니라 파일명 목록을 먼저 훑었는가로.
- 컨텍스트 폭발. 한 질의에 30k+ 토큰을 쓰기 시작하면 A는 이미 깨진 상태다.
- 진짜 사인(死因): 문서가 안 는다. 월 증가분이 10건 아래로 3개월 지속되면 검색층을 올려도 소용없다. 고칠 것은 검색이 아니라 캡처다.
반대 조건: 문서가 300건이어도 ①PDF/웹클리핑이 절반 이상이거나 ②하루 질의가 30건을 넘으면 A는 이미 부족하다. 반대로 문서 3,000건이어도 하루 질의가 2건 미만이면 올라가지 마라 — 인덱스가 죽는다.
A→B 해금 조건 (30일 연속, 4개 중 3개 충족 시에만)
- 문서 수 ≥ 1,200 또는 총 토큰 ≥ 200만
- 검색 1왕복 토큰 ≥ 30k 또는 지연 ≥ 20초
- "있는데 못 찾음" 비율 ≥ 20% (주 20질의 중 4건 이상 — 로그를 남겨서 세라)
- 월 증가분 ≥ 80건이 3개월 연속
레시피 B — 문서 5,000 내외, 1인: 마크다운 원본 + 하이브리드 + MCP
원본은 여전히 .md + git이다. 인덱스는 파생물이며 언제든 지우고 다시 만들 수 있어야 한다(선행글B의 3층 분리 결론을 그대로 승계).
| 역할 | 도구 | 근거 |
|---|---|---|
| 인덱스 | SQLite + FTS5(BM25) + sqlite-vec | 파일 1개, 백업=cp. 단 sqlite-vec는 pre-v1, breaking change 경고 유지 중 |
| 융합 | RRF, 1/(k+rank), k=60 |
점수 정규화 문제를 순위 차원에서 우회 |
| 임베딩 | text-embedding-3-small($0.02/1M, Batch $0.01/1M) 또는 로컬 FastEmbed(ONNX, bge-small-en-v1.5, 384차원, GPU 불필요) |
원가가 무의미 → 선택 기준은 지연·프라이버시 |
| 리랭킹 | BAAI/bge-reranker-v2-m3 (0.6B, Apache-2.0, FlagEmbedding) 로컬 | 관리형 리랭커 전용 인스턴스는 시간당 $5 = 월 $3,250 — 개인 규모에서 논외 |
| MCP | 자작 FastMCP 서버, 또는 Obsidian Local REST API(내장 /mcp/, 기본 27124 HTTPS), 또는 Basic Memory(마크다운 원본+SQLite FTS+FastEmbed+선택적 cross-encoder, AGPL-3.0) |
툴은 search/read/write 3개로 제한 |
| 캡처 | Obsidian Web Clipper(무료) / Karakeep(구 Hoarder, AGPL-3.0, Meilisearch, ollama 가능) / Readwise $9.99·월납 $12.99 | 마찰 제거가 이 레시피의 절반 |
-- 하이브리드 검색의 전부. 튜닝 손잡이는 k와 두 가중치뿐이다.
WITH kw AS (
SELECT doc_id, ROW_NUMBER() OVER (ORDER BY bm25(chunks_fts)) AS r
FROM chunks_fts WHERE chunks_fts MATCH :q LIMIT 60
), vec AS (
SELECT doc_id, ROW_NUMBER() OVER (ORDER BY distance) AS r
FROM chunks_vec WHERE embedding MATCH :qvec AND k = 60
)
SELECT COALESCE(kw.doc_id, vec.doc_id) AS doc_id,
1.0 * COALESCE(1.0/(60 + kw.r), 0)
+ 1.0 * COALESCE(1.0/(60 + vec.r), 0) AS rrf
FROM kw FULL OUTER JOIN vec USING (doc_id)
ORDER BY rrf DESC LIMIT 20; -- 이후 상위 20건만 로컬 리랭커로 재정렬
월비용 실측 계산 (5,000문서 × 평균 1,500토큰 = 750만 토큰)
| 항목 | 1회 | 월 (증분 200문서 = 30만 토큰) |
|---|---|---|
| 임베딩 (3-small) | $0.15 (Batch $0.075) | $0.006 |
| Contextual Retrieval 전처리 옵션 | $7.65 ($1.02/1M doc tokens) | $0.31 |
| 인덱스 호스팅 (로컬 SQLite) | $0 | $0 |
| 리랭커 (로컬) | $0 | $0 |
| 캡처 SaaS (선택) | — | $0 ~ $12.99 |
| 합계 | < $10 | $0.2 ~ $25 |
이 표가 이 글의 손익분기 수치다. 5,000문서 위키의 인덱스 전량 재구축이 커피 한 잔의 1/20이다. 따라서 B로 올라갈지 말지의 판단에서 API 비용은 변수가 아니다. 변수는 초기 16~30시간(인덱서 6~10h, 하이브리드 쿼리 4~6h, MCP 3~5h, 캡처 자동화 3~8h)과 주 1~2시간의 유지 공수뿐이다.
Contextual Retrieval 주의: Anthropic 실험은 top-20 검색 실패율을 5.7%→3.7%(−35%), BM25 결합 시 2.9%(−49%), 리랭킹까지 1.9%(−67%)로 줄였다. 다만 이는 그들의 코퍼스 기준이며 개인 한국어 위키 5,000건에서 같은 폭이 재현된다는 보장은 없다 — 20문항 골든셋으로 직접 측정해서 정하라(골든셋 구성법은 선행글B).
이 레시피가 실패하는 방식
- 인덱스 드리프트 = false green. 파일을 고쳤는데 재인덱싱이 조용히 실패해 옛 청크를 반환한다. 에러가 없어서 몇 주를 모른다. → 인덱서에
max(mtime)대조를 넣고 드리프트 건수를 매일 한 줄로 출력하라. - 임베딩 모델 교체 = 전량 재임베딩 + 차원 변경 = 스키마 마이그레이션. 차원을 컬럼이 아니라 테이블명에 박아두면 두 판을 병렬로 굴릴 수 있다.
- 청킹이 마크다운 구조를 무시해 표·코드블록이 반토막 난다. 헤딩 경계 우선 분할이 아니면 기술 노트에서 특히 크게 깨진다.
- MCP 툴 과다. 툴 12개를 노출하면 에이전트가 엉뚱한 툴을 고른다. 3개로 시작하라.
- 사인: 주 1시간이 안 나온다. 그러면 6주 만에 인덱스가 죽고, 남는 것은 A보다 느린 A다.
반대 조건: 노트 대부분이 짧은 일지·회의록이라 어휘가 자기 안에서 반복되면 하이브리드 이득이 작다. 이때는 B 대신 A + 파일명 규약 + 수작업 백링크(→ 대안 비교 섹션)가 비용 대비 낫다. 반대로 문서가 2,000건이어도 PDF/영한 혼용/외부 클리핑이 40% 이상이면 곧장 B다.
B→C 해금 조건 (2개 이상)
- 동시 쓰기 주체 ≥ 2 (사람이든 에이전트든 무관)
- 하루 질의 ≥ 100, 또는 에이전트 자동 질의 ≥ 500/일
- 권한 경계 필요 (고객 자료·NDA·개인정보가 같은 볼트에 혼재)
- 전량 재인덱싱 시간 ≥ 10분, 또는 인덱스 파일 ≥ 2GB
레시피 C — 팀/다중 에이전트: 선행글B 아키텍처로 넘어가는 지점
C는 새 설계가 아니다. 선행글B의 3층 아키텍처(원본 / 관계·메타 / 파생 인덱스)를 그대로 채택하는 것이 C의 정의다. 여기서는 B 대비 추가로 필요한 것만 적는다.
| 추가 항목 | 실체 | 왜 B로는 안 되나 |
|---|---|---|
| 인덱스 저장소 이전 | SQLite → PostgreSQL + pgvector, BM25는 ParadeDB pg_search(AGPL-3.0, 현재 벡터는 pgvector 사용, 네이티브 하이브리드는 로드맵) |
동시 쓰기에서 SQLite 단일 라이터가 병목 |
| 권한 경계 | 문서 단위 ACL을 검색 쿼리 자체에 (후처리 필터 금지) | 후처리 필터는 순위·개수를 통해 존재를 흘린다 |
| 동시성 | 인덱싱 잡 큐 + 문서 단위 리스 | 에이전트 2개가 같은 노트를 덮어씀 |
| 관측 | 질의 로그 + 골든셋 회귀를 CI에서 | 품질 회귀는 예외를 던지지 않는다 |
| 소유자 | 위키 운영 담당 1명 (겸임 가능) | 주인 없는 인덱스는 반드시 낡는다 |
추가 월비용: 관리형 Postgres 최소 인스턴스(공급자별 확인 필요) + 인당 Claude 구독($17~20, Max는 $100~) + 선택적 관리형 지식 백엔드(예: Basic Memory Cloud $15/월). 인프라비보다 사람 시간이 훨씬 비싸다.
이 레시피가 실패하는 방식
- 권한 유출. 검색층이 ACL을 모르면 "요약"이 원문보다 먼저 샌다.
- 에이전트 쓰기 폭주. 자동 저장을 켠 순간 저품질 노트가 하루 수백 건 쌓여 검색 정밀도가 떨어진다. → 에이전트 쓰기는 초안 영역 격리 + 사람 승격이 기본값.
- 평가 부재. 임베딩 모델을 바꾸고 "좋아진 것 같다"로 넘어가면 6개월 뒤 아무도 원인을 못 찾는다.
- 사인: 팀이 아직 아닌데 C를 지었다. 실제 동시 쓰기 주체가 1명이면 C의 모든 추가분은 순수 부채다.
반대 조건: 팀이 5명이어도 읽기 전용 소비자가 4명이면 C가 아니라 B + 읽기 전용 웹 뷰로 충분하다. 반대로 혼자여도 자율 에이전트 3개가 24시간 쓰기를 하면 그 순간부터 C다 — 사람 수가 아니라 동시 쓰기 주체 수가 기준이다.
참고 출처
↗ Introducing Contextual Retrieval — Anthropic↗ asg017/sqlite-vec — A vector search SQLite extension (pre-v1)↗ Hybrid full-text search and vector search with SQLite — Simon Willison↗ coddingtonbear/obsidian-local-rest-api — REST API + MCP server for an Obsidian vault↗ basicmachines-co/basic-memory — Markdown-first knowledge base with SQLite FTS, FastEmbed and MCP↗ qdrant/FastEmbed — lightweight local ONNX embedding library↗ BAAI/bge-reranker-v2-m3 — multilingual cross-encoder reranker (Apache-2.0)↗ paradedb/paradedb — pg_search: BM25 full-text search inside Postgres↗ karakeep-app/karakeep — self-hostable bookmark-everything app (Meilisearch, ollama)↗ Is grep all you need? Lexical vs semantic search for agents — LlamaIndex↗ text-embedding-3-small model — OpenAI API docs↗ Claude plan pricing (Free / Pro / Max)↗ Readwise pricing↗ Cohere pricing (Rerank / Embed model instances)↗ Maximum Number of Notes in Vault — Obsidian Forum사다리를 오르내리는 법 — 되돌릴 수 없는 결정만 1일차에
핵심 요점
- 승급에서 깨지는 건 인덱스가 아니라 주소다 — 인덱스·임베딩·그래프는 전부 재생성 가능한 파생물이지만, 식별자·경로·링크 표기는 원문에 박혀 있어 재생성으로 복구되지 않는다. Obsidian 이 링크를 파일명으로 해석한다는 점이 T1→T2 락인의 실제 발생 지점이다.
- 1일차에 못 박을 one-way door 는 다섯 개뿐: ①원문은 로컬 평문 Markdown+git ②frontmatter 에 불변 id(파일명·제목 금지) ③2단계 이내 얕은 배치 ④frontmatter 6필드 ⑤id 기반 링크 표기. 총 소요는 30분 수준이다.
- Tier 1 전체는 $10 미만으로 갈아엎을 수 있다 — 600만 토큰 재임베딩이 text-embedding-3-small $0.12 / 3-large $0.78, contextual retrieval LLM 전처리도 $6.12(Anthropic 실측 $1.02/M토큰). 이 층에서 아키텍처 회의를 하는 건 순손실이다.
- 승급 절차는 세 구간 모두 동일하다: 새 이름의 스키마에 병행 생성 → 질의 20~30건 대조 → 스위치 → 2주 뒤 구 경로 삭제. 제자리 마이그레이션을 하는 순간 롤백이 사라진다. 단 T0→T1 은 재생성이 수십 분이라 병행 운영 자체가 과잉이다.
- T2→T3 만 롤백이 아픈 이유는 LLM 엔티티 추출이 비결정적이기 때문 — 그래프를 사람이 손으로 고치는 순간 그래프가 두 번째 원문이 되어 재생성이 불가능해진다.
- 내려오기의 유일한 전제는 '모든 파생 계층이 원문에서 1커맨드로 재생성 가능'이며, 이는 주장이 아니라 분기 1회 drop-and-rebuild 드릴로 증명해야 한다. 바닥까지 내려가도 ripgrep(13GB/1.042초)과 45만 토큰 이하 전량 컨텍스트 투입이 남는다.
이 글에서 "개인 규모" 는 문서 300~3,000편 · 편당 1,500~2,000토큰(총 45만~600만 토큰, 텍스트 30MB 안팎) · 하루 질의 5~50회 · 월 증가 20~80편 으로 고정한다. 아래 수치는 전부 이 밴드 기준이다.
승급에서 깨지는 것은 검색기가 아니라 주소다
Tier 를 올릴 때 다들 "인덱스를 다시 만들어야 하는 것" 을 겁내는데, 그건 안 깨진다. 인덱스는 원래 파생물이고 재생성이 정상 동작이다. 실제로 깨지는 건 문서를 가리키는 방법 — 식별자, 경로, 링크 — 이고, 이건 파생물이 아니라 원문에 박혀 있어서 재생성으로 복구되지 않는다. Bezos 의 one-way / two-way door 분류를 그대로 쓰면, 사다리에서 one-way door 는 딱 다섯 개다.
| 승급 구간 | 재생성으로 끝나는 것 (two-way) | 원문을 건드려야 하는 것 (one-way) |
|---|---|---|
| T0 → T1 (파일+grep → 하이브리드) | 청킹, 임베딩, BM25/tsvector 인덱스 전부 | 청크 경계가 문서마다 제멋대로면 heading 구조를 소급 삽입해야 함 |
| T1 → T2 (관계층) | 백링크 테이블, 인접 문서 캐시 | 링크 표기가 파일명 기반이면 전 문서 리라이트 |
| T2 → T3 (그래프) | 엔티티·관계·커뮤니티 요약 전부 | 문서 단위가 흔들리면(한 파일에 주제 5개) 엔티티가 문서에 안 붙음 → 문서 분할 = 수작업 |
T1→T2 행이 이 섹션의 핵심이다. Obsidian 은 [[Note name]] 을 파일명으로 해석하고 리네임 시 볼트 전체의 링크를 자동 갱신한다(문서). 볼트 안에서만 살면 편하지만, 그 순간 제목이 곧 식별자가 된다. 외부 인덱스·DB·MCP 툴이 경로나 제목을 키로 잡고 있으면 리네임 한 번에 전부 스테일이 되고, Obsidian 은 자기 밖의 참조를 갱신해 주지 않는다.
1일차에 못 박을 다섯 가지
| # | 결정 | 권고 | 나중에 바꾸는 비용 | 단, 반대 조건 |
|---|---|---|---|---|
| 1 | 원문의 물리적 위치·형식 | 로컬 평문 Markdown + git. DB·벡터스토어는 전부 파생 | 최고. 원문이 DB 에 있으면 export 품질이 곧 천장 | 유입의 절대다수가 모바일 클리핑·다인 동시편집이면 DB-first 가 낫다. 파일은 동시성 모델이 없다 |
| 2 | 문서 식별자 | frontmatter 에 불변 id(ULID 또는 20260811T1432). 파일명·제목·경로를 id 로 쓰지 말 것 |
최고. 소급 부여는 가능하지만 기존 링크를 다 못 고침 | 문서가 300편 미만이고 영원히 혼자 쓸 거면 파일명 id 로도 산다. 단 외부 인덱스를 붙이는 순간 후회 |
| 3 | 파일 배치 규칙 | 얕게. notes/YYYY/ 2단계까지. 폴더에 의미를 담지 말 것 |
중간. 이동 자체는 git mv 지만 폴더가 분류였다면 이동 = 데이터 손실 |
법적·보안 격리(공개/비공개)가 필요하면 폴더가 경계여야 한다. 이건 태그로 대체 불가 |
| 4 | frontmatter 최소 필드 | id, title, created, updated, tags, source 6개. 그 이상은 나중에 |
낮음(추가) / 높음(소급 채우기). 3,000편 소급은 LLM 을 써도 검수가 남는다 | 필드를 늘리는 게 아니라 처음부터 안 쓰는 것이 더 자주 옳다. status·type 은 실제 질의에 쓰이기 전엔 넣지 마라 |
| 5 | 링크 표기 | 하나만 골라 고정하고, 링크 대상은 id | 높음. 표기 혼재는 파서 분기를 영구히 남긴다 | Obsidian 그래프뷰·자동완성을 매일 쓴다면 wikilink 를 포기하기 어렵다 → id 를 파일명 prefix 로 넣어 둘 다 만족시켜라(20260811T1432-ladder.md) |
---
id: 01JZ8Q3M4K7N2P9R # 불변. 파일이 어디로 가든 안 바뀜
title: 사다리를 오르내리는 법 # 얼마든지 바뀜
created: 2026-08-11
updated: 2026-08-11
tags: [rag, second-brain]
source: self # self | web | paper | chat
---
관련: [[01JZ8Q3M4K7N2P9R|링크는 id 로, 표시는 제목으로]]
나중에 바꿔도 싼 결정 — 얼마나 싼지 숫자로
600만 토큰 전량 재계산 기준 실측 단가(OpenAI 가격표):
| 결정 | 전면 교체 비용 | 소요 시간 | 판정 |
|---|---|---|---|
| 임베딩 모델 | text-embedding-3-small $0.12 / 3-large $0.78 |
배치 수십 분 | 실질 무료. 고민하지 마라 |
| 검색 DB | Postgres 는 tsvector+ts_rank+GIN 이 내장이라 추가 인프라 0 |
스키마 1개 | 이미 Postgres 를 쓴다면 Tier 1 의 어휘 검색 절반은 공짜 |
| 벡터 인덱스 | pgvector HNSW 재구축 | 600만 토큰이면 분 단위 | 싸다. 단 vector 타입 HNSW 는 2,000차원까지 — 3072차원 모델을 고르면 halfvec(4,000) 로 가야 한다(pgvector) |
| 검색 엔진/랭커 | 코드 교체 | 반나절 | 싸다 |
| UI | 전면 재작성 | 며칠 | 싸다. 원문이 파일이면 UI 는 뷰어일 뿐 |
| LLM 전처리(contextual retrieval) | $6.12 (Anthropic 실측 $1.02/M토큰, 프롬프트 캐싱 적용) | 시간 단위 | 임베딩의 50배지만 여전히 커피 두 잔 |
즉 Tier 1 의 모든 구성요소는 $10 미만으로 통째로 갈아엎을 수 있다. 이 층에서 아키텍처 회의를 하는 건 순손실이다. 단, 반대 조건: 임베딩 모델을 바꿀 때 저장된 벡터와 신규 벡터를 섞어 쓰면 안 된다. 부분 재계산은 예외 없이 조용히 틀린 결과를 낸다 — 전량 재계산이 아니면 아예 하지 마라.
승급 3구간의 실제 공수
| 구간 | 공수 | 새 인프라 | 멱등 재실행 | 롤백 |
|---|---|---|---|---|
| T0→T1 | 반나절~1일 | Postgres 1개(FTS+pgvector) 또는 파일 인덱스 | O | DROP SCHEMA — T0 로 즉시 복귀 |
| T1→T2 | 1~2일 | 없음(테이블 2개) | O — 링크가 id 기반일 때만 | 테이블 드롭. 원문의 링크 표기는 남음 |
| T2→T3 | 3일~2주 | 그래프 스토어 또는 임베디드 그래프 | △ — LLM 추출은 비결정적 | 여기서만 롤백이 아프다(아래) |
승급 절차는 셋 다 같은 형태다: ① 원문에서 파생 산출물을 새 이름의 스키마/디렉터리로 생성 → ② 기존 경로와 병행 운영하며 질의 20~30건 대조 → ③ 스위치 → ④ 2주 뒤 구 경로 삭제. 새 스키마를 옆에 짓지 않고 제자리 마이그레이션을 하는 순간 롤백이 사라진다. 단, 반대 조건: T0→T1 처럼 전량 재생성이 수십 분이면 병행 운영 자체가 과잉이다 — 그냥 지우고 다시 만들어라.
T2→T3 만 성격이 다르다. 그래프 계층은 LLM 추출이 비결정적이라 같은 원문으로 두 번 돌려도 엔티티 집합이 달라진다. 그래서 "그래프에서 사람이 손으로 고친 것" 이 생기는 순간 그래프가 원문이 되어버린다. 이게 락인의 진짜 발생 지점이다.
내려오는 경로
그래프를 걷어낼 때 잃는 것과 남는 것:
| 잃는 것 | 남는 것 | |
|---|---|---|
| T3 → T2 | 다중 홉 연결, 커뮤니티 요약(RAPTOR 계열이 QuALITY 에서 절대 정확도 +20%p 를 보고한 그 능력) | 명시적 백링크, 태그, 문서 단위 검색 |
| T2 → T1 | 이웃 확장, "이것과 관련된 것" 질의 | 하이브리드 검색 — 사실 조회의 대부분 |
| T1 → T0 | 의미 검색, 동의어 매칭 | 30MB 를 수 ms 에 훑는 grep. ripgrep 은 13GB 단일 파일을 1.042초에 스캔한다(벤치마크) — 30MB 면 선형 외삽으로 한 자릿수 ms |
| T0 → 없음 | 검색 자체 | 45만 토큰 이하면 전량을 컨텍스트에 넣는 게 정답(Anthropic: 20만 토큰 미만이면 RAG 불필요) |
내려오기가 가능하려면 지켰어야 하는 것은 하나뿐이다: 모든 파생 계층이 원문에서 1커맨드로 재생성 가능해야 한다. 이걸 문서로 주장하지 말고 분기에 한 번 실제로 실행해서 증명하라 — 재생성 가능하다고 믿는 파이프라인의 대부분은 아니다.
# 하강 드릴: 분기 1회. 실패하면 그게 락인의 증거다
git -C ~/wiki status --porcelain | grep -q . && { echo "원문 미커밋"; exit 1; }
psql -c 'DROP SCHEMA wiki_derived CASCADE' # 파생물 전멸
rm -rf ~/wiki/.graph ~/wiki/.index
make reindex # 원문만으로 재건
python eval_queries.py --baseline before.json # 질의 20건 회귀 대조
단, 반대 조건: 그래프에 원문에 없는 정보를 의도적으로 넣고 있다면(수작업 관계 큐레이션, Graphiti 류의 시간 구간 무효화 같은 상태) 그건 파생물이 아니라 두 번째 원문이다. 그때는 드릴을 돌릴 게 아니라 그 계층을 별도 원문으로 승격시켜 git 에 넣어라. 어느 쪽이든 "재생성 불가능한 상태가 인덱스 안에만 존재" 하는 상황만은 만들지 마라.
락인을 만드는 흔한 실수
| 실수 | 왜 치명적인가 | 진단법 |
|---|---|---|
| 제목·파일명을 식별자로 사용 | 리네임 = 외부 참조 전멸. Obsidian 은 볼트 밖을 안 고쳐준다 | 아무 문서나 리네임하고 인덱스 조회 → 404 나오면 감염 |
| 원문을 벡터 DB / 그래프 DB 에만 저장 | export 품질이 곧 천장이 됨(선행글의 결론 그대로) | "이 DB 를 지금 지워도 되는가?" 에 즉답 못 하면 감염 |
| 그래프를 손으로 편집 | 비결정적 산출물이 원문이 됨 → 재생성 불가 | 재추출 후 diff. 사람 편집분이 사라지면 감염 |
| 폴더 구조에 의미를 인코딩 | 재분류가 마이그레이션이 됨 | 폴더 정보를 태그로 전부 복제할 수 있는가? |
| frontmatter 를 미리 20필드 설계 | 대부분 안 쓰이고, 안 쓰이는 필드는 썩는다 | 실제 질의에서 참조되는 필드 수를 세라. 6개를 넘으면 과설계 |
| 프레임워크의 청킹 규칙에 문서를 맞춤 | 프레임워크 교체가 원문 재작성이 됨 | 청킹은 파이프라인 안에서만 일어나야 한다 |
마지막 항목의 반대 조건: heading 구조를 읽는 사람 기준으로 정돈하는 건 청킹에 맞추는 게 아니라 글을 잘 쓰는 것이다. 이건 어느 Tier 에서도 이득이고, T0 에서도 grep 결과의 가독성을 올린다.
정리하면 1일차에 들일 노력은 위 다섯 결정에 대한 30분과 id 필드 하나가 전부다. 나머지 전부 — 임베딩 모델, DB, 랭커, UI, 그래프 프레임워크(LightRAG·HippoRAG·Graphiti 모두 MIT/Apache-2.0 이고 셋 다 원문으로부터의 재구축을 전제로 설계돼 있다) — 는 $10 과 하루로 뒤집힌다. 어느 계층을 언제 올릴지의 정량 트리거와 대안 간 정면 비교는 (→ 다른 섹션에서 다룸).
참고 출처
↗ Introducing Contextual Retrieval — Anthropic↗ Internal links — Obsidian Help↗ pgvector — open-source vector similarity search for Postgres↗ PostgreSQL Documentation: GIN and GiST Index Types for Text Search↗ OpenAI API Pricing (embedding models)↗ LightRAG: Simple and Fast Retrieval-Augmented Generation (HKUDS)↗ Graphiti — temporal knowledge graphs for AI agents (getzep)↗ HippoRAG 2 — OSU NLP Group↗ RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval (arXiv:2401.18059)↗ ripgrep — benchmarks (BurntSushi)↗ 2016 Letter to Shareholders — Type 1 / Type 2 decisions (one-way and two-way doors)착수 체크리스트와 함정 10종
핵심 요점
- 시간 예산을 산출물보다 먼저 고정한다: 첫날 90분(문서 3편 + 실제 질문 1건 답변, 인덱스 0개), 첫주 5시간(7일 중 5일 캡처), 첫달 15시간(30일 중 20일 캡처, miss 라벨 20건). 예산 초과 자체가 실패 신호다.
- 1순위 판정은 검색 품질이 아니라 '매일 실제로 쓰는가'다. 습관 자동화에 평균 66일(약 10주)이 걸린다는 연구를 근거로 합격선을 '주 5일 / 월 20일'로 잡고, 30일 판정 2회 연속 불합격이면 '만들지 않는 선택'이 정답이다.
- 첫달까지 인덱스가 없는 것은 근거 있는 선택이다. Anthropic은 20만 토큰(약 500페이지) 미만이면 RAG를 건너뛰라고 명시하고, pgvector도 기본이 exact 검색(재현율 100%)이며 작은 테이블에선 테이블 스캔이 더 빠를 수 있다고 문서화한다.
- 함정 10종(도구 쇼핑/분류체계 먼저/인덱스 먼저/자동화 먼저/그래프 시각화/전부 자동 승격/캡처 없이 검색만/마이그레이션 반복/남의 볼트 따라하기/측정 없이 튜닝)마다 측정 가능한 조기 경보 신호를 하나씩 지정했다 — 예: '폴더+태그 수 > 문서 수', '크론 수 > 주간 신규 문서 수', '그래프 화면에서 답을 찾은 횟수 0'.
- RAPTOR의 QuALITY +20%p, HippoRAG의 멀티홉 최대 20%·10~30배 저렴, Zep의 LongMemEval 최대 +18.5%·지연 90% 감소는 모두 멀티홉 QA 벤치마크 수치이지 개인 300편 볼트의 수치가 아니다. 자기 miss 비율 없이 이 숫자로 튜닝하면 근거가 없다.
- 10개 권고 전부에 반대 조건을 명시했다 — 300만 토큰 존량 임포트면 1일차 인덱스가 맞고, 녹취·로그는 자동화가 선행하며, 시각화가 산출물인 경우엔 그래프가 제품이고, 문서 50편 미만이면 측정조차 과잉이다.
첫날 90분 / 첫주 5시간 / 첫달 15시간
착수의 성패는 설계가 아니라 시간 예산이 가른다. 예산을 넘긴다는 것은 곧 "문서를 늘리는 일"이 아닌 다른 일을 하고 있다는 뜻이므로, 예산 초과 자체를 실패 신호로 취급한다. 아래 산출물 번호는 순서대로 처리한다 — 건너뛰기 금지.
| 구간 | 시간 예산 | 산출물 | 완료 정의(DoD) |
|---|---|---|---|
| 첫날 | 90분 | ① 평문 마크다운 디렉토리 1개 + git init② 5초 이내로 끝나는 캡처 명령 1개 ③ 에이전트가 그 디렉토리를 grep/glob으로 읽는 상태 |
오늘 실제로 생긴 질문 1건을 그 디렉토리만으로 답했다. 문서 3편. DB·임베딩·인덱스 0개 |
| 첫주 | 누적 5시간 | ④ 캡처 경로 2개(터미널 + 폰) ⑤ 프론트매터 4필드 고정( id/date/tags/source)⑥ 하루 1줄 지표 로그 |
7일 중 5일에 캡처 발생. 누적 25~40편(≈3만~5만 토큰). 질의 로그 10건. 폴더 depth ≤ 2, 태그 총량 ≤ 12 |
| 첫달 | 누적 15시간 | ⑦ 실패 질의 라벨(miss)⑧ 30일 지표 1장 ⑨ Tier 1 해금 판정서 1장 |
30일 중 20일 캡처. 누적 100~150편(10만~20만 토큰). miss 라벨 20건 이상. 트리거 미충족이면 "올리지 않는다"를 문서로 남긴다 |
첫달까지 인덱스가 없는 것은 게으름이 아니라 근거 있는 선택이다. Anthropic은 지식베이스가 20만 토큰(약 500페이지) 미만이면 RAG를 건너뛰고 통째로 프롬프트에 넣으라고 명시하며, pgvector 역시 기본이 exact 검색(재현율 100%)이고 작은 테이블에선 인덱스보다 테이블 스캔이 빠를 수 있다고 문서화한다. 즉 문서 150편 규모에서 벡터 인덱스는 성능 개선이 아니라 운영 부채다. (해금 트리거의 정량 정의 → 사다리 섹션에서 다룸)
단, 반대인 경우: 이미 완결된 아카이브 2,000편·300만 토큰 이상을 1회 임포트하는 상황이라면 첫날부터 인덱스가 맞다. 증분이 아니라 존량(存量)이 문제인 케이스다.
판정은 사실상 하나뿐이다
"검색 품질이 좋은가"는 첫 90일의 판정 기준이 아니다. 유일한 1순위 판정은 매일 실제로 쓰는가다. 습관 연구(Lally et al.)에서 행동이 자동화되기까지 평균 약 **66일(≈10주)**이 걸리고, 하루를 빼먹는 것 자체는 치명적이지 않지만 전반적 불규칙은 습관 형성을 실패시킨다. 그래서 판정선을 "매일"이 아니라 "주 5일 / 월 20일"로 잡는다.
| 시점 | 1순위 판정(합격선) | 2순위 | 불합격 시 조치 |
|---|---|---|---|
| 첫날 | 볼트로 실제 질문 1건 답변 | 문서 3편 | 도구를 버리고 기존 에디터 + grep으로 원복 |
| 첫주 | 7일 중 5일 캡처 | 25~40편 / 질의 10건 | 캡처를 5초로 줄이는 일 외 전부 중단 |
| 첫달 | 30일 중 20일 캡처 | 100~150편 / miss 20건 |
사다리 상승 금지, 30일 재시도 |
| 3개월 | 66일 지점 통과(자동화) | 총 20만 토큰 돌파 | 만들지 않는 선택 — 30일 판정 2회 연속 불합격이면 종료가 정답 |
#!/usr/bin/env bash
# vault-stat.sh — 하루 1회. 이 로그가 없으면 이후 모든 튜닝은 근거가 없다.
V=~/vault; D=$(date +%F); mkdir -p "$V/.metrics"
DOCS=$(find "$V" -name '*.md' | wc -l | tr -d ' ')
WORDS=$(find "$V" -name '*.md' -exec cat {} + | wc -w | tr -d ' ')
NEW=$(find "$V" -name '*.md' -newermt "-1 day" | wc -l | tr -d ' ')
Q=$(grep -c "^$D q" "$V/.metrics/q.log" 2>/dev/null || echo 0)
MISS=$(grep -c "^$D q miss" "$V/.metrics/q.log" 2>/dev/null || echo 0)
# 질의는 세션에서 한 줄 append: echo "$(date +%F) q miss <질문>" >> ~/vault/.metrics/q.log
echo "$D docs=$DOCS tok~=$((WORDS*3/2)) new=$NEW q=$Q miss=$MISS" \
>> "$V/.metrics/daily.log"
# 해금 판정: tok~ > 200000 AND 최근 30일 miss/q >= 0.20 → 그때 비로소 Tier 1
함정 10종 — 조기 경보와 탈출법
| # | 함정 | 조기 경보 신호(측정 가능) | 탈출법 |
|---|---|---|---|
| 1 | 도구 쇼핑 | 착수 3일째 문서 0편인데 비교 탭 5개 이상 | 90분 타이머. 도구는 "이미 매일 쓰는 것"으로 강제 고정(파일시스템 + 에디터 + 코딩 에이전트) |
| 2 | 완벽한 분류체계 먼저 | 폴더+태그 수 > 문서 수 | 태그 상한 12, depth 2, 기본값은 무조건 inbox/. 분류는 100편 넘긴 뒤 사후 정리 |
| 3 | 인덱스 먼저 | 총 20만 토큰 미만인데 임베딩 파이프라인 착수 | 2주간 grep + 에이전트로 버티고 실패 질의만 라벨링. 인덱스는 실패율이 산 근거를 만든 뒤 |
| 4 | 자동화 먼저 | 크론/훅 수 > 주간 신규 문서 수, 파이프라인 디버깅 시간 > 글쓰기 시간 | 같은 수작업 20회 반복 후에만 자동화. 그 전엔 손으로 붙여넣는다 |
| 5 | 그래프 시각화 | 그래프 화면 스크린샷은 있는데 그 화면에서 답을 찾은 횟수 0 | 그래프 뷰는 공식 문서상으로도 검색 기제가 아니라 시각화다. 30일 사용 금지, 링크는 "답변에 실제 인용된 문서쌍"만 생성 |
| 6 | 전부 자동 승격 | 자동 생성 문서 비율 > 60%인데 검색 상위 10건 중 8건이 사람이 쓴 문서 | 자동 산출물은 auto/ 네임스페이스로 격리, 승격은 사람 1클릭 (점수식 → 선행글) |
| 7 | 캡처 없이 검색만 | 주간 신규 문서 < 3편인데 검색 코드는 계속 증가 | 검색 작업 전면 동결. 캡처가 5초를 넘으면 그 경로부터 고친다. 실패는 검색 품질이 아니라 문서 미증가에서 온다 |
| 8 | 마이그레이션 반복 | 12개월에 도구 2회 이상 교체, 링크 깨짐 발생 | 원본은 평문 마크다운 + git으로 동결, 나머지는 전부 재생성 가능한 파생물로. 이관 비용을 0으로 만들면 교체 충동이 무해해진다 |
| 9 | 남의 볼트 따라하기 | 템플릿 필드 중 30일간 한 번도 채워지지 않은 필드가 절반 이상 | 30일간 실제로 채워진 필드만 남기고 삭제. 남의 볼트는 그 사람의 질의 분포에 최적화된 것이다 |
| 10 | 측정 없이 튜닝 | daily.log 없이 "체감상 좋아졌다"만 존재 |
실패 질의 20건을 고정하고 변경 전후 동일 질의로 비교. RAPTOR의 QuALITY +20%p, HippoRAG의 멀티홉 최대 20%·10~30배 저렴, Zep의 LongMemEval 최대 +18.5%·지연 90% 감소는 전부 멀티홉 QA 벤치마크 수치이지 당신의 300편 볼트 수치가 아니다 (대안 비교 → 다른 섹션) |
각 함정의 반대 조건
권고는 전부 개인 규모(문서 300~3,000편, 총 40만~400만 토큰, 하루 질의 3~30건, 월 증가 20~80편) 전제다. 아래에 해당하면 위 표를 뒤집어라.
- 1↔ 3인 이상 공유가 요구사항이면 도구 선택이 실제 제약이다. 도구 비교에 하루를 써라.
- 2↔ 계약·의료·감사 대상 문서는 분류가 산출물 자체다. 먼저 정의하라.
- 3↔ 존량 임포트가 300만 토큰 이상이면 1일차 인덱스가 맞다.
- 4↔ 회의 녹취·로그처럼 자동 수집 없이는 물리적으로 캡처가 불가능한 소스는 자동화가 선행한다.
- 5↔ 그림이 곧 산출물인 경우(발표·세일즈·디지털 가든)엔 시각화가 제품이다.
- 6↔ 커밋 로그·이슈처럼 이미 구조가 검증된 소스는 자동 승격이 맞다.
- 7↔ 논문 500편처럼 완결된 코퍼스를 읽기만 한다면 캡처 경로는 불필요하다.
- 8↔ 원본이 평문으로 표현 불가한 형태(오디오·이미지 중심)면 평문 동결 전략이 성립하지 않는다.
- 9↔ 팀 표준·규제 템플릿은 미사용 필드라도 남긴다.
- 10↔ 아직 문서 50편 미만이면 측정도 과잉이다. 그냥 써라.
전체를 관통하는 원칙은 Anthropic이 컨텍스트 엔지니어링 가이드에서 말한 그대로다 — 에이전트에게 grep/glob과 파일 경로만 쥐여주는 just-in-time 탐색이 사전 인덱싱보다 느리지만, "가장 단순한 것부터 한다"가 여전히 최선이다. 사전 인덱싱이 언제 이득으로 바뀌는지에 대한 보편 임계값은 그들도 제시하지 않는다. 당신의 miss 비율만이 그 답을 안다.
지금 당장 한 가지만 한다면
지금 열려 있는 창을 닫지 말고, 방금 머리에 떠오른 질문 하나를 5초 안에 마크다운 파일 한 개로 떨어뜨리는 명령을 만들어라 — 그것 하나가 나머지 전부의 전제조건이다.