Vector DB 마이그레이션
LHG 2026-06-04
처음 보는 사람도 이해되게 먼저 풀어둔다
편한 관리형 서비스에서 직접 운영하는 오픈소스로 옮기는 이유는 네 가지다
벡터가 천만 건을 넘어가면서 보이던 것이 달라진다
같은 층위의 도구가 아니다. 하나는 라이브러리, 하나는 데이터베이스다
이 차이가 선택을 가른다
가장 먼저 할 일은 지금 인덱스의 정확한 모양을 아는 것이다
from pinecone import Pinecone pc = Pinecone(api_key=PINECONE_API_KEY) index = pc.Index("rag-prod") stats = index.describe_index_stats() print(stats["dimension"], stats["total_vector_count"]) for ns, info in stats["namespaces"].items(): print(ns, info["vector_count"])
여기서 나온 dimension이 새 인덱스의 차원이 된다.
천만 벡터를 옮기다 보면 네트워크 단절은 반드시 일어난다
import faiss, numpy as np D = 1536 index = faiss.IndexFlatIP(D) # cosine을 흉내내려면 정규화 + 내적 vecs = np.asarray(vecs, dtype="float32") faiss.normalize_L2(vecs) index = faiss.IndexIDMap2(index) # 문자열 ID를 모르므로 정수로 매핑 index.add_with_ids(vecs, np.asarray([hash_id(i) for i in ids], dtype="int64")) faiss.write_index(index, "rag-prod.faiss")
import chromadb client = chromadb.PersistentClient(path="./chroma-rag-prod") col = client.get_or_create_collection( name="rag-prod", metadata={"hnsw:space": "cosine"}, # 거리 측정법을 반드시 명시 ) for ids, vecs, metas, docs in iter_parquet_batches("export/rag-prod"): col.add(ids=ids, embeddings=vecs, metadatas=metas, documents=docs)
hnsw:space를 안 쓰면 기본값 L2가 적용돼 결과가 통째로 어긋난다.
hnsw:space
옮기고 나면 반드시 옛 인덱스와 새 인덱스의 결과를 겹쳐본다
def recall_at_k(pinecone_ids, new_ids, k=10): p = set(pinecone_ids[:k]) n = set(new_ids[:k]) return len(p & n) / k scores = [] for q in load_query_samples(1000): pc_top = [m.id for m in index.query(vector=q, top_k=10).matches] new_top = chroma_query(col, q, k=10) scores.append(recall_at_k(pc_top, new_top)) print(f"mean recall@10 = {sum(scores)/len(scores):.3f}")
이전이 끝나도 일은 시작도 안 한 셈이다
임베딩 모델을 바꾸면 인덱스를 통째로 다시 만들어야 한다
비용만 보고 옮기면 운영 비용이 절감액을 넘는 함정에 빠진다