대규모 벡터 검색: 당신의 인덱스가 생각만큼 건강하지 않은 이유
요약
RAG 및 AI 시스템의 핵심 인프라인 벡터 검색의 운영상 문제점과 ANN 인덱스의 작동 원리를 다룹니다. 데이터 변화에 따른 재현율(Recall) 저하와 지연 시간 등 프로덕션 환경에서 발생할 수 있는 실패 모드를 경고합니다.
핵심 포인트
- ANN 인덱스는 속도를 위해 재현율(Recall)을 일부 희생하는 근사 검색 방식임
- 데이터 분포가 변하면 인덱스의 재현율도 함께 변하므로 지속적인 관리가 필요함
- 대규모 환경에서는 재현율 저하, 지연 시간 증가, 유령 결과 등의 문제가 발생할 수 있음
- 벡터 인덱스의 작동 원리를 이해해야 예방 가능한 운영 실패를 막을 수 있음
벡터 검색(Vector search)은 현대 AI 시스템에서 놀라울 정도로 빠르게 핵심적인 기반 인프라가 되었습니다. 1~2년 전만 해도 이는 주로 연구 목적의 호기심 대상이자 시맨틱 검색(Semantic search)을 위한 틈새 도구였습니다. 오늘날 벡터 검색은 RAG(Retrieval-Augmented Generation) 파이프라인, 추천 엔진, 멀티모달 검색 시스템(Multimodal retrieval systems), 그리고 비정형 데이터(Unstructured data)를 바탕으로 추론하는 점점 늘어나는 애플리케이션 계층의 중심에 자리 잡고 있습니다.
하지만 운영 패턴은 이러한 도입 속도를 따라가지 못하고 있습니다.
프로덕션 환경에 벡터 검색을 배포하는 대부분의 팀은 인덱싱(Indexing)을 이해하기 전의 관계형 데이터베이스(Relational databases)를 다루던 방식과 동일하게 이를 취급합니다. 즉, 문제가 발생하기 전까지는 잘 작동하는 인프라로 여기며, 직접 맞닥뜨리기 전까지는 실패 모드(Failure modes)를 제대로 이해하지 못합니다. 규모가 커짐에 따라 발생하는 문제들 — 재현율(Recall) 저하, 예측 불가능한 지연 시간(Latency), 삭제된 레코드에서 발생하는 유령 결과(Ghost results) — 은 예방 가능한 것들입니다. 하지만 이를 예방하려면 벡터 인덱스(Vector indices)가 실제로 어떻게 작동하는지, 그리고 지속적인 변화 속에서 인덱스에 어떤 일이 일어나는지를 이해해야 합니다.
이 포스트는 바로 그 점에 대해 다룹니다.
벡터 검색이 실제로 수행하는 작업
실패 모드에 대해 알아보기 전에, ANN(Approximate Nearest Neighbor, 근사 최근접 이웃) 인덱스가 무엇을 하는지, 그리고 어떤 트레이드오프(Tradeoffs)를 수반하는지 정확히 짚고 넘어갈 가치가 있습니다.
벡터 데이터베이스에 벡터 임베딩(Vector embedding)을 저장할 때, 여러분은 고차원 공간(High-dimensional space) 내의 한 점을 저장하는 것입니다. 이는 임베딩 모델(Embedding model)에 따라 768차원, 1536차원 또는 그 이상의 차원을 가질 수 있는 공간 내의 위치입니다. 벡터 검색 쿼리는 다음과 같이 질문합니다: "쿼리 벡터(Query vector)가 주어졌을 때, 이 공간에서 저장된 벡터 중 어떤 것이 가장 가까운가?"
정확한 최근접 이웃 검색(Exact nearest neighbor search) — 모든 저장된 벡터를 모든 쿼리와 대조하는 방식 — 은 정확하지만 대규모 환경에서는 계산적으로 불가능합니다. 1,000만 개의 벡터가 있을 때, 정확한 검색을 수행하려면 쿼리당 1,000만 번의 거리 계산(Distance computations)이 필요합니다. ANN 인덱스는 검색 시 공간의 대부분을 건너뛰고 높은 확률로 근사적으로 가장 가까운 이웃을 찾을 수 있는 데이터 구조를 구축함으로써 이 문제를 해결합니다.
핵심 단어는 _근사적으로 (approximately)_입니다. ANN (근사 최근접 이웃) 검색은 쿼리 속도의 대폭적인 향상을 위해 제한된 범위 내의 정확도 (Recall, 재현율)를 일부 희생합니다. 잘 조정된 인덱스는 95%의 확률로 실제 가장 가까운 10개의 이웃을 반환할 수 있습니다 — 즉, recall@10이 0.95인 상태입니다. 대부분의 애플리케이션에서 그 5%의 격차는 허용 가능한 수준입니다. 허용할 수 없는 상황은 인덱스가 현재 서비스 중인 데이터 분포와 다른 데이터 분포를 기준으로 구축되었기 때문에, 운영 환경에서 그 격차가 예기치 않게, 그리고 소리 없이 커지는 경우입니다.
재현율 (Recall)은 상수가 아닙니다. 이는 인덱스 구조와 데이터 분포 사이의 관계를 나타내는 속성입니다. 데이터가 변하면 재현율도 함께 변합니다.
대규모 환경에서의 세 가지 실패 모드
1. 지속적인 업데이트 상황에서의 인덱스 성능 저하 (Index Degradation)
가장 널리 배포된 ANN 알고리즘 제품군은 HNSW — Hierarchical Navigable Small World (계층적 탐색 가능 소세계) 그래프입니다. HNSW는 노드 (벡터)들이 자신의 근사적 이웃들과 연결된 계층적 그래프 구조를 구축합니다. 검색은 이 그래프를 가로지르며, 거친(coarse) 계층에서 세밀한(fine) 계층으로 탐색하여 근사 최근접 이웃을 효율적으로 찾아냅니다.
HNSW는 주로 정적 데이터셋을 위해 설계되었습니다. 전체 데이터셋에 대해 인덱스를 한 번 구축하면 매우 뛰어난 성능을 발휘합니다. 문제는 운영 환경의 데이터셋은 정적이지 않다는 점입니다. 새로운 문서, 새로운 제품, 새로운 사용자 프로필 등 새로운 임베딩 (Embedding)이 지속적으로 추가됩니다. 기존 임베딩은 기반 콘텐츠가 변경됨에 따라 업데이트됩니다. 레코드가 삭제되면 오래된 임베딩은 삭제됩니다.
이러한 각 작업은 서로 다른 방식으로 그래프를 저하시킵니다:
**삽입 (Insertions)**은 새로운 노드를 추가하지만, 새로운 추가 사항에 맞춰 기존 노드들의 연결을 소급하여 최적화할 수는 없습니다. 시간이 지남에 따라 그래프의 탐색 가능성(navigability) — 즉, 검색 쿼리를 공간의 올바른 영역으로 효율적으로 라우팅하는 능력 — 이 침식됩니다.
대부분의 구현체에서 **업데이트 (Updates)**는 삭제 후 삽입의 과정을 거칩니다. 삭제는 그래프에 빈 공간(gap)을 남기며, 삽입은 주변 이웃 구조(neighborhood structure)에 완전히 통합되지 않은 채 새로운 노드를 추가합니다. 이러한 업데이트가 반복되면 구조적 부채(structural debt)가 축적됩니다.
**삭제 (Deletions)**는 가장 교활한 문제입니다. 대부분의 HNSW 구현체는 벡터를 그래프 구조에서 완전히 제거하는 대신
이러한 파라미터(Parameters)들은 일반적으로 인덱스 구축 시점에 데이터셋의 크기와 당시의 쿼리 분포(Query distribution)에 맞춰 한 번 튜닝됩니다. 데이터셋이 100만 개에서 1,000만 개, 1억 개의 벡터로 성장함에 따라, 동일한 파라미터 값은 더 낮은 재현율(Recall)을 초래합니다. 100만 개의 벡터를 탐색하기에 충분했던 인덱스 구조는 1억 개의 벡터 환경에서는 관련 결과를 정기적으로 놓칠 수 있습니다. 이는 소규모 규모에서 대부분의 실제 이웃(True neighbors)을 포착할 만큼 충분했던 후보 목록(Candidate list)이, 대규모 규모에서는 동일한 비율로 공간을 샘플링할 만큼 충분하지 않기 때문입니다.
이는 기술적인 문제인 동시에 용량 계획(Capacity planning)의 문제입니다. 인덱스를 한 번 튜닝한 후 해당 파라미터를 영구적인 설정으로 취급하는 팀은, 재현율 저하가 조용하고 점진적인 운영 이슈로 나타나는 상황을 맞이하게 될 것입니다.
3. 임베딩 모델 업데이트 간의 분포 변화 (Distribution Shift)
세 번째 실패 모드는 임베딩 모델(Embedding model) 자체가 변경될 때 발생합니다.
임베딩(Embeddings)은 모델 버전 간에 호환되지 않습니다. text-embedding-ada-002에 의해 생성된 벡터는 text-embedding-3-large에 의해 생성된 벡터와 완전히 다른 기하학적 공간(Geometric space)에 존재합니다. 동일한 임베딩 모델이라 할지라도 마이너 버전 업데이트만으로도 임베딩 공간의 기하학적 구조가 변하여 기존 인덱스를 무효화할 수 있을 만큼 충분히 이동할 수 있습니다.
품질 향상, 비용 절감, 또는 제공업체 전환을 위해 팀이 임베딩 모델을 업데이트할 때, 이들은 마이그레이션(Migration) 문제에 직면합니다. 저장된 벡터들을 새로운 모델을 사용하여 다시 계산해야 하며, 새로운 임베딩에 맞춰 인덱스를 처음부터 다시 구축해야 합니다. 점진적인(Incremental) 경로는 존재하지 않습니다.
이러한 마이그레이션은 대규모 환경에서 비용이 많이 듭니다. 수백만 개의 레코드에 대해 임베딩을 다시 계산하려면 상당한 컴퓨팅 자원과 시간이 소요됩니다. 마이그레이션 기간 동안 시스템은 오래된 인덱스(이전 임베딩, 이전 모델)를 통해 결과를 제공하거나, 전환 기간 동안 두 인덱스 모두에서 결과를 반환하는 복잡한 이중 인덱스 서빙(Dual-index serving) 전략을 관리해야 합니다.
임베딩 모델 마이그레이션 (Embedding model migration)을 계획하지 않은 팀은 업그레이드를 원할 때, 업그레이드 비용을 매우 높게 만드는 의존성을 구축했다는 사실을 깨닫고 나서야 문제를 발견하는 경향이 있습니다.
아키텍처적 대응 (Architectural Responses)
세그먼트 기반 인덱싱 (Segment-Based Indexing)
지속적인 업데이트 문제에 대한 가장 운영적으로 성숙한 대응책은 LSM-tree 데이터베이스(RocksDB 및 Cassandra와 같은)가 쓰기 집약적인 워크로드 (Write-heavy workloads)를 처리하는 방식을 모델로 한 세그먼트 기반 아키텍처입니다.
단일 모놀리식 인덱스 (Monolithic index) 대신, 벡터 저장소는 여러 인덱스 세그먼트 (Index segments)를 유지합니다:
- 핫 세그먼트 (Hot segments): 새로운 벡터를 포함하는 작고 최근에 구축된 세그먼트입니다. 데이터가 오래되면 빠르게 재구축할 수 있습니다.
- 웜 세그먼트 (Warm segments): 중간 정도의 연령을 가진 세그먼트로, 업데이트가 누적됨에 따라 주기적으로 재구축됩니다.
- 콜드 세그먼트 (Cold segments): 최근에 변경되지 않은 벡터를 포함하는 크고 안정적인 세그먼트입니다. 거의 재구축되지 않습니다.
새로운 벡터는 핫 세그먼트에 들어갑니다. 쿼리 실행 시 모든 세그먼트를 검색하고 결과를 병합합니다. 백그라운드 컴팩션 (Compaction)은 작은 세그먼트들을 더 큰 세그먼트로 병합하며, 이 과정에서 그래프 구조 (Graph structure)를 재구축하고 최적화합니다.
새로운 벡터 ──► 핫 세그먼트 (작고, 신선하며, 빠른 재구축 가능)
│
[compaction]
...
이 아키텍처는 모놀리식 인덱스에 비해 몇 가지 장점을 가집니다:
- 삭제 및 업데이트 시 인덱스 전체가 아닌, 해당 벡터가 포함된 세그먼트만 무효화됩니다.
- 핫 세그먼트는 신선도 비용 (Freshness penalty)을 감수할 수 있을 만큼 작고 빠르게 재구축 가능합니다.
- 콜드 세그먼트는 재구축 비용을 장기간에 걸쳐 분할 상환 (Amortize)할 수 있을 만큼 안정적입니다.
- 세그먼트 재구축 중에도 다른 세그먼트들이 사용 가능한 상태로 유지되므로, 시스템이 계속해서 쿼리를 처리할 수 있습니다.
트레이드오프 (Tradeoff)는 쿼리 복잡성입니다. 여러 세그먼트를 검색하고 결과를 병합하는 것은 단일 인덱스를 검색하는 것보다 더 복잡하며, 병합 단계에서 지연 시간 (Latency)이 추가됩니다. 실질적인 오버헤드는 대개 허용 가능한 수준이지만, 명시적인 설계가 필요합니다.
프로덕션 지표로서의 재현율 모니터링 (Recall Monitoring as a Production Metric)
벡터 검색을 위한 가장 중요한 운영 관행 중 하나는 대부분의 팀이 건너뛰는 것입니다: 바로 런타임 지표로서의 재현율 (Recall) 추적입니다.
오프라인 평가 (Offline evaluation)에서 재현율은 정답 테스트 세트 (Ground-truth test set)를 기준으로 계산되는 벤치마크 수치입니다. 프로덕션 환경에서는 이를 측정하기가 더 어렵습니다. 실시간 쿼리에 대한 실제 최근접 이웃 (True nearest neighbors)을 항상 알 수는 없기 때문입니다. 하지만 대리 지표 (Proxies)를 활용하는 것은 가능합니다:
정기적인 정답 샘플링 (Periodic ground-truth sampling): 프로덕션 쿼리 샘플에 대해 정확한 검색 (Exact search, Brute-force)을 실행하고 그 결과를 ANN 결과와 비교합니다. ANN이 반환한 실제 최근접 이웃의 비율이 곧 재현율 추정치가 됩니다.
결과 세트 안정성 (Result set stability): 동일한 인덱스를 사용하여 연속적으로 실행했을 때 동일한 쿼리가 현저히 다른 결과를 반환한다면, 해당 인덱스는 조사할 가치가 있는 구조적 불일치 (Structural inconsistencies)를 가지고 있는 것입니다.
선행 지표로서의 지연 시간 (Latency as a leading indicator): 특히 HNSW의 경우, 그래프를 탐색하기가 점점 더 어려워짐에 따라 쿼리 지연 시간 (Query latency)의 증가가 재현율 저하보다 먼저 나타나는 경우가 많습니다. 지연 시간 추세가 쿼리량 추세와 다르게 나타난다면, 재현율이 떨어지기 전에 조사해 볼 가치가 있습니다.
def estimate_recall(query_vectors, k=10, sample_size=100):
sample = random.sample(query_vectors, sample_size)
recall_scores = []
...
이를 전체 규모로 지속적으로 실행하는 것은 비용이 많이 들기 때문에 샘플링이 필수적입니다. 하지만 시간 단위로, 또는 인덱스 업데이트 볼륨 임계값에 의해 트리거되도록 정기적으로 실행하면 재현율 저하가 사용자에게 눈에 보이기 전에 조기 경보를 받을 수 있습니다.
하이브리드 검색을 위한 프리 필터링 vs 포스트 필터링 (Pre-filtering vs. Post-filtering for Hybrid Search)
프로덕션 벡터 검색은 거의 항상 순수한 의미론적 유사성 (Semantic similarity)만으로 이루어지지 않습니다. 실제 워크로드에서는 벡터 검색 위에 메타데이터 필터 (Metadata filters)를 적용합니다. 예를 들어, 재고가 있는 가장 유사한 항목, 사용자의 언어로 된 가장 관련성 높은 문서, 매출 임계값 이상의 가장 관련 있는 고객 등을 찾는 식입니다.
메타데이터 필터링을 ANN 검색과 결합하는 데에는 세 가지 아키텍처 패턴이 있으며, 각 패턴은 서로 다른 성능 및 정확도 프로필을 가집니다:
Post-filtering (사후 필터링): 모든 벡터에 대해 광범위하게 ANN (Approximate Nearest Neighbor) 검색을 수행한 다음, 결과에 메타데이터 필터 (metadata filter)를 적용합니다. 구현은 간단하지만 낭비가 심합니다. 만약 필터의 선택도 (selectivity)가 매우 높다면 (예: 벡터의 1%만 통과하는 경우), 필터링 후 K개의 결과를 얻기 위해 ANN으로부터 K개보다 훨씬 더 많은 후보를 검색해야 합니다. 선택도가 높은 필터 조건에서는 재현율 (Recall)이 급격히 떨어질 수 있습니다.
Pre-filtering (사전 필터링): 메타데이터 필터를 먼저 적용하여 후보 집합 (candidate set)을 얻은 다음, 해당 집합 내에서 정확한 검색 (exact search) 또는 근사 검색 (approximate search)을 수행합니다. 선택도가 높은 필터 조건에서 더 정확하지만, 후보 집합이 효율적인 검색이 가능할 정도로 충분히 작아야 합니다. 대규모 데이터셋에서 선택도가 매우 높은 필터를 사용할 경우, 이는 수백만 개의 벡터를 실체화 (materializing)하고 검색해야 함을 의미할 수 있습니다.
In-graph filtering (그래프 내 필터링): 인덱스 구조 자체에 필터 인지 (filter awareness) 기능을 구축하여, 별도의 사전 또는 사후 필터링 단계 없이 그래프 탐색 (graph traversal)이 필터 제약 조건을 준수하도록 합니다. 구현은 더 복잡하지만, 사후 필터링의 재현율 저하와 사전 필터링의 후보 실체화 비용을 모두 피할 수 있습니다. 이는 더 성숙한 벡터 데이터베이스 (vector database) 구현체에서 나타나고 있는 방식입니다.
올바른 선택은 쿼리 분포 (query distribution), 즉 필터가 평균적으로 얼마나 선택적인지에 따라 달라집니다. 대부분의 쿼리가 데이터셋의 큰 부분을 필터링한다면 사후 필터링이 잘 작동합니다. 쿼리가 빈번하게 매우 높은 선택도를 가진다면, 그래프 내 필터링이나 세심하게 설계된 사전 필터링 전략이 필요합니다.
이는 단순히 평균적인 사례가 아니라, 실제 쿼리 분포를 바탕으로 검증할 가치가 있는 결정입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기