필터링된 벡터 검색이 K개 미만의 결과를 반환하는 이유
요약
RAG 시스템에서 메타데이터 필터링 시 발생하는 사전 필터링과 사후 필터링의 차이점을 분석합니다. 필터링 전략에 따라 검색 결과의 수가 부족해지거나 성능이 저하될 수 있는 이유와 해결 방안을 다룹니다.
핵심 포인트
- 사후 필터링은 벡터 검색 후 결과를 제거하므로 결과 집합이 부족할 수 있음
- 사전 필터링은 후보군을 먼저 줄여 정확한 결과 반환을 보장함
- 오버페칭(Overfetching)은 결과 부족 위험을 줄이지만 지연 시간을 증가시킴
- 필터의 선택도와 벡터 분포에 따라 적절한 필터링 전략 선택이 필수적임
벡터 유사도(Vector similarity)가 결과의 유용성을 결정하는 유일한 규칙인 경우는 드뭅니다.
지원 어시스턴트(support assistant)는 특정 테넌트와 제품 버전에 대한 문서를 필요로 할 수 있습니다. 코드 어시스턴트(code assistant)는 활성 저장소(repository)와 브랜치(branch)가 필요할 수 있습니다. 내부 RAG 시스템은 모델에 텍스트를 반환하기 전에 권한, 게시 상태, 언어 및 날짜 범위 등을 강제해야 할 수도 있습니다.
이러한 제약 조건들은 일반적으로 메타데이터 필터(metadata filters)라고 불립니다. 하지만 API 요청에 필터를 추가하는 것이 언제 필터가 적용되는지 또는 얼마나 많은 벡터 검색 작업을 회피하는지는 알려주지 않습니다.
이 구별은 중요합니다:
필터는 벡터 검색을 줄이지 않으면서 최종 결과 집합(result set)을 줄일 수 있습니다.
본 글은 그 실행 경계(execution boundary)에 초점을 맞춥니다. 후보 집합(candidate set)을 줄이는 방법에 대한 더 광범위한 가이드는 RAG Retrieval Optimization: Reduce Vector Search Before Ranking에서 확인할 수 있습니다.
벡터 검색에서의 사전 필터링(Pre-filtering) 대 사후 필터링(Post-filtering)
백만 개의 문서 벡터에 대한 검색을 고려해 봅시다:
tenant_id = "acme"
product = "billing"
language = "en"
...
만약 이 다섯 가지 조건들을 모두 만족하는 벡터가 2,000개밖에 없다면 어떨까요? 애플리케이션은 가장 유사한 적격 문서 10개를 요청합니다.
시스템이 이러한 요청을 실행할 수 있는 방법은 여러 가지가 있습니다:
| 전략 | 간소화된 실행 |
|---|---|
| Post-filtering (사후 필터링) | 광범위하게 검색한 다음, 필터를 통과하지 못하는 결과를 제거함 |
| ... |
인덱스의 다른 곳에 일치하는 문서가 존재하더라도, 어떤 방식은 결과가 전혀 생성되지 않을 수도 있습니다.
오버페칭 (Overfetching)은 그러한 위험을 줄여줍니다:
원하는 결과 수: 10
초기 벡터 후보: 500
메타데이터 필터 적용
...
하지만 적절한 오버페칭 계수 (overfetch factor)는 필터의 선택도 (selectivity)와 적격 벡터가 벡터 공간 (vector space)에 어떻게 분포되어 있는지에 따라 달라집니다. 고정된 배수는 특정 테넌트 (tenant)에게는 작동할 수 있지만, 다른 테넌트에게는 실패할 수 있습니다. 재시도 루프 (retry loop)를 통해 완전성을 개선할 수는 있지만, 이는 작업량을 늘리고 꼬리 지연 시간 (tail latency)을 예측하기 어렵게 만듭니다.
필터가 광범위하거나, 후보 풀 (candidate pool)이 이미 작거나, 가끔 결과 집합이 짧아지는 것이 허용되는 경우에는 사후 필터링 (Post-filtering)이 여전히 합리적입니다. 하지만 엄격한 권한 부여 경계 (authorization boundaries)나 선택도가 매우 높은 필터의 경우, 사후 필터링은 적절한 기본 설정이 아닙니다.
Weaviate의 문서는 동일한 두 가지 사후 필터링 위험을 설명합니다: 예측 불가능한 결과 수와 제한적인 필터로 인해 초기 벡터 결과에서 일치하는 항목이 남지 않을 가능성입니다. Weaviate의 구현 방식은 대신 허용 목록 (allow list)을 사용한 사전 필터링 (pre-filtering)을 사용합니다.
사전 필터링은 적격 검색 공간을 변경합니다
사전 필터링 (Pre-filtering)은 유사도 순위 지정 (similarity ranking) 전에 어떤 레코드가 적격한지를 결정합니다.
단순화된 계획은 다음과 같습니다:
권한 부여 필터 (authorization filter)
-> 테넌트 및 제품 범위 (tenant and product scope)
-> 적격 벡터 ID (eligible vector IDs)
...
이를 통해 결과 계약 (result contract)을 더 이해하기 쉽게 만듭니다. 즉, 최근접 이웃 (nearest neighbors)이 전역적으로 선택되어 나중에 확인되는 것이 아니라, 적격한 집합 내에서 선택됩니다.
하지만 "사전 필터링"이 엔진이 모든 적격 벡터에 대해 반드시 정확한 스캔 (exact scan)을 수행한다는 것을 의미하지는 않습니다. 구현 방식에 따라 역색인 메타데이터 인덱스 (inverted metadata index)와 ANN 그래프를 결합하거나, 그래프 탐색 (graph traversal)에 허용 목록을 전달하거나, 카디널리티 (cardinality)에 따라 정확한 검색 (exact search)과 근사 검색 (approximate search) 사이를 선택하거나, 또는 다른 필터 인식 전략 (filter-aware strategy)을 사용할 수 있습니다.
따라서 중요한 질문은 실무적인 것입니다:
- 결과가 top-k 세트에 진입하기 전에 적격성 (eligibility)이 확립되는가?
- 벡터 인덱스 (vector index)가 선택적인 필터 (selective filter) 하에서 효율적으로 탐색 (traverse)할 수 있는가?
- 엔진이 특정 필터 형태에 대해 전수 조사 (exact scan)로 전환되는가?
- 어떤 메타데이터 필드 (metadata fields)가 자체적인 인덱스 (indexes)를 필요로 하는가?
- 적격 세트 (eligible set)가 비어 있거나 매우 작은 경우에는 어떤 일이 발생하는가?
예를 들어, Qdrant는 필터에 사용되는 필드에 대해 페이로드 인덱스 (payload indexes)를 생성할 것을 권장하며, 가급적 데이터 삽입 (ingestion) 전에 생성하는 것이 좋습니다. Qdrant의 문서에서는 가용성 (availability), 위치 (location), 가격 (price)과 같은 필드들을 임베딩 (embeddings)이 신뢰성 있게 표현하지 못하는 비즈니스 제약 조건 (business constraints)으로 취급합니다.
필터 선택도 (filter selectivity)가 벡터 검색 성능을 변화시키는 방식
필터 선택도 (filter selectivity)는 코퍼스 (corpus) 중에서 적격한 상태로 남는 비율을 의미합니다. 100만 개의 레코드 중 80만 개와 일치하는 필터는 80%의 선택도를 가집니다. 2,000개의 레코드와 일치하는 필터는 0.2%의 선택도를 가집니다.
이 수치는 최적의 실행 계획 (execution plan)을 바꿀 수 있습니다:
- 광범위한 필터 (broad filter)는 많은 벡터 후보 (vector candidates)를 제거하지 않으면서 메타데이터 작업만 추가할 수 있습니다.
- 선택적인 사후 필터 (selective post-filter)는 거의 모든 ANN 결과를 폐기할 수 있습니다.
- 선택적인 사전 필터 (selective pre-filter)는 적격 세트에 대한 전수 조사 (exact scan)를 실용적으로 만들 수 있습니다.
- 중간 단계의 필터 (intermediate filter)는 필터 인식 그래프 탐색 (filter-aware graph traversal)이 필요할 수 있습니다.
- 안정적이고 선택도가 높은 범위 (scope)는 라우팅 (routing) 또는 파티셔닝 (partitioning)으로 더 잘 표현될 수 있습니다.
최종 결과 수(result count)만 보고 성능을 추론하지 마세요. 두 개의 쿼리가 모두 10개의 행을 반환하더라도, 하나는 수천 개의 더 많은 벡터 후보를 고려하고 있을 수 있습니다. 모든 중요한 필터 형태에 대해 적격 카디널리티 (eligible cardinality)와 벡터 작업량을 기록하세요.
메타데이터 성능보다 메타데이터 정확성이 우선이다
필터링은 단순한 최적화가 아닙니다. 일부 필터는 문서가 고려될 수 있는지 여부 자체를 정의합니다.
RAG 검색 파이프라인 (retrieval pipeline)을 위한 유용한 순서는 다음과 같습니다:
- 권한 부여 (authorization) 및 테넌트 격리 (tenant isolation) 강제;
- 저장소 (repository), 제품 (product), 또는 버전 (version)과 같이 안정적인 애플리케이션 범위 (application scope) 선택;
- 상태 (status), 언어 (language), 날짜 (date)와 같은 동적 메타데이터 (dynamic metadata) 적용;
- 벡터 (vector) 또는 하이브리드 (hybrid) 후보 순위 지정 (candidate ranking) 수행;
- 제한된 후보 집합 (bounded candidate set)에 대한 재순위 지정 (rerank) 수행;
- 필드 투영 (project fields) 및 컨텍스트 예산 (context budget) 강제.
권한 부여 (Authorization)는 부적격한 벡터가 우연히 top-k 컷오프 (cutoff)를 벗어났는지 여부에 의존해서는 안 됩니다. 이는 관련성 순위 지정 (relevance ranking)과는 독립적으로 엄격한 경계 (hard boundary)로서 강제되어야 합니다.
메타데이터 모델 또한 명시적인 의미론 (semantics)이 필요합니다. 예를 들어:
| 필드 (Field) | 유용한 질문 |
|---|---|
tenant_id | 이것은 보안 경계 (security boundary)인가요, 라우팅 경계 (routing boundary)인가요, 아니면 둘 다인가요? |
| ... |
벡터 데이터베이스 (vector database)는 임베딩 (embedding)으로부터 이러한 정책을 추론할 수 없습니다. 필드의 의미와 이를 결합하는 규칙은 여전히 애플리케이션이 소유합니다.
테넌트 범위 벡터 검색 (Tenant-scoped vector search): 필터인가 네임스페이스인가?
네임스페이스 (Namespaces)와 파티션 (partitions)은 벡터 순위 지정 (vector ranking) 전에 검색 공간을 줄일 수 있지만, 메타데이터 인덱스 (metadata indexes)를 대체할 수는 없습니다.
좋은 라우팅 (routing) 또는 파티션 경계는 일반적으로 다음과 같습니다:
- 검색 (retrieval) 전에 미리 알 수 있음;
- 레코드가 끊임없이 이동하지 않을 정도로 충분히 안정적임;
- 상당한 양의 관련 없는 데이터를 제외할 수 있을 정도로 충분히 선택적임;
- 권한 부여 (authorization) 또는 애플리케이션 동작에 의미가 있음;
- 관리 가능한 수의 범위 (scopes)로 제한됨.
테넌트 (Tenant), 저장소 (repository), 제품 (product), 코퍼스 소스 (corpus source)가 이에 해당하는 경우가 많습니다. 자유 형식의 태그 (free-form tags), 임시 UI 필터, 임의의 가격 범위, 빈번하게 변경되는 상태 값 등은 일반적으로 메타데이터 필터링 (metadata filtering)에 더 적합합니다.
멀티 테넌트 (multi-tenant) 벡터 검색의 경우, 선택은 워크로드 (workload)에 따라 달라집니다. 네임스페이스 (namespace) 또는 라우팅된 파티션 (routed partition)은 테넌트 범위 (tenant scope)를 명시적으로 만들며 테넌트 간 검색 (cross-tenant search) 작업을 피할 수 있습니다. tenant_id 메타데이터 필터는 쿼리가 정당하게 여러 테넌트에 걸쳐 있을 수 있거나, 많은 물리적 범위 (physical scopes)를 생성하는 것이 운영상 비용이 많이 들 때 더 유연합니다.
어느 쪽도 인증을 자동으로 구현하지는 않습니다. 테넌트(Tenant) 식별자는 인증된 컨텍스트(authenticated context)에서 와야 하며, 모든 검색 경로(retrieval path)는 동일한 정책을 강제해야 합니다.
이는 계층적 설계(layered design)를 만듭니다:
stable scope
-> dynamic metadata filter
-> vector or hybrid ranking
...
안정적인 스코프(stable scope)는 첫 번째 문제를 작게 만듭니다. 메타데이터 필터(Metadata filters)는 해당 스코프 내부의 조건을 표현합니다. 벡터 유사도(Vector similarity)가 나머지 의미적 후보군을 순서화합니다.
필터링된 벡터 검색에서 스캔되는 벡터 측정 방법
결과 10개를 반환한다고 해서 단지 10개의 벡터만 고려되었다는 의미는 아닙니다. 필터링된 벡터 검색을 평가할 때는 최소한 다음 항목들을 기록해야 합니다:
- 코퍼스(corpus) 내 총 벡터 수;
- 선택된 네임스페이스 또는 파티션의 벡터 수;
- 메타데이터 필터링 후 적격한 레코드(records);
- 방문하거나 점수가 매겨진 벡터 후보군(vector candidates);
- 필터링 후 남은 결과;
- 적격한 실제 값에 대한 k에서의 재현율(recall at k);
- 빈 결과 및 짧은 결과 비율(empty-result and short-result rates);
- p50 및 p95 검색 지연 시간(retrieval latency);
- 리랭커(reranker)로 전달된 후보군;
- 모델에 전달된 토큰 수.
여러 필터 선택도(filter selectivities)를 테스트하세요. 컬렉션의 80%와 일치하는 쿼리는 0.1%와 일치하는 쿼리와는 다른 실행 형태를 가집니다. 또한 상관관계(correlation)도 테스트하세요. 적격한 벡터들은 벡터 공간에 함께 군집화되어 있거나, 그곳에 흩어져 있을 수 있습니다.
마지막으로, 의도적으로 잘못된 스코프를 평가에 포함시키세요. 좁은 쿼리가 빠를 수 있는 이유는 올바른 문서를 제외했기 때문일 수 있습니다. 재현율(recall)과 인증 정확성(authorization correctness)이 온전하게 유지되지 않는 한 지연 시간 개선은 유용하지 않습니다.
실질적인 결정 체크리스트
벡터 검색 메타데이터 필터링을 배포하기 전에, 다음 질문들을 해보세요:
- 어떤 필터가 관련성 힌트 (relevance hints)가 아닌 권한 규칙 (authorization rules)인가요?
- 필터가 벡터 top-k가 형성되기 전(pre-filtering)에 적용되나요, 아니면 후(post-filtering)에 적용되나요?
- 어떤 필터 필드에 인덱스 (indexes)가 있나요?
- 필터 선택도 (selectivity)가 변함에 따라 성능이 어떻게 변하나요?
- 안정적인 네임스페이스 (namespace)나 애플리케이션 라우트 (application route)를 통해 검색 공간을 먼저 줄일 수 있나요?
- 과도한 데이터 가져오기 (overfetching)가 불완전한 사후 필터링 (post-filtering) 설계를 숨기고 있지는 않나요?
- 재현율 (recall), 검사된 벡터 수, 지연 시간 (latency), 그리고 컨텍스트 토큰 (context tokens)을 함께 측정하고 있나요?
- 선택한 범위 (scope)가 잘못되었거나 비어 있을 때의 폴백 (fallback) 전략은 무엇인가요?
필터링된 벡터 검색 FAQ
메타데이터 필터링이 벡터 검색의 작업량을 줄여주나요?
엔진이 벡터 후보 선택 전이나 도중에 필터를 사용하거나, 애플리케이션이 쿼리를 더 작은 범위 (scope)로 라우팅할 때만 그렇습니다. 사후 필터링 (post-filter)은 원래의 벡터 검색을 변경하지 않은 채 반환되는 행 (rows)의 수만 줄일 수 있습니다.
왜 벡터 검색이 k개보다 적은 결과를 반환하나요?
코퍼스 (corpus)에 조건에 맞는 레코드가 k개보다 적게 포함되어 있을 수 있습니다. 만약 조건에 맞는 레코드가 충분히 존재한다면, 사후 필터링 (post-filter)이 초기 ANN 후보를 너무 많이 제거했거나, 근사 필터링 검색 (approximate filtered search)이 k개의 일치 항목을 찾기 전에 검색 예산 (search budget)을 모두 소진했을 수 있습니다.
사전 필터링 (pre-filtering)이 벡터 검색의 재현율 (recall)을 떨어뜨리나요?
재현율은 조건에 맞는 정답 집합 (ground truth)을 기준으로 측정해야 합니다. 올바른 필터는 의도적으로 범위를 벗어난 레코드를 제외하지만, 잘못된 필터는 정답을 제외할 수 있습니다. 필터링된 ANN 탐색 (traversal)은 필터링되지 않은 그래프와 다른 재현율 동작을 보일 수 있으므로, 동일한 대상 집합에 대한 정확한 검색 (exact search)과 비교해야 합니다.
테넌트 ID (tenant ID)는 메타데이터 필터를 사용해야 하나요, 아니면 네임스페이스 (namespace)를 사용해야 하나요?
테넌트 범위 (tenant scope)가 안정적이고 선택적이며, 거의 모든 쿼리가 하나의 테넌트에 속한다면 네임스페이스 (namespace)나 라우팅 경계 (routing boundary)를 사용하세요. 범위가 동적이거나 합법적인 교차 테넌트 (cross-tenant) 쿼리가 흔한 경우에는 인덱싱된 메타데이터 필터를 사용하세요. 일부 시스템은 이 두 가지를 결합합니다. 즉, 테넌트 수준의 범위로 라우팅한 다음 그 내부에서 메타데이터 필터를 적용합니다.
다른 접근 방식: KoutenDB의 배치 인식 검색 (placement-aware retrieval)
메타데이터 필터링 (Metadata filtering)이 랭킹 (ranking) 전 벡터 연산량을 줄이는 유일한 방법은 아닙니다.
데이터가 저장되는 동안 유용한 검색 범위 (search scope)를 예측할 수 있다면, 배치 (placement) 자체로 첫 번째 후보 경계 (candidate boundary)를 제공할 수 있습니다.
KoutenDB는 Nim 언어로 작성된 오픈 소스 문서 및 벡터 데이터베이스 (vector database)입니다.
KoutenDB는 전통적인 메타데이터 필터로서 링 (ring)을 구현하지 않습니다. 링은 데이터가 배치될 때 선택되는 애플리케이션 정의 로컬리티 좌표 (locality coordinate)입니다. 동일한 좌표는 나중에 검색을 위한 시작 범위 (starting scope)가 될 수 있습니다.
예를 들어, 서로 다른 Laravel 릴리스 (releases)에 대한 문서는 별도의 링에 유지될 수 있는 반면, 스텔라 렌즈 (stellar lens)는 이들이 동일한 프레임워크 컨텍스트 (framework context)에 속한다는 것을 기록할 수 있습니다. 렌즈는 가시성 메타데이터 (visibility metadata)를 변경할 뿐, 링 사이에서 문서 페이로드 (document payloads)를 복사하지 않습니다:
import koutendb
var db = koutendb.open(dataDir = "data")
...
스텔라 읽기 (stellar read)는 Laravel 10 및 Laravel 11의 결과를 원래의 링에 따라 그룹화된 상태로 유지하므로, 애플리케이션은 버전 경계를 무너뜨리지 않고도 이들을 연관된 것으로 볼 수 있습니다. 단 하나의 멤버 좌표만 필요한 경우 subrings를 통해 스텔라 읽기 범위를 좁힐 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기