로컬 sqlite-vec RAG: 50만 자 노트를 200 MB 파일로 인덱싱
로컬 sqlite-vec RAG: 50만 자 노트를 200 MB 파일로 인덱싱
결론부터 말하면: Gemini가 내 로컬 노트를 조회할 수 있는 RAG 시스템을 만들려고 LanceDB나 Chroma, 로컬 Docker를 돌릴 필요는 없습니다. GeminiDesktop은 sqlite-vec + gemini-embedding-001(768차원) 을 선택해, 50만 자 Markdown 노트를 단일 200 MB SQLite 파일로 인덱싱하며 top-5 cosine 쿼리가 50ms 이하로 안정적입니다 — NotebookLM 웹의 300-800ms보다 한 자릿수 빠른 수준입니다. 이 글은 그 엔지니어링 트레이드오프에 대한 기록입니다.
한 줄 요약: 데스크톱 앱 안의 RAG는 벡터 DB의 모든 의식을 치를 필요가 없습니다. 번들된 sqlite-vec extension + 768차원 임베딩 + 2000자 청크 + 콜드 인덱스 배치 동시성이면 “충분히 좋은” 체험을 얻을 수 있습니다. 핵심은 GUI와 MCP가 같은 .db 파일을 공유하도록 하고 SQLite 자체 락으로 동시성을 처리하는 것입니다.
왜 sqlite-vec이고, LanceDB / Chroma가 아닌가
데스크톱 앱의 RAG 제약은 클라우드 RAG와 완전히 다릅니다.
| 항목 | sqlite-vec | LanceDB | Chroma | PGVector |
|---|---|---|---|---|
| 배포 형태 | 단일 .db 파일 + Tauri 번들 extension | 단일 디렉터리(복수 lance 파일) | 별도 프로세스 | Postgres 별도 |
| 콜드 스타트 | < 5ms(SQLite attach) | 50-200ms(mmap 메타) | 2-5s(HTTP server 부팅) | 1-3s(psql handshake) |
| 백업 | 파일 하나 복사 | 디렉터리 tar | API 내보내기 | pg_dump |
| 프로세스 간 공유 | SQLite 자체 락 | 가능하지만 복잡 | 단일 프로세스 서비스 | 네이티브 |
| 최대 규모 | ~1천만 벡터, 10억은 위험 | 1억+ | 1억+ | 10억+ |
“개인 노트 규모” 데스크톱 앱에서는 sqlite-vec이 단일 파일 · 제로 서비스 · 번들 친화 세 박자를 만족합니다.
- Tauri 빌드 시
sqlite-vec.dylib/.dll/.so를 설치 파일에 번들 - 첫 실행에서
~/Library/Application Support/app.geminidesktop.desktop/local_index.db생성 - GUI / CLI / MCP 세 개 진입점이 같은 파일을 공유, 포트도 백그라운드 프로세스도 불필요
LanceDB도 데스크톱에서 쓸 수 있지만 다중 파일 디렉터리 구조는 iCloud 동기화에서 문제가 생기기 쉽고 원자적 백업이 어렵습니다. Chroma는 서버 프로세스가 필요해 Tauri의 “모두 내장” 철학과 충돌합니다.
768차원 gemini-embedding-001 vs ada-002 vs BGE-small 실측
주류 임베딩 모델 3개를 4.2만 개 한영 혼합 Markdown 노트로 벤치마크:
| 모델 | 차원 | 한국어/중국어 recall@5 | 영어 recall@5 | 속도(batch=100) |
|---|---|---|---|---|
text-embedding-ada-002 (OpenAI) |
1536 | 78% | 88% | ~1.2s(클라우드만) |
BGE-small-zh-v1.5 (로컬) |
512 | 83% | 72% | ~0.4s(로컬 CPU) |
gemini-embedding-001 |
768 | 87% | 91% | ~0.8s(클라우드만) |
결론:
- 비영어 정확도: gemini-embedding-001 > BGE-small > ada-002. 한국어/중국어 노트가 주 용도이므로 가중치가 가장 큼.
- 차원 선택: 768이 정확도/저장공간 스위트 스팟. 1536은 인덱스 파일 2배, 쿼리 60% 느린데 정확도 향상은 3% 미만.
- 로컬 vs 클라우드: BGE-small은 제로 네트워크 시나리오에 가치 있음, 추후 옵션으로 추가 예정. MVP는 “임베딩 소스 하나”로 복잡도 축소.
DB에는 (chunk_id, source_path, chunk_text, embedding BLOB) 저장, embedding은 768 × 4 = 3072바이트 float32 BLOB. 50만 자를 2000자 단위로 자르면 약 300 청크(오버랩 포함), 본문과 메타데이터를 더해 최종 파일은 약 200 MB입니다.
청크 전략: 2000자 + overlap 200, 시맨틱 청크를 쓰지 않는 이유
흔한 청크 전략 3가지:
- 고정 크기 청크(2000자 + 200 overlap) — GeminiDesktop 채택
- 재귀적 문자 분할(LangChain 스타일, 문단→문장→문자 계층 강등)
- 시맨틱 청크(임베딩 유사도로 “주제 경계” 탐색)
MVP 단계에서 가장 단순한 고정 크기를 선택한 이유:
- 시맨틱 청크는 경계를 찾기 위해 임베딩을 한 번 더 돌려야 함 — 1만 청크 콜드 인덱스가 고정 크기보다 2-3배 느려짐
- 재귀 분할은 Markdown의 H2/H3 구조에는 친화적이지만 회의록·채팅 로그처럼 계층이 없는 텍스트에서는 오히려 나빠짐
- 고정 크기 + 200 overlap은 재귀 분할보다 recall이 약 4%만 떨어지는 반면 구현이 단순·예측 가능·병렬화 쉬움
다음 버전에서는 하이브리드를 넣을 예정: 기본은 고정 크기, Markdown 파일엔 heading-aware 재귀 분할. MVP의 요점은 “충분히 좋은” 체험을 먼저 띄우는 것. 로컬 우선 설계 트레이드오프 전반은 NotebookLM의 대안: 왜 데스크톱 앱으로 만드는가에서 확인할 수 있습니다.
콜드 스타트 벤치마크: 10K 청크 0부터 인덱스
테스트 환경: M2 MacBook Air(8코어), 100 Mbps 네트워크, 1만 청크(약 20만 자).
| 전략 | 총 소요 시간 | 병목 |
|---|---|---|
| 단일 스레드 순차 embed | 186분 | 네트워크 RTT |
| batch=100, 동시성=4 | 4.8분 | Gemini API QPS |
| batch=200, 동시성=8 | 3.9분(단 12% 확률로 429) | 레이트 제한 |
기본값은 batch=100 + concurrency=4. 안정성과 커피 한 잔 시간의 균형. GUI가 진행률과 ETA를 표시, Mac이 슬립에 들지 않으면 20만 자는 5분 안에 인덱스 완료.
인덱스 중 SQLite 쓰기 전략:
BEGIN TRANSACTION으로 batch 감싸고 1000행마다 commitPRAGMA synchronous=NORMAL(데스크톱엔 FULL 불필요)PRAGMA journal_mode=WAL로 읽기/쓰기 동시성 비차단 — 이게 뒤의 GUI + MCP 2프로세스 공유에 결정적
쿼리 벤치마크: top-5 cosine 50ms 이하
같은 M2 MacBook Air, 20만 자 인덱스된 상태:
| 동작 | 시간 | 비중 |
|---|---|---|
| Query embedding(gemini-embedding-001 클라우드) | 280ms | 85% |
| sqlite-vec top-5 cosine | 18ms | 6% |
| chunk 원문 읽기(SQLite) | 9ms | 3% |
| 총합(첫 호출) | 307ms | 100% |
| 총합(query embedding LRU 적중) | 27ms | — |
NotebookLM 웹과 비교: 쿼리당 300-800ms(노트북 크기에 따라), 로그인·네트워크·가끔 스로틀링. GeminiDesktop 로컬 RAG는 임베딩 단계만 제외하고 모두 로컬, LRU 적중 시 30ms 이하 — “방금 검색한 키워드 다시 검색” 시나리오가 질적으로 달라집니다.
LRU 캐시 전략: 최근 200개 쿼리의 (query_text_hash, embedding_bytes)를 메모리에 보관, 실측 적중률은 약 35%.
2프로세스 1개의 .db: GUI + MCP 공유
GeminiDesktop 아키텍처는 GUI(Tauri 메인 프로세스)와 MCP 서버(Rust stdio 자식, Claude Code / Cursor가 spawn)가 같은 local_index.db에 동시 접근할 수 있습니다. SQLite의 해법:
- WAL 모드: 다중 리더 + 단일 라이터 병행 비차단
- 파일 락: SQLite 자체
fcntl락, 프로세스 세이프 - busy_timeout=5000: 짧은 경합은 5초간 자동 재시도
- 쓰기는 GUI 프로세스에 집중: MCP는 기본 읽기 전용(
rag_query),rag_index만 쓰기이며 이때 앱 레벨 mutex 획득
실제 시나리오:
- GUI에서 새 노트 인덱스 완료 → Claude Code가 즉시
rag_query→ 최신 데이터, 충돌 0 - Claude Code가 MCP 통해
rag_index트리거 → GUI의 “인덱스된 파일” 리스트가 실시간 새로 고침 - 크래시 복구: WAL 롤백 저널이 한쪽의 비정상 종료에서도 DB 손상을 방지
200시간+ 혼합 읽기/쓰기 테스트에서 손상 0, 데드락 0. MCP 측 엔지니어링은 GeminiDesktop MCP 서버 2026: Claude Code / Cursor에서 Gemini 네이티브 도구 호출 참고.
디렉터리 구조와 백업 전략
~/Library/Application Support/app.geminidesktop.desktop/
├── config.json # API 키 및 설정
├── local_index.db # sqlite-vec 메인 인덱스
├── local_index.db-wal # WAL 저널(실행 중 존재)
├── local_index.db-shm # 공유 메모리
└── backups/
├── local_index.db.2026-04-15.bak
├── local_index.db.2026-04-16.bak
└── local_index.db.2026-04-17.bak
백업 전략:
- 일일 자동 백업: GUI 시작 시 당일 백업이 없으면 백그라운드에서
VACUUM INTO실행(파일 복사보다 안전, 미 checkpoint WAL 데이터까지 처리) - 7일 보관: 7일 넘은 백업은 자동 삭제
- iCloud 친화: 단일 .db 파일이므로
~/Documents/GeminiDesktop/에 심볼릭 링크 걸면 동기화 OK, 200 MB는 iCloud 무료 용량에서 무시할 수준
제한 사항과 다음 버전
현재 구현의 알려진 제한, 다음 버전에서 개선 예정:
- BM25 하이브리드 미지원: 순수 dense retrieval은 “제품 SKU”, “전화번호”처럼 정확 일치가 필요한 쿼리에 약함. 다음에
sqlite-vec+ FTS5 하이브리드 추가. - 다국어 rerank 부재: gemini-embedding-001은 다국어이지만 rerank 단계가 없음. Cohere Rerank 3 또는 로컬 BGE-reranker-m3 도입 검토 중.
- 구조화된 메타데이터 필터 약함: 지금은
source_path LIKE '%notes/2026%'같은 문자열 매칭만. 다음에 전용 메타데이터 컬럼과 복합 인덱스 추가. - PDF/Word 파싱 기본 수준:
.md / .txt / .pdf / .docx지원, 복잡한 표나 OCR 필요한 PDF는 Gemini Vision 폴백에 의존.
빠른 시작
- GeminiDesktop 다운로드: https://geminidesktop.app
- 앱 열기 → 설정 → Gemini API 키 입력
- “RAG” 탭 → 인덱스할 폴더 선택(
~/Documents/Notes같은 작은 디렉터리부터 시작 권장) - 인덱싱 완료 후 Claude Code에서
claude mcp add geminidesktop -- /Applications/GeminiDesktop.app/Contents/MacOS/GeminiDesktop mcp실행 - Claude에게 “내 노트에서 XXX 찾아줘”라고 말하면 자동으로
rag_query를 호출합니다
자주 묻는 질문
Q1: 로컬 RAG가 정말 NotebookLM보다 그렇게 빠른가요?
A: 네, 단 query embedding이 LRU 캐시에 적중했을 때. 첫 쿼리는 여전히 약 300ms(네트워크 왕복 불가피), 반복 쿼리는 30ms 이하. NotebookLM은 매번 전체 라운드트립이라 300-800ms가 바닥입니다.
Q2: 50만 자에 200 MB는 너무 큰가요?
A: 크지 않습니다. 50만 자 ≈ 200페이지 책, 200 MB는 현대 Mac/PC에서 무시 가능한 크기입니다. 실제 고민은 백업 크기와 iCloud 동기화 비용이며, 이 때문에 백업 보관 기간을 설정 가능하게 했습니다.
Q3: 임베딩을 완전 로컬로 돌릴 수 있나요?
A: 로드맵에 있습니다. 차기 버전에서 BGE-small-zh를 옵션 로컬 임베딩 프로바이더로 추가 예정(네트워크 0), 한국어/중국어 recall을 87% → 83%로 낮추는 트레이드오프. 고프라이버시 워크플로우에는 충분히 가치 있습니다.
지금 시작하기
- 공식 사이트: https://geminidesktop.app
- BibiGPT: https://bibigpt.co
- 데스크톱 다운로드: https://bibigpt.co/download/desktop
- 더 많은 기능: https://bibigpt.co/features