이 프로젝트의 9,586 청크는 numpy 배열 내적 + pickle BM25로 충분하다. 검색 지연이 100ms 미만이고, 메모리 사용량도 약 29MB(임베딩 14MB + BM25 인덱스 7.1MB + 토큰화 코퍼스 7.6MB) 수준이다. 하지만 규모가 커지면:
| 청크 수 | 임베딩 크기 (384d) | 내적 검색 시간 | 문제 |
|---|---|---|---|
| 10K | 14MB | <100ms | 없음 |
| 100K | 140MB | ~500ms | 메모리 부담 시작 |
| 1M | 1.4GB | ~5초 | 메모리 한계, 지연 비허용 |
| 10M | 14GB | 불가능 | numpy로는 불가 |
변경 최소화 접근. 현재 코드 구조를 유지하면서 성능만 개선:
| 구성 요소 | 현재 | 변경 |
|---|---|---|
| 벡터 검색 | numpy 내적 | FAISS IndexFlatIP (exact, but optimized) |
| BM25 | rank-bm25 pickle | 동일 (50K까지 in-memory 가능) |
| 메타데이터 | JSONL 파일 | SQLite (chunks.db) |
| 임베딩 생성 | PyTorch SentenceTransformer | ONNX 변환 (변환 스크립트 준비됨: convert_to_onnx.py) |
용어 참고:
# FAISS 적용 예시 (embed.py 변경 부분만)
import faiss
# 빌드 시
index = faiss.IndexFlatIP(384) # Inner Product = cosine (정규화 후)
index.add(embeddings)
faiss.write_index(index, "data/faiss.index")
# 검색 시
index = faiss.read_index("data/faiss.index")
scores, indices = index.search(query_embedding, top_k) # GPU도 가능
근사 검색(ANN) 도입. 정확도를 약간 희생하고 속도를 대폭 확보:
| 구성 요소 | 옵션 A (셀프호스팅) | 옵션 B (매니지드) |
|---|---|---|
| 벡터 검색 | FAISS IndexIVFFlat (클러스터 기반) |
Qdrant (Docker) |
| BM25 | Elasticsearch (Docker) | OpenSearch Serverless |
| 메타데이터 | PostgreSQL | Supabase (PostgreSQL + REST API) |
| 임베딩 생성 | GPU 서버에서 배치 처리 | OpenAI text-embedding-3-small API |
| 리랭커 | 로컬 bge-reranker | Cohere Rerank API |
# FAISS IVF 적용 (100K+ 청크에 효과적)
nlist = 100 # 클러스터 수 (sqrt(N) 정도)
quantizer = faiss.IndexFlatIP(384)
index = faiss.IndexIVFFlat(quantizer, 384, nlist, faiss.METRIC_INNER_PRODUCT)
index.train(embeddings) # 클러스터 학습
index.add(embeddings)
index.nprobe = 10 # 검색 시 탐색할 클러스터 수 (정확도↔속도 트레이드오프)
전용 벡터 DB + 검색 엔진 클러스터:
| 구성 요소 | 옵션 | 특징 |
|---|---|---|
| 벡터 검색 | Pinecone | 완전 매니지드, 서버리스, 자동 스케일링 |
| Weaviate | 하이브리드 검색 내장 (BM25+Dense 단일 쿼리) | |
| Qdrant | Rust 기반 고성능, 셀프호스팅/클라우드 | |
| pgvector + PostgreSQL | 기존 RDB에 벡터 검색 추가 | |
| 키워드 검색 | Elasticsearch 클러스터 | BM25 + 형태소 분석 내장 (nori/ik) |
| 임베딩 | API 서비스 (OpenAI / Cohere) | 로컬 GPU 불필요, 비용은 토큰당 과금 |
| 오케스트레이션 | LangChain / LlamaIndex | 검색→리랭킹→LLM 체인 자동 구성 |
현재 paraphrase-multilingual-MiniLM-L12-v2 (384차원)는 가볍지만 최신 모델 대비 정밀도가 낮다:
| 모델 | 차원 | 크기 | 특징 | 적합한 규모 |
|---|---|---|---|---|
| MiniLM-L12-v2 (현재) | 384 | 120MB | 로컬 실행, 50+언어 | ~50K |
bge-m3 (BAAI) |
1024 | 2.2GB | Dense+Sparse+ColBERT 3-in-1, 다국어 최강 | 50K~500K |
multilingual-e5-large-instruct |
1024 | 2.2GB | 지시형 쿼리에 최적화 | 50K~500K |
text-embedding-3-small (OpenAI) |
1536 | API | 저비용 API, 우수한 다국어 | 100K+ |
embed-multilingual-v3 (Cohere) |
1024 | API | 100+ 언어, 검색 특화 | 100K+ |
교체 시 영향 범위:
embed.py의 모델명 변경 + 배치 크기 조정embeddings.npy 재생성 (차원 변경 → 파일 크기 증가)search.py는 변경 불필요 (numpy 내적은 차원 무관)| 방식 | 장점 | 단점 | 적합한 상황 |
|---|---|---|---|
| 전체 리빌드 | 단순, 일관성 보장 | 규모가 커지면 시간↑ | <50K 청크, 일일 1회 |
| 증분 추가 | 빠름 (변경분만 처리) | 삭제/수정 관리 복잡 | 50K~500K, 실시간성 필요 |
| 증분 + 주기 리빌드 | 실시간 + 품질 | 두 시스템 관리 | 500K+, 프로덕션 |
이 프로젝트는 9,586 청크에서 전체 리빌드가 3~5분이므로 전체 리빌드로 충분하다. 하지만 100K 청크를 넘어가면 증분 인덱싱이 필수적이다.
현재 시스템은 전부 로컬에서 동작한다. 이것을 외부 서비스로 확장하거나 팀에서 공유할 때, 각 단계별로 어떤 옵션이 있는지 정리한다.
| 옵션 | 설명 | 적합한 환경 |
|---|---|---|
| 파일시스템 워칭 | watchdog/inotify로 디렉토리 변경 감지 → 자동 재처리 |
로컬, 소규모 |
| Git 웹훅 | 문서 레포 push 시 CI/CD에서 자동 리빌드 | 기술 문서, IaC |
| Confluence/Notion API 동기화 | REST API로 변경된 페이지 감지 → 증분 처리 | 기업 환경 |
| S3 이벤트 + Lambda | S3에 문서 업로드 시 Lambda로 청킹+임베딩 | AWS 환경, 서버리스 |
| Unstructured.io | PDF/DOCX/PPTX/HTML → 구조화 텍스트 자동 변환 | 다양한 파일 형식 |
| 옵션 | 설명 | 적합한 환경 |
|---|---|---|
| 직접 구현 (현재) | chunk.py: 512자, 단락 경계 존중 |
도메인 맞춤 제어 필요 시 |
| LangChain TextSplitter | RecursiveCharacterTextSplitter: 마크다운 헤딩 인식, 코드 블록 보존 |
범용, 빠른 시작 |
| LlamaIndex SentenceSplitter | 문장 경계 존중, 메타데이터 자동 추출 | 범용 |
| Unstructured.io | 문서 구조 자동 파싱 후 요소 기반 청킹 (제목, 본문, 표, 이미지 캡션 분리) | 비정형 문서 |
| Semantic Chunking | 임베딩 유사도 기반으로 의미가 전환되는 지점에서 분할 | 구조 없는 긴 텍스트 |
| 옵션 | 비용 | 지연 | 적합한 환경 |
|---|---|---|---|
| 로컬 SentenceTransformer (현재) | 무료 | 배치 3~5분 | 로컬 개발, <100K |
| 로컬 ONNX (변환 스크립트 준비됨) | 무료 | 배치 ~2분 | 로컬, CPU 최적화 |
| GPU 서버 | 인스턴스 비용 | 배치 ~30초 | 100K+, 빈번한 리빌드 |
| OpenAI Embedding API | $0.02/1M토큰 | 실시간 | 프로덕션, 관리 부담↓ |
| Cohere Embed API | $0.10/1M토큰 | 실시간 | 다국어 특화 |
| Amazon Bedrock Titan | $0.02/1M토큰 | 실시간 | AWS 환경 |
| 옵션 | BM25 | Dense | 하이브리드 | 적합한 환경 |
|---|---|---|---|---|
| 파일 기반 (현재) | rank-bm25 pickle (2파일: bm25_index + tokenized_corpus) | numpy .npy | 수동 합산 | 로컬, <50K |
| FAISS + Elasticsearch | Elasticsearch | FAISS | 별도 합산 로직 | 셀프호스팅, 50K~1M |
| Weaviate | 내장 BM25 | 내장 HNSW | alpha 파라미터로 자동 |
올인원, 50K~10M |
| Qdrant | (별도 ES 필요) | 내장 HNSW | FastEmbed 연동 | 고성능 벡터 검색 |
| Pinecone | (별도 필요) | 내장 | Sparse+Dense 지원 | 완전 매니지드, 스케일 |
| pgvector + PostgreSQL | pg_trgm / FTS | pgvector | SQL 쿼리로 합산 | 기존 RDB 활용 |
| 옵션 | 비용 | 성능 | 적합한 환경 |
|---|---|---|---|
| bge-reranker-v2-m3 (현재) | 무료 | 40후보/500ms | 로컬, 다국어 |
| Cohere Rerank | $2/1K검색 | 40후보/200ms | 프로덕션, API 기반 |
| Jina Reranker | 무료~유료 | 40후보/300ms | 다국어, API/로컬 |
| ColBERT v2 | 무료 | 토큰 단위 매칭 | 긴 문서, 정밀 매칭 |
| LLM 기반 리랭킹 | 토큰 비용 | 느리지만 최고 정밀 | 소량 고가치 검색 |
RAG로 관리하면 효과적인 데이터:
| 데이터 유형 | RAG 적합도 | 이유 |
|---|---|---|
| 서비스 기획서 | 높음 | 자주 변경, 버전 관리 필요, 팀 간 공유 |
| API 문서 | 매우 높음 | 정확한 용어 매칭 필수, 버전별 차이 |
| 온보딩 가이드 | 높음 | 신규 입사자의 다양한 질문에 대응 |
| 장애 대응 매뉴얼 | 매우 높음 | 긴급 상황에서 빠른 검색 필수 |
| 코드 리뷰 가이드라인 | 중간 | 코드 자체는 IDE 검색이 더 적합 |
| 회의록 | 낮음 | 비정형, 결정사항만 추출 후 구조화 필요 |
| 일일 스탠드업 메모 | 매우 낮음 | 짧은 수명, TTL 기반 만료 처리 |
┌──────────────────────────────────────────────────┐
│ 데이터 소스 │
│ Confluence │ Notion │ Google Docs │ Git (MD) │
└──────┬───────────┬────────────┬──────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────┐
│ 수집 + 정제 레이어 │
│ - API 동기화 (변경분 감지) │
│ - HTML→Markdown 변환 │
│ - 메타데이터 추출 (작성자, 수정일, 라벨, 버전) │
│ - 이전 버전 아카이브 (검색 대상에서 제외) │
│ - 구조 정규화 (헤딩 레벨 통일, 섹션 구분선 삽입) │
└──────────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ 청킹 + 인덱싱 │
│ - 헤딩 기반 청킹 (## 단위로 분할) │
│ - 메타데이터 보존 (문서 제목, 스페이스, 태그) │
│ - 증분 인덱싱 (변경분만 재처리) │
│ - 하이브리드 인덱스 (BM25 + Dense) │
└──────────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ 검색 + 활용 │
│ - Slack/Teams 봇: "결제 API 타임아웃 몇 초?" │
│ - AI Agent: 기획서 기반 코드 생성 │
│ - 온보딩 봇: 신규 입사자 질문 대응 │
│ - 장애 대응: 에러 메시지로 해결 방법 검색 │
└──────────────────────────────────────────────────┘
사주 프로젝트와 기업 환경은 구조는 같지만 운영 요구사항이 다르다:
1. 접근 권한 (ACL)
문서마다 접근 권한이 다르다. HR 문서를 엔지니어가 검색할 수 없어야 한다.
→ 청크 메타데이터에 access_group 필드 추가, 검색 시 필터링
2. 감사 로그
누가 무엇을 검색했는지 추적해야 한다.
→ 검색 쿼리 + 반환 결과 + 사용자 ID 로깅
3. PII(개인정보) 마스킹
기획서에 포함된 개인정보가 RAG 결과로 노출되면 안 된다.
→ 청킹 전 PII 탐지 + 마스킹 파이프라인 추가
4. 다중 테넌시
팀별로 독립된 코퍼스 또는 공유 코퍼스 + 팀별 전용 코퍼스 구조.
→ 네임스페이스(namespace)로 코퍼스 분리, 검색 시 네임스페이스 필터