aaf2tbz/graphiq. 저장소를 통째로 읽어 함수·타입·호출·import·상수·타입 흐름을 '구조 그래프(structural graph)' 하나로 인덱싱하고, 그것을 BM25 전문검색 + 그래프 워크 재랭킹으로 검색하는 코드 인텔리전스 엔진이다. 핵심 주장은 강렬하다 — 임베딩도, LLM도, 네트워크 호출도 없다(no embeddings, no LLM, no network). 모든 것이 단일 SQLite 파일 안에 들어간다. Rust로 작성됐고, CLI · MCP 서버 · 데스크탑 앱 세 창구로 노출되어 Claude Code·Codex·OpenCode·Cursor 같은 AI 코딩 에이전트가 "파일을 통째로 읽는" 대신 랭킹된 심볼만 받게 한다. (저장소: aaf2tbz/graphiq · 언어 Rust · 워크스페이스 4개 크레이트 + 데스크탑 앱 · graphiq-core v4.3.3 / 아키텍처 v3 · 라이선스 MIT · TensorFlow 6.46M 라인 벤치마크에서 grep 대비 NDCG@10 +63% · ★ 급상승 신생 — TrendShift 라이브 멘션)
"AI에게 '파일 더미'를 던지는 대신, 미리 만들어 둔 '코드 색인'에서 가장 관련 있는 함수만 순위로 뽑아 준다 — 그것도 AI 모델 없이."
요즘 코드 검색 도구는 대개 임베딩(embedding)에 의존합니다 — 코드를 AI 모델로 숫자 벡터로 바꿔 '의미가 비슷한' 조각을 찾죠. 하지만 그건 GPU·모델·네트워크가 필요하고, 결과가 왜 그렇게 나왔는지 설명하기 어렵고, 토큰/비용이 듭니다. GraphIQ는 정반대로 갑니다. tree-sitter로 코드를 구문 분석해 심볼(함수·타입·변수)을 점, 관계(호출·import·타입 흐름)를 선으로 잇는 그래프를 만들고, 검증된 검색 수학인 BM25로 1차 후보를 뽑은 뒤 그래프를 따라 걸으며(graph walk) 순위를 다시 매깁니다. AI 모델은 한 번도 부르지 않습니다. 전부 SQLite 파일 하나에서, 오프라인으로, 결정론적으로 돕니다.
calls(호출)·imports·references(참조)·contains(포함)·extends(상속)·implements(구현) 같은 구문적 관계와, 한 발 더 나아가 shares_type(같은 타입 사용)·shares_error_type(같은 에러 처리)·shares_data_shape(같은 필드 접근)·shares_constant(같은 상수 사용) 같은 의미적(deep) 관계까지 포함합니다.좀 더 구체적으로, GraphIQ는 Rust로 만든 명령줄 도구이자 서버입니다. graphiq index /프로젝트로 한 번 색인하면 .graphiq/graphiq.db라는 SQLite 파일이 생기고, 이후 graphiq search "rate limit middleware"처럼 이름·자연어·파일 경로·에러 메시지 무엇으로든 검색할 수 있습니다. 같은 엔진을 ① 터미널에서 직접(CLI), ② AI 에이전트가 표준 규격으로(MCP 서버), ③ 데스크탑 앱에서 시각적으로 쓸 수 있습니다. 자랑거리는 "임베딩·LLM·네트워크 0, 그런데도 grep보다 의미 검색이 훨씬 정확하다"는 것 — 즉 'AI 없이 AI급 검색'이 이 프로젝트의 진짜 상품입니다.
이 문서가 파고드는 건 사용법만이 아니라 그 안의 설계입니다 — "AI 모델 없이 어떻게 자연어 질문을 이해하나(질의 패밀리 분류)", "BM25가 놓친 코드를 그래프가 어떻게 주워 오나(graph walk)", "왜 임베딩을 버렸나(29단계 실험기)". 검색·정보검색(IR)·코드 분석·에이전트 도구를 처음 공부하는 사람에게 드물게 솔직한 실전 교본입니다.
"모두가 임베딩(벡터 검색)으로 달려갈 때, GraphIQ는 'BM25면 충분하다'며 정반대로 걸어가 더 좋은 점수를 냈다."
2026년 코드 검색 도구의 8할은 임베딩 + 벡터 DB 조합입니다. 코드를 AI 임베딩 모델로 벡터로 바꿔 저장하고, 질문도 벡터로 바꿔 '가까운' 것을 찾죠(시맨틱 검색). 강력하지만 비용이 큽니다 — 임베딩 모델을 돌릴 GPU나 API, 벡터를 담을 DB, 그리고 코드가 바뀔 때마다 다시 임베딩해야 합니다. 게다가 결과가 왜 1등인지 설명하기 어렵고(블랙박스), 회사 코드를 외부 API로 보내야 하는 프라이버시 문제도 생깁니다.
임베딩은 멋지지만, 코드 검색에서 진짜 신호의 상당수는 구조에 있습니다 — "이 함수를 누가 부르나", "같은 에러 타입을 다루는 함수들", "같은 상수를 공유하는 코드". 이건 벡터 유사도가 아니라 그래프 관계로 풀어야 정확합니다. 임베딩에만 올인하면 비용은 비용대로 쓰면서 정작 구조 신호를 놓칩니다.
GraphIQ의 공식은 "BM25가 검색하고(retrieve), 구조가 재랭킹한다(rerank)"입니다. 1차로 SQLite의 FTS5 BM25 전문검색이 후보를 뽑고, 2차로 그래프 워크가 "후보와 호출·타입·에러를 공유하는 이웃"을 따라가 순위를 보정합니다. 임베딩이 필요 없고, 결과가 왜 그 순위인지 explain 툴로 설명할 수 있으며, 회사 코드는 한 줄도 밖으로 나가지 않습니다.
주장만 있는 게 아닙니다. README에는 TensorFlow 저장소(646만 라인, 심볼 433,898개, 엣지 726,439개, 5개 언어 — GraphIQ가 벤치마크한 역대 최대 규모)에서 grep과 100문항씩 맞붙은 결과가 실려 있습니다.
| 지표 | GraphIQ | Grep | 차이 |
|---|---|---|---|
| NDCG@10 | 0.201 | 0.123 | +63% |
| MRR@10 | 0.558 | 0.343 | +63% |
| Hit@10 | 0.85 | 0.52 | +33%p |
| 이긴 카테고리 | 8 / 10 | 2 / 10 | — |
| 관점 | GraphIQ | 전형적 임베딩 검색 |
|---|---|---|
| 의미 이해 방식 | BM25 + 구조 그래프 (규칙·수학) | 임베딩 벡터 유사도 (AI 모델) |
| 외부 의존 | 0 — 모델·GPU·네트워크·DB 불필요 | 임베딩 모델 + 벡터 DB |
| 저장 | 단일 SQLite 파일 | 벡터 인덱스 + 메타 저장소 |
| 프라이버시 | 100% 로컬, 코드 유출 0 | 외부 API 전송 가능성 |
| 설명가능성 | explain/why로 순위 근거 제시 | 대체로 블랙박스 |
| 증분 갱신 | 콘텐츠 해시로 바뀐 파일만 재색인 | 바뀐 코드 재임베딩 필요 |
| 속도 | 웜 ~50ms / MCP 인프로세스 ~18µs | 벡터 검색 + 모델 지연 |
임베딩 검색 = 모든 책을 통째로 외운 천재 사서. 비슷한 내용을 잘 찾지만, 외우는 데 시간·돈(GPU)이 들고 "왜 이 책을 골랐냐" 물으면 설명을 못 합니다.
GraphIQ = 색인 카드(BM25)와 상호참조 지도(그래프)를 든 베테랑 사서. AI 기억력은 없지만, 잘 만든 색인과 "이 책은 저 책을 인용한다"는 관계망으로 더 정확히, 더 빨리, 그리고 근거를 대며 찾아 줍니다.
이 레포가 개발자들 사이에서 회자되는 이유는 성능뿐 아니라 드물게 솔직한 개발 회고 때문입니다. 현재 아키텍처(v3)는 29단계의 실험 끝에 나왔는데, README가 실패를 그대로 공개합니다.
초기 버전은 스펙트럴/홀로그래픽/예측 수학을 잔뜩 넣었습니다 — 체비쇼프 열확산, FFT 홀로그래픽 인코딩, KL-발산 예측 모델, 리치 곡률(Ricci curvature), MDL 설명 집합. 코드 5,087줄에 RAM 18GB를 먹었는데, NDCG 개선은 고작 +0.02~0.05. 개발자의 표현으로 "반올림 오차를 리파이낸싱하고 있었다".
v3는 그 복잡한 수학을 전부 들어내고, 그래프 워크(구조 신호) + BM25(어휘 신호)만 남겼습니다. 결과는 더 단순하고, 더 가볍고, 설명 이름이 좋은 코드베이스에선 오히려 정확도가 올랐습니다. 핵심 교훈 7가지 중 첫째가 "BM25는 이기기 어렵다 — 늘 BM25가 검색하고 구조가 재랭킹하는 패턴이 승리한다", 둘째가 "단순한 게 낫다(Simpler is better)"입니다.
여기에 모든 주요 에이전트와 호환(Claude Code·Codex·OpenCode·Cursor·Pi)된다는 실용성, 단일 바이너리 + SQLite라는 배포 단순함이 더해져 빠르게 입소문을 탔습니다. 다만 솔직히 짚자면, GraphIQ는 아직 신생 프로젝트입니다(Live Mentions로 떠오른 초기 단계). 벤치마크 수치는 개발자가 제시한 값이고, NDCG@10 절대값(0.2대)이 보여주듯 코드 검색 자체가 본질적으로 어려운 문제라 "grep보다 낫다"는 상대 비교로 읽는 게 정확합니다.
"전부 Rust. 그리고 핵심은 딱 두 외부 기술 — 코드를 뜯는 tree-sitter, 검색·저장을 맡는 SQLite(FTS5)."
GraphIQ는 화면이 거의 없는 엔진 중심 프로젝트입니다. 무게중심이 graphiq-core 크레이트에 쏠려 있고, 외부 인프라(DB 서버, 벡터 스토어, 모델 서버)는 하나도 없습니다. 의존성 철학이 곧 셀링 포인트라, 의존성 목록을 보는 것만으로 설계 의도가 읽힙니다.
| 기술 | 역할 | 한 줄 설명 |
|---|---|---|
| Rust 2021 | 전체 언어 | 단일 바이너리·메모리 안전·빠른 속도. 워크스페이스(resolver 3)로 4개 크레이트를 묶는다. |
| rusqlite (bundled) | 저장 + 검색 | SQLite를 내장 컴파일(bundled)해 외부 DB가 필요 없다. FTS5 가상 테이블로 BM25 전문검색을 공짜로 얻는다. |
| tree-sitter 0.24 | 소스 파싱 | 13개 문법(typescript·rust·python·go·java·c·cpp·ruby·json·yaml·toml·html·css)으로 16개 언어 변종을 같은 방식의 구문 트리(AST)로 만든다. |
| rayon | 병렬 처리 | 인덱싱(파일 파싱·엣지 계산)을 멀티코어로 병렬화. 데이터 병렬의 표준 크레이트. |
| ignore | 파일 순회 | .gitignore를 존중하며 소스 파일을 걷는다. ripgrep과 같은 크레이트라 빠르고 정확하다. |
| zstd · bincode | 캐시 직렬화 | 인메모리 색인(CruncherIndex)을 cruncher.bin.zst로 압축 저장(20K 심볼에 ~6.5MB)해 재실행을 빠르게 한다. |
| sha2 | 증분 색인 | 파일 내용 해시로 "바뀐 파일만" 다시 파싱(content-hash incremental). |
| dashmap · lru · regex · thiserror · serde | 유틸 | 동시성 해시맵, LRU 캐시, 정규식, 에러 타입, 직렬화 — 흔하지만 탄탄한 기반. |
| 크레이트/창구 | 대상 | 설명 |
|---|---|---|
| graphiq-mcp | AI 에이전트 | 핵심 상품. stdio(JSON-RPC)로 에이전트에 물리는 MCP 서버. search·context·blast·briefing 등 툴을 노출. |
| graphiq-cli | 사람(터미널) | graphiq search/context/blast… 처럼 사람이 직접 질의. main.rs가 184KB에 이르는 가장 큰 단일 파일. |
| apps/desktop | 브라우저/데스크탑 | 색인을 시각적으로 둘러보는 인덱스 브라우저 앱. |
| graphiq-bench + benches/ | 품질 측정 | NDCG/MRR 벤치마크 하네스와 질의 세트. "grep 대비 +63%"가 나온 곳. |
흥미로운 디테일입니다. Cargo.toml을 보면 candle(Rust 딥러닝), llama-cpp-2(로컬 LLM), wgpu(GPU) 의존성이 선택적(optional) 기능으로 들어 있고, 기본값은 전부 꺼짐(default = [])입니다. 소스에도 embed.rs·gpu_compute.rs가 존재하지만 피처 게이트로 막혀 있습니다.
| 피처 | 켜면 추가되는 것 | 기본값 |
|---|---|---|
| embed-candle | candle-core/nn/transformers, tokenizers, hf-hub (임베딩) | 꺼짐 |
| embed | llama-cpp-2, ureq (로컬 LLM/다운로드) | 꺼짐 |
| gpu | wgpu, pollster (GPU 가속) | 꺼짐 |
즉 GraphIQ는 "임베딩을 할 줄 모르는 게 아니라, 안 켜는 것을 기본으로 택한" 프로젝트입니다. 29단계 실험에서 "BM25+구조만으로 충분하더라"는 결론을 내렸기에, 무거운 경로는 옵션으로만 남겨 둔 것이죠. "기본은 가볍게, 필요하면 켜라"는 성숙한 설계 태도입니다.
| 항목 | 내용 |
|---|---|
| 배포 형태 | 단일 바이너리 + SQLite 내장. 런타임 의존성·외부 서비스 0. |
| 설치 | Homebrew, install.sh 스크립트, 또는 소스 빌드. rust-toolchain.toml로 툴체인 고정. |
| CI | GitHub Actions linux-smoke 워크플로 — 핵심 동작 회귀 테스트. |
| 문서 | docs/가 매우 충실 — 동작 원리(18KB), 연구 노트(40KB, 29단계), 벤치마크, 신뢰성, 하드닝 로드맵까지. |
| 에이전트 통합 | graphiq setup/sync/discover로 Claude Code·Codex·Cursor·Pi 등에 자동 연결. skills/graphiq/SKILL.md 동봉. |
"두 개의 큰 막 — 색인(그래프를 만든다)과 검색(5단계 파이프라인으로 순위를 매긴다)."
색인은 파일을 걷고 → tree-sitter로 뜯어 심볼을 뽑고 → 엣지를 잇고 → 역할 힌트를 붙여 → SQLite와 인메모리 색인에 적재하는 흐름입니다. 검색은 5단계 파이프라인으로, 한 질의가 분류 → 후보 → 확장 → 채점 → 정리를 거쳐 순위가 됩니다. 아래에서 설계 포인트를 하나씩 봅니다.
GraphIQ는 같은 데이터를 두 군데에 둡니다. SQLite(4개 테이블: symbols·edges·files·symbols_fts)는 영속 저장과 BM25 검색을 맡고, CruncherIndex는 그래프 워크를 위해 인접 리스트·IDF 가중치·이웃 용어·구조 차수를 전부 메모리에 미리 계산해 둡니다. 후자는 cruncher.bin.zst로 압축 캐시되어, 두 번째 실행부터는 ~50ms 만에 뜹니다.
SQLite는 도서관 서고(모든 책이 안전하게 보관·검색됨), CruncherIndex는 사서 책상 위에 펼쳐 둔 색인 카드와 상호참조 지도입니다. 매번 서고를 뒤지지 않고 책상 위 지도로 즉답하되, 지도는 한 번 만들어 두면 압축해 보관했다가 다음에 그대로 펴면 됩니다.
가장 영리한 트릭입니다. 색인 시점에 GraphIQ는 심볼 이름·호출 패턴·파일 경로를 보고 19개 역할 태그(validator, cache, handler, retry, auth-gate…)와 8개 구조 모티프(connector, orchestrator, hub, guard, transform, sink, source, leaf)를 추론해 FTS의 hints 컬럼에 적어 둡니다. 그러면 함수 이름엔 없는 단어로도 검색이 됩니다 — 예: ensureFreshness라는 캐시 검증 함수에 "cache validate check verify" 힌트가 박혀, "validate cache entry" 질의가 이 함수를 찾아냅니다. 질의 시점 비용은 0(이미 색인돼 있으니).
검색 1단계에서 모든 질의는 8개 패밀리 중 하나로 분류됩니다.
| 패밀리 | 감지 신호 | 예시 |
|---|---|---|
| SymbolExact | PascalCase, snake_case, :: | RateLimiter |
| FilePath | 경로 구분자, 확장자 | scheduler/worker.rs |
| ErrorDebug | error/panic/timeout/crash | "timeout in channel send" |
| NaturalDescriptive | 동작 서술(기본 자연어) | "encode a value in VLQ" |
| NaturalAbstract | "how does…", "what controls…" | "how does auth work" |
| CrossCuttingSet | "all", "every", 복수형 | "all connector implementations" |
| Relationship | "what calls", "callers of" | "RateLimiter vs TokenBucket" |
핵심 통찰은 이것입니다 — 분류기는 "사용자 의도"를 맞히는 게 아니라, "어떤 신호를 켜도 되는지" 권한 경계를 정한다. 각 패밀리는 RetrievalPolicy(BM25 잠금 강도·다양성·증거 가중)와 ScoreConfig(채점 가중치·기능 게이트)를 내놓습니다. 심볼 검색은 BM25를 믿고, 추상 질문은 구조 확장에 기대고, "전부(all)" 질의는 다양성을 최대화 — 질의 종류마다 신호 조합이 다르기 때문입니다.
2단계는 후보(seed)를 모읍니다. 모든 질의는 FTS5 BM25로 시작하는데, 컬럼마다 가중치가 다릅니다(이름이 본문보다 10배 중요).
| FTS 컬럼 | 가중치 | 내용 |
|---|---|---|
| name | 10.0 | 심볼 이름 |
| decomposed | 8.0 | 식별자 분해(RateLimiter → rate, limiter) |
| qualified · hints · sig | 6 · 5 · 4 | 정규화 이름 · 역할 힌트 · 시그니처 |
| file_path · doc · source | 3.5 · 3 · 1 | 경로 · 문서주석 · 소스 본문 |
자연어 질의에선 여기에 네 갈래가 더해집니다 — per-term 확장(어간추출·동의어), numeric bridge("30초 후" 같은 숫자로 shares_constant 엣지 탐색), source scan(에러 질의 한정, 소스 본문 직접 매칭), graph-aware 확장(상위 시드에서 의미 엣지를 타고 확장). 패밀리별로 어떤 갈래를 켤지 시드 활성화 매트릭스가 정해 줍니다(심볼 질의는 BM25만, 자연어는 전부).
3단계가 GraphIQ의 가장 강한 구조 신호입니다. 상위 8개 시드에서 출발해 구조 그래프를 BFS로 최대 깊이 2까지 걷되, 이웃마다 두 관문을 통과해야 합니다.
| IDF 게이트 | 이웃이 희귀한(중간값 이상 IDF) 질의어를 하나라도 매칭 — 흔한 유틸 함수 거름 |
| 커버리지 게이트 | 이웃이 자기 텍스트로도 질의어를 하나는 매칭 — 순수 구조만으로의 오답 방지 |
걷기 증거는 커버리지 × 0.5^깊이(근접 감쇠) × 엣지 가중치로 계산합니다. 이 덕에 BM25가 텍스트로는 못 찾지만 구조로 연결된 심볼을 주워 옵니다 — README는 관계(Relationship) 질의에서 grep 대비 3.7배 우수하다고 말합니다(타입 흐름·에러 표면·데이터 모양을 공유하는 심볼을, 질의와 글자가 안 겹쳐도 찾기 때문).
4단계 채점은 패밀리별 가중치(BM25 w / 커버리지 w / 이름 w / 워크 w)로 베이스 점수를 만든 뒤, 가산(additive) 보정을 더합니다 — 이름 겹침과 이웃 지문(1-hop 이웃 용어와의 일치)인데, 둘 다 신뢰 게이트(threshold) 아래면 기여가 정확히 0입니다. 이어 곱셈(multiplicative) 보정으로 커버리지·종류(함수/타입↑, 변수/import↓)·테스트 페널티를 반영합니다. 5단계 후처리는 BM25 신뢰 잠금(1등이 2등보다 1.2배 이상 앞서고 이름이 질의어를 포함하면 1등으로 고정)과 파일 다양성 상한(한 파일이 결과를 독식하지 못하게 1~5개로 제한)입니다.
① BM25는 이기기 어렵다 — 늘 'BM25 검색 + 구조 재랭킹'이 승리. ② 단순한 게 낫다 — v1의 18GB 수학을 지우니 더 좋아짐. ③ 신뢰가 중요 — BM25 잠금이 명백한 어휘 일치를 구조 신호가 뒤엎지 못하게. ④ 가산이 곱셈을 이긴다 — 곱셈은 순서만 섞지만 가산은 후보를 진짜 끌어올림(단, 게이트 필수). ⑤ 신호에 게이트를 — 이름 겹침 임계값이 코드베이스에 자동 적응. ⑥ 패밀리 라우팅이 맞는 추상화. ⑦ 기하는 계산하되 점수화하진 말라 — 리치 곡률 같은 토폴로지는 인프라일 뿐, 랭킹 신호는 커버리지·이름·워크 증거.
"Cargo 워크스페이스 하나에 4개 크레이트 + 데스크탑 앱. 무게는 graphiq-core의 35개 모듈에 쏠려 있다."
모듈이 많아 보이지만, 역할로 묶으면 "검색엔진의 부품 목록"이 또렷해집니다.
| 묶음 | 대표 모듈 | 하는 일 |
|---|---|---|
| 색인·추출 | index.rs(71KB), files.rs, symbol.rs, edge.rs, languages/, manifest.rs, chunker.rs | 파일을 걷고 → tree-sitter로 심볼/엣지 추출 → 파일 메타·청크 관리 |
| 그래프·딥엣지 | graph.rs, deep_graph.rs, edge_evidence.rs, calls.rs, numeric_bridges.rs | 구조 그래프 본체 + 의미 엣지(타입/에러/데이터모양/상수) 계산 |
| 인메모리 색인 | cruncher.rs(34KB), cache.rs | 인접리스트·IDF·이웃항을 메모리에 미리 계산(CruncherIndex) + 캐시 |
| 검색 파이프라인 | query_family.rs, seeds.rs, pipeline.rs, search.rs, scoring.rs, rerank.rs(24KB), fts.rs | 분류 → 시드 → 워크 → 채점 → 재랭킹의 5단계 + FTS5 연동 |
| 텍스트·역할 | tokenize.rs, decompose.rs(27KB), roles.rs, motifs.rs, behavioral.rs | 식별자 분해·토큰화·역할 태그 19종·모티프 8종·행동 힌트 추론 |
| 분석·영향 | impact.rs(34KB), subsystems.rs(47KB), structural_alias.rs(27KB), dead_code.rs, trace.rs | 변경 영향(blast)·서브시스템 구조·구조적 별칭·죽은코드·추적 |
| 저장·선택기능 | db.rs(41KB), embed.rs, gpu_compute.rs(31KB) | SQLite 스키마/질의 + (기본 꺼짐) 임베딩·GPU 경로 |
검색엔진 공장으로 보면, 색인·추출은 원자재 가공 라인, 그래프·딥엣지는 부품 간 배선도, 인메모리 색인은 작업대 위 즉시조회 선반, 검색 파이프라인은 주문을 받아 처리하는 5단계 컨베이어, 분석·영향은 품질·위험 검사실입니다. db.rs 한 파일이 41KB인 건 SQLite 스키마·질의가 그만큼 엔진의 중심이라는 뜻입니다.
index.rs(71KB)가 코어에서 가장 크다는 건 "색인의 정확함이 검색의 전부"라는 철학을 보여 줍니다(엣지·힌트를 잘 만들어 둬야 검색이 산다). 그리고 graphiq-cli/main.rs(184KB)·graphiq-mcp/main.rs(123KB)가 거대한 건, 사람용·에이전트용 표면(surface)에 명령·툴·출력 포맷이 그만큼 촘촘히 들어 있다는 뜻입니다.
"GraphIQ 한 레포 안에 정보검색(IR)·컴파일러 프런트엔드·그래프 알고리즘·Rust 시스템·도구 철학이 다 들어 있다."
배울 것: 임베딩 없이도 강력한 고전 검색의 핵심. TF-IDF에서 BM25로 이어지는 발상, 그리고 SQLite FTS5만으로 전문검색을 돌리는 법. "왜 이름 컬럼에 10배 가중을 주나" 같은 가중 컬럼 설계도.
실습: SQLite로 FTS5 가상 테이블을 만들어 작은 코드 모음을 색인하고, bm25() 함수로 "rate limit" 검색 결과를 점수순으로 출력해 보기. 컬럼 가중치를 바꿔 순위가 어떻게 변하는지 관찰.
배울 것: 소스를 텍스트가 아니라 구문 트리로 다루는 법. 언어가 달라도 같은 노드/엣지 스키마로 환원하는 디스패치 구조(languages/), 함수·호출·import를 트리에서 잡아내는 쿼리.
실습: tree-sitter로 작은 Python 파일을 파싱해 함수 이름을 전부 출력하고, 그다음 "함수 호출(call)" 노드를 잡아 호출 목록을 뽑아 보기 = GraphIQ calls 엣지의 축소판.
배울 것: 심볼=노드/관계=엣지 스키마를 안정 ID로 설계하고, 빠른 조회를 위한 인접 리스트(정방향·역방향)를 만드는 법. 그리고 근접 감쇠(0.5^깊이)를 곁들인 BFS로 이웃을 점수와 함께 확장하는 패턴.
실습: map[심볼][]엣지 두 개(정/역방향)로 미니 호출 그래프를 만들고, 시드에서 깊이 2까지 BFS하며 0.5^깊이 × 엣지가중으로 이웃 점수를 누적해 보기.
배울 것: LLM 없이 자연어 질의를 패밀리로 분류하고, 분류가 "의도"가 아니라 "어떤 신호를 켤지 권한"을 정하게 만드는 설계. 우선순위 캐스케이드로 오분류를 막는 법(예: "all error types"는 cross-cutting이지 error-debug가 아님).
실습: 정규식 몇 개로 질의를 SymbolExact / FilePath / Natural 셋으로 나누는 분류기를 짜고, 각 분류마다 "BM25만" vs "BM25+그래프"를 켜는 정책 객체를 반환해 보기.
배울 것: "검색이 좋아졌다"를 숫자로 증명하는 법. 정답 세트를 만들고, 상위 K 결과로 Hit/MRR/NDCG를 계산해 grep 같은 베이스라인과 비교하는 벤치마크 설계(benches/queries/가 본보기).
실습: 질의 10개와 정답을 손으로 정해 두고, 내 검색기와 단순 grep의 결과로 Hit@10·MRR@10을 계산해 표로 비교. "어느 질의 유형에서 격차가 큰가"를 분석.
배울 것: Cargo 워크스페이스로 라이브러리/CLI/서버를 분리하기, rayon으로 색인을 병렬화하기, bincode+zstd로 인메모리 구조를 압축 캐시하기, 콘텐츠 해시로 증분 색인하기 — "혼자서도 배포 가능한 빠른 도구"의 전형.
실습: 작은 Rust 워크스페이스(core 라이브러리 + cli)를 만들고, 파일 목록을 rayon으로 병렬 해시(sha2)한 뒤 결과 구조체를 bincode+zstd로 저장/복원해 보기.
배울 것: 이 레포 전체를 관통하는 사상. "복잡한 수학이 항상 이기는 건 아니다 — 검증된 베이스라인(BM25)에 잘 게이트된 신호 몇 개를 더하는 게 낫다"는 관점. docs/research.md(40KB)는 그 자체로 훌륭한 엔지니어링 에세이입니다.
실습: docs/research.md를 읽고 "삭제된 기능 5가지와 그 이유"를 한 페이지로 요약해 보기. 내 프로젝트에서 "효과 대비 복잡한" 부분을 하나 찾아 제거 실험을 설계.
"GPU·클라우드·DB·모델 전부 필요 없다. 드는 건 'Rust 빌드 한 번'과 '심볼 수에 비례하는 약간의 RAM'뿐."
| 항목 | 요구사항 / 메모 |
|---|---|
| 언어/런타임 | Rust 툴체인으로 빌드(또는 Homebrew·install.sh로 설치). 결과는 단일 바이너리라 런타임 의존성 0. |
| 외부 서비스 | 없음. 데이터베이스·벡터DB·클라우드 API·임베딩 모델 전부 불필요. SQLite가 내장(bundled)이라 별도 설치도 없음. |
| GPU | 불필요. gpu·임베딩 피처는 기본 꺼짐. 기본 동작은 100% CPU·오프라인. |
| 네트워크 | 불필요. 색인·검색에 네트워크 호출이 없다 = 에어갭 환경·사내 코드에 안전. |
| 메모리(RAM) | 심볼 수에 비례. 인메모리 CruncherIndex가 핵심 — 20K 심볼 캐시가 ~6.5MB. 거대 모노레포는 더 들지만, 임베딩 방식보다 훨씬 가볍다. |
| CPU 제어 | GRAPHIQ_MAX_THREADS / RAYON_NUM_THREADS로 스레드 상한. 세션/백그라운드 모드는 기본 4스레드, 포그라운드 색인은 전체 코어 사용. |
| 디스크 | .graphiq/graphiq.db(SQLite) + cruncher.bin.zst(압축 인메모리 캐시). 프로젝트 폴더 안에 생긴다. |
| 속도 | 콜드(첫 실행) ~5–10초(캐시 빌드), 웜 CLI ~50ms, MCP 인프로세스 ~18µs. |
| OS | 크로스 플랫폼(Linux CI smoke 테스트 운영). 백그라운드 데몬은 세션 종료 시 자동 정리(dormancy). |
GraphIQ는 에이전트가 띄운 데몬으로 상주할 수 있어 크래시 안전성을 신경 씁니다 — 콘텐츠 해시 증분 색인(바뀐 파일만 재파싱), 0-심볼 색인도 크래시 없이 "결과 없음"으로 우아하게 처리, 출력을 head로 받다 끊겨도 패닉 대신 SIGPIPE(exit 141)로 종료(ripgrep·fd와 같은 정석). 락파일·256KB 초과 JSON/YAML/TOML은 심볼 추출에서 제외해 그래프가 잡음으로 오염되지 않게 합니다.
모든 그래프 데이터를 메모리에 올리는 설계라, 수십만 심볼 규모(예: TensorFlow 433K 심볼)에선 RAM 사용이 올라갑니다. 완화책으로 백그라운드 저메모리 프로파일(GRAPHIQ_INDEX_MODE=background)과 소스 토큰 창 축소(GRAPHIQ_SOURCE_TERM_LIMIT)가 있지만, 초대형 레포에서는 색인 시간·메모리를 미리 가늠하는 게 좋습니다.
"색인해서 검색해 보기 → SQLite 속을 들여다보기 → 에이전트에 물리기 → 파이프라인을 읽고 → 미니 엔진을 만들기."
설치 후 graphiq index /내/프로젝트로 색인 → graphiq search "rate limit middleware"처럼 자연어로 검색. 같은 검색을 두 번 돌려 첫 실행(캐시 빌드)과 두 번째(웜)의 속도 차이를 느껴 본다.
목표: "이름이 아니라 동작 설명으로도 코드가 찾아진다"를 체감. 같은 질의를 grep -r로도 해 보고 결과 품질을 눈으로 비교.
sqlite3 .graphiq/graphiq.db로 접속해 .tables로 4개 테이블을 확인하고, symbols·edges 행을 들여다본다. symbols_fts에 bm25() 질의를 직접 날려 본다.
목표: "검색엔진이 별게 아니라 잘 설계된 SQLite 테이블"임을 깨닫기. edges에서 shares_error_type 같은 deep 엣지를 찾아 "이게 왜 연결됐지?"를 추적.
graphiq setup으로 에이전트에 연결한 뒤, 심볼·파일경로·에러·자연어·관계 등 서로 다른 유형의 질의를 던져 본다. explain/why 툴로 "왜 이 결과가 1등인지" 근거를 받아 본다.
목표: 같은 엔진이 질의 유형마다 다른 신호를 켜는 것을 관찰. "what calls X"(관계)와 "RateLimiter"(심볼)에서 결과 성격이 어떻게 달라지나 비교.
crates/graphiq-core/src의 query_family.rs → seeds.rs → pipeline.rs → scoring.rs를 순서대로 읽으며, 자연어 질의 하나가 분류 → 시드 → 워크 → 채점을 어떻게 지나는지 손으로 따라가 본다.
목표: docs/how-graphiq-works.md의 설명과 실제 코드를 대조. "왜 ErrorDebug만 source_scan을 켜나" 같은 게이팅 결정을 코드에서 확인.
좋아하는 언어로 작은 코드 검색기를 만든다 — tree-sitter로 함수 추출 → SQLite FTS5에 색인 → BM25로 검색 → calls 엣지로 1-hop 재랭킹. GraphIQ의 "BM25 검색 + 구조 재랭킹" 패턴을 축소 재현.
목표: 질의 세트로 BM25 단독 vs BM25+1-hop 재랭킹의 NDCG@10을 비교해, 구조 신호가 실제로 순위를 끌어올리는지 수치로 확인. 이게 GraphIQ 논지의 핵심 검증.
"Rust 기초에서 출발해 정보검색·파싱·그래프·랭킹 평가까지, GraphIQ를 길잡이 삼아."
| 주차 | 주제 | 핵심 학습 + GraphIQ 연결점 |
|---|---|---|
| 1주 | Rust 기초 + 워크스페이스 | 소유권·에러처리·Cargo 워크스페이스로 라이브러리/CLI 분리 → Cargo.toml·crates/graphiq-core·cli |
| 2주 | 정보검색(IR) 기초 | TF-IDF → BM25, SQLite FTS5로 전문검색, 가중 컬럼 설계 → fts.rs·symbols_fts |
| 3주 | 파싱 · AST · tree-sitter | 구문 트리에서 심볼/호출/import 추출, 멀티언어 디스패치 → index.rs·languages/ |
| 4주 | 그래프 모델링 + 순회 | 인접 리스트(정/역방향), BFS 워크, IDF·근접 감쇠 → graph.rs·cruncher.rs·pipeline.rs |
| 5주 | 질의 이해 + 채점 + 평가 | 규칙기반 패밀리 분류, 가산/곱셈 채점, NDCG/MRR 벤치마크 → query_family.rs·scoring.rs·benches/ |
| 6주 | MCP 서버 + 도구 철학 | 에이전트 툴 표면 설계, 신뢰성/도먼시, 연구 회고 정독 → graphiq-mcp + docs/research.md |
이 로드맵은 "정보검색 + 컴파일러 프런트엔드(파싱) + 그래프 알고리즘 + 시스템/도구화"를 한 줄로 꿴 코스입니다. 특히 GraphIQ는 "화려한 AI 없이 검증된 기법을 잘 조합하면 어디까지 가는가"를 보여주는 살아 있는 사례라, 검색·랭킹을 공부하는 사람에게 이론과 실전을 동시에 잡게 해 줍니다.
"이 문서와 저장소에서 반복되는 용어를 한곳에 모았다."
| 용어 | 의미 |
|---|---|
| 코드 인텔리전스 엔진 | 소스를 구조 데이터로 바꿔 '정의·참조·호출' 같은 코드 내비게이션과 자연어 검색을 API로 제공하는 도구 |
| 구조 그래프 | 심볼=노드, 관계=엣지로 코드를 표현한 그물망. GraphIQ의 본체 |
| BM25 | TF(빈도)와 IDF(희귀도)를 결합한 고전 전문검색 랭킹 공식. 임베딩 없이도 강력 |
| FTS5 | SQLite 내장 전문검색 엔진. BM25를 기본 제공 — 별도 검색서버가 필요 없음 |
| IDF (역문서빈도) | 흔한 단어는 깎고 희귀한 단어를 키우는 가중치. GraphIQ는 그래프 워크의 'IDF 게이트'에도 씀 |
| NDCG@10 / MRR@10 / Hit@10 | 검색 품질 채점표. 정답을 얼마나 위쪽에 잘 모았나(NDCG)·첫 정답 순위(MRR)·상위10 적중(Hit). 높을수록 좋음 |
| 임베딩 (embedding) | 코드를 AI 모델로 벡터화해 의미 유사도로 검색하는 방식. GraphIQ는 일부러 안 씀(코드는 있으나 기본 꺼짐) |
| tree-sitter / AST | 여러 언어를 같은 방식으로 구문 트리(AST)로 파싱하는 파서. 16개 언어 심볼 추출에 사용 |
| CruncherIndex | 인접리스트·IDF·이웃항·구조차수를 메모리에 미리 계산한 인메모리 색인. cruncher.bin.zst로 압축 캐시 |
| 질의 패밀리 (query family) | 질의를 8종(SymbolExact·FilePath·ErrorDebug·NaturalDescriptive·NaturalAbstract·CrossCuttingSet·Relationship 등)으로 분류해 신호를 다르게 켬 |
| RetrievalPolicy / ScoreConfig | 패밀리가 내놓는 '권한 경계' — BM25 잠금 강도·다양성·채점 가중·기능 게이트를 정의 |
| 구조 엣지 / deep 엣지 | 구조: calls·imports·contains·extends·implements / deep: shares_type·shares_error_type·shares_data_shape·shares_constant·comment_ref |
| hints 컬럼 | 색인 시점에 추론한 역할 태그 19종 + 모티프 8종을 FTS에 새겨, 이름에 없는 단어로도 검색되게 함 |
| 그래프 워크 (graph walk) | 시드에서 BFS로 이웃을 확장하는 재랭킹 단계. 커버리지 × 0.5^깊이 × 엣지가중으로 증거 누적 |
| 시드 생성 (seed) | BM25 + per-term 확장 + numeric bridge + source scan + graph-aware로 1차 후보를 모으는 단계 |
| 이웃 지문 (neighbor fingerprint) | 후보의 1-hop 이웃 용어와 질의어의 겹침. 동명이의 일반 이름을 문맥으로 구별하는 가산 신호 |
| BM25 신뢰 잠금 | BM25 1등이 2등보다 1.2배 앞서고 이름이 질의어를 포함하면 1등으로 고정 — 구조 신호의 과잉승격 방지 |
| 파일 다양성 상한 | 한 파일이 상위 결과를 독식하지 못하게 1~5개로 제한(패밀리별) |
| blast radius (영향 반경) | 한 심볼을 바꿀 때 영향받는 범위. 의존 엣지를 역방향으로 따라가 계산(blast 툴) |
| 콘텐츠 해시 증분 | 파일 내용 해시로 바뀐 파일만 다시 파싱하는 색인 방식(sha2) |
| specificity (특이도) | 질의어 중 IDF가 높은(희귀한) 비율. 높으면 BM25 가중↑, 낮으면 커버리지 가중↑로 동적 조정 |
| rayon / zstd / bincode | 병렬 처리 / 압축 / 직렬화 — 인덱싱 병렬화와 인메모리 캐시 저장에 사용 |
| MCP | 에이전트와 도구를 잇는 표준 규격. GraphIQ는 코드검색 툴을 가진 MCP 서버로 동작 |