GeminiDesktop GeminiDesktop
가이드

로컬 sqlite-vec RAG: 50만 자 노트를 200 MB 파일로 인덱싱

게시일 · 작성자: GeminiDesktop Team

로컬 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가지:

  1. 고정 크기 청크(2000자 + 200 overlap) — GeminiDesktop 채택
  2. 재귀적 문자 분할(LangChain 스타일, 문단→문장→문자 계층 강등)
  3. 시맨틱 청크(임베딩 유사도로 “주제 경계” 탐색)

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행마다 commit
  • PRAGMA 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의 해법:

  1. WAL 모드: 다중 리더 + 단일 라이터 병행 비차단
  2. 파일 락: SQLite 자체 fcntl 락, 프로세스 세이프
  3. busy_timeout=5000: 짧은 경합은 5초간 자동 재시도
  4. 쓰기는 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 폴백에 의존.

빠른 시작

  1. GeminiDesktop 다운로드: https://geminidesktop.app
  2. 앱 열기 → 설정 → Gemini API 키 입력
  3. “RAG” 탭 → 인덱스할 폴더 선택(~/Documents/Notes 같은 작은 디렉터리부터 시작 권장)
  4. 인덱싱 완료 후 Claude Code에서 claude mcp add geminidesktop -- /Applications/GeminiDesktop.app/Contents/MacOS/GeminiDesktop mcp 실행
  5. 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%로 낮추는 트레이드오프. 고프라이버시 워크플로우에는 충분히 가치 있습니다.

지금 시작하기