본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 10장: 메타데이터 필터링과 고급 쿼리
2026년 3월 4일·AI / ML·

10장: 메타데이터 필터링과 고급 쿼리

사전 필터링과 사후 필터링의 차이, 필터 인덱스 설계, 복합 필터 조건, 지오 필터, 멀티테넌시 필터 패턴, 성능 최적화 전략을 다룹니다.

16분511자9개 섹션
vector-databaseaiembeddingsearchinfrastructure
공유
vector-database10 / 11
1234567891011
이전9장: 하이브리드 검색 구현다음11장: 운영, 모니터링, 스케일링 전략

학습 목표

  • **사전 필터링(Pre-filtering)**과 **사후 필터링(Post-filtering)**의 차이와 트레이드오프를 이해합니다
  • 필터 인덱스 설계 원칙을 학습합니다
  • 복합 필터(AND/OR/NOT)와 범위 쿼리의 구현 방법을 익힙니다
  • 멀티테넌시 환경에서의 필터링 패턴을 파악합니다
  • 필터링 성능을 최적화하는 전략을 학습합니다

메타데이터 필터링이 중요한 이유

실제 프로덕션 환경에서 순수 벡터 검색만으로 충분한 경우는 드뭅니다. 대부분의 애플리케이션에서는 벡터 유사도와 함께 구조화된 조건을 적용해야 합니다.

예를 들어 이커머스 검색에서 "빨간색 운동화와 비슷한 상품"을 찾을 때, 벡터 유사도만 사용하면 다른 카테고리의 빨간색 제품이 섞여 나올 수 있습니다. category = "shoes" AND color = "red" AND price <= 100000 같은 필터를 함께 적용해야 실용적인 결과를 얻을 수 있습니다.

Info

벡터 데이터베이스 벤더들의 사용 패턴 분석에 따르면, 프로덕션 쿼리의 70% 이상이 메타데이터 필터링을 포함합니다. 필터링 성능은 전체 검색 성능에 직접적인 영향을 미칩니다.

사전 필터링 vs 사후 필터링

메타데이터 필터를 적용하는 시점에 따라 크게 두 가지 전략으로 나뉩니다.

사후 필터링 (Post-filtering)

벡터 검색을 먼저 수행한 뒤, 결과에 메타데이터 필터를 적용합니다.

장점: 벡터 검색 성능에 영향 없음, 구현이 단순

단점: 필터 선택성(selectivity)이 높으면 (매칭 비율이 낮으면) 결과가 비어 나올 수 있음. Top 100을 가져왔는데 필터를 통과하는 것이 3개뿐이라면, 실질적으로 Top 3만 반환됩니다.

사전 필터링 (Pre-filtering)

메타데이터 조건에 맞는 벡터만 대상으로 ANN 검색을 수행합니다.

장점: 항상 정확한 수의 결과를 반환, 필터 조건을 완벽히 만족

단점: 필터링이 ANN 인덱스와 통합되어야 하므로 구현이 복잡. 필터로 후보가 극단적으로 줄면 ANN 인덱스의 효율이 떨어질 수 있음

각 솔루션의 접근 방식

솔루션기본 전략비고
Pinecone사전 필터링내부적으로 최적화된 사전 필터
Weaviate사전 필터링HNSW + 역인덱스 통합
Qdrant사전 필터링페이로드 인덱스 기반
Milvus사전 필터링파티션 + 스칼라 인덱스
pgvector사전 필터링SQL WHERE + HNSW
Tip

현대 벡터 데이터베이스 대부분은 사전 필터링을 기본으로 사용합니다. 다만 내부 구현은 솔루션마다 다릅니다. 일부는 "사전 필터링처럼 보이지만 실제로는 더 정교한 하이브리드 전략"을 사용합니다.

필터 인덱스 설계

메타데이터 필터링의 성능은 필터 인덱스 설계에 달려 있습니다. 자주 사용하는 필터 필드에는 전용 인덱스를 생성해야 합니다.

Qdrant의 페이로드 인덱스

qdrant_payload_index.py
python
from qdrant_client.models import PayloadSchemaType
 
# 키워드 인덱스 (정확한 일치)
client.create_payload_index(
    collection_name="products",
    field_name="category",
    field_schema=PayloadSchemaType.KEYWORD
)
 
# 정수 인덱스 (범위 쿼리)
client.create_payload_index(
    collection_name="products",
    field_name="price",
    field_schema=PayloadSchemaType.INTEGER
)
 
# 전문 검색 인덱스
client.create_payload_index(
    collection_name="products",
    field_name="description",
    field_schema=PayloadSchemaType.TEXT
)
 
# 지리 인덱스
client.create_payload_index(
    collection_name="stores",
    field_name="location",
    field_schema=PayloadSchemaType.GEO
)

Weaviate의 인덱스 설정

Weaviate는 프로퍼티 정의 시 인덱스 옵션을 설정합니다.

weaviate_index_config.py
python
import weaviate.classes.config as wc
 
client.collections.create(
    name="Product",
    properties=[
        wc.Property(
            name="category",
            data_type=wc.DataType.TEXT,
            indexFilterable=True,     # 필터 인덱스 활성화
            indexSearchable=False,    # BM25 검색 인덱스 비활성화
            skip_vectorization=True
        ),
        wc.Property(
            name="price",
            data_type=wc.DataType.NUMBER,
            indexFilterable=True,
            indexRangeFilters=True,   # 범위 쿼리 인덱스
            skip_vectorization=True
        ),
        wc.Property(
            name="description",
            data_type=wc.DataType.TEXT,
            indexFilterable=False,
            indexSearchable=True,     # BM25 검색 대상
        ),
    ]
)

복합 필터 조건

실제 애플리케이션에서는 단일 조건이 아닌 복합 필터가 필요합니다.

AND/OR/NOT 조합

complex_filters.py
python
# Qdrant: 복합 필터
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
 
complex_filter = Filter(
    must=[
        # AND 조건
        FieldCondition(key="category", match=MatchValue(value="electronics")),
        FieldCondition(key="in_stock", match=MatchValue(value=True)),
    ],
    should=[
        # OR 조건 (하나 이상 만족)
        FieldCondition(key="brand", match=MatchValue(value="Samsung")),
        FieldCondition(key="brand", match=MatchValue(value="Apple")),
    ],
    must_not=[
        # NOT 조건
        FieldCondition(key="status", match=MatchValue(value="discontinued")),
    ]
)
 
results = client.query_points(
    collection_name="products",
    query=query_vector,
    query_filter=complex_filter,
    limit=10
)
complex_filters_weaviate.py
python
# Weaviate: 복합 필터
from weaviate.classes.query import Filter
 
results = articles.query.near_text(
    query="검색어",
    filters=(
        Filter.by_property("category").equal("electronics") &
        Filter.by_property("in_stock").equal(True) &
        (
            Filter.by_property("brand").equal("Samsung") |
            Filter.by_property("brand").equal("Apple")
        )
    ),
    limit=10
)

범위 쿼리

range_queries.py
python
# Qdrant: 가격 범위 + 날짜 범위
range_filter = Filter(
    must=[
        FieldCondition(
            key="price",
            range=Range(gte=10000, lte=50000)
        ),
        FieldCondition(
            key="created_at",
            range=Range(gte="2024-01-01T00:00:00Z")
        ),
    ]
)

지오 필터 (Geo Filtering)

위치 기반 검색에서는 벡터 유사도와 지리적 근접성을 결합해야 합니다.

geo_filtering.py
python
# Qdrant: 반경 내 검색
from qdrant_client.models import GeoRadius, GeoPoint
 
geo_filter = Filter(
    must=[
        FieldCondition(
            key="location",
            geo_radius=GeoRadius(
                center=GeoPoint(lat=37.5665, lon=126.9780),  # 서울 시청
                radius=5000  # 5km 반경
            )
        )
    ]
)
 
# 벡터 유사도 + 5km 반경 내 매장 검색
results = client.query_points(
    collection_name="stores",
    query=query_vector,
    query_filter=geo_filter,
    limit=10
)
Info

지오 필터는 "반경 검색(radius)"과 "바운딩 박스(bounding box)" 두 가지 방식을 지원합니다. 도시 단위 검색에는 바운딩 박스가, 특정 지점 주변 검색에는 반경이 적합합니다. Qdrant와 Weaviate 모두 지오 필터를 네이티브로 지원합니다.

멀티테넌시 필터 패턴

SaaS 애플리케이션에서 여러 고객의 데이터를 하나의 벡터 DB에서 관리할 때, 테넌트 격리는 필수입니다. 주요 패턴을 비교합니다.

패턴 1: 메타데이터 필터 기반

모든 벡터에 tenant_id 필드를 추가하고 검색 시 필터로 격리합니다.

tenant_filter_pattern.py
python
# 삽입 시 테넌트 ID 포함
client.upsert(
    collection_name="documents",
    points=[
        PointStruct(
            id=1,
            vector=[0.1, 0.2, ...],
            payload={
                "tenant_id": "tenant-a",
                "content": "문서 내용"
            }
        )
    ]
)
 
# 검색 시 테넌트 ID 필터
results = client.query_points(
    collection_name="documents",
    query=query_vector,
    query_filter=Filter(
        must=[FieldCondition(key="tenant_id", match=MatchValue(value="tenant-a"))]
    ),
    limit=10
)

장점: 구현이 단순, 하나의 컬렉션으로 관리 단점: 필터 인덱스 오버헤드, 데이터 격리가 논리적 수준

패턴 2: 네이티브 멀티테넌시

Weaviate의 네이티브 멀티테넌시나 Pinecone의 네임스페이스를 활용합니다.

native_multitenancy.py
python
# Weaviate: 네이티브 멀티테넌시
tenant_collection = collection.with_tenant("tenant-a")
results = tenant_collection.query.near_text(query="검색어", limit=10)
 
# Pinecone: 네임스페이스
results = index.query(
    vector=query_vector,
    top_k=10,
    namespace="tenant-a"
)

장점: 물리적/논리적 격리, 테넌트별 리소스 관리 가능 단점: 솔루션 종속, 테넌트 수 제한 가능

패턴 3: 컬렉션 분리

테넌트별로 독립적인 컬렉션을 생성합니다.

장점: 완전한 격리, 테넌트별 인덱스 최적화 가능 단점: 테넌트 수가 많으면 관리 부담, 리소스 낭비 가능

패턴적합한 경우
메타데이터 필터테넌트 수 적고, 데이터가 유사한 구조
네이티브 멀티테넌시수천 개 테넌트, 비활성 테넌트 오프로드 필요
컬렉션 분리테넌트별 스키마가 다르거나, 완전한 격리 필요

성능 최적화 전략

1. 필터 인덱스 최적화

자주 사용하는 필터 필드에만 인덱스를 생성합니다. 불필요한 인덱스는 쓰기 성능을 저하시키고 메모리를 낭비합니다.

2. 카디널리티 고려

**카디널리티(Cardinality)**는 필터 필드의 고유 값 수입니다. 카디널리티가 너무 높은 필드(예: UUID)는 필터 인덱스 효율이 낮습니다. 반대로 카디널리티가 너무 낮은 필드(예: boolean)는 선택성이 낮아 검색 범위를 크게 줄이지 못합니다.

cardinality_guide.py
python
# 높은 카디널리티 (비효율적 필터)
# user_id: 100만 개 고유 값 -> 각 값에 해당하는 벡터가 극소수
# -> 멀티테넌시 패턴 사용 권장
 
# 적절한 카디널리티 (효율적 필터)
# category: 20개 고유 값 -> 각 카테고리에 수만 개 벡터
# -> 필터 인덱스 최적
 
# 낮은 카디널리티 (선택성 부족)
# is_active: 2개 값 (True/False) -> 필터 효과 미미
# -> 다른 필터와 조합하여 사용

3. 과도한 필터 회피

필터 조건이 너무 많으면 후보 벡터가 지나치게 줄어들어 ANN 인덱스의 효율이 떨어집니다. 가장 선택적인 2-3개 필터를 DB 레벨에서 적용하고, 나머지는 애플리케이션 레벨에서 사후 필터링하는 것도 방법입니다.

4. 벤치마크 기반 튜닝

filter_benchmark.py
python
import time
 
def benchmark_filter(client, collection, query_vector, filter_condition, n_runs=100):
    """필터 성능 벤치마크"""
    latencies = []
    for _ in range(n_runs):
        start = time.perf_counter()
        client.query_points(
            collection_name=collection,
            query=query_vector,
            query_filter=filter_condition,
            limit=10
        )
        latencies.append((time.perf_counter() - start) * 1000)
 
    latencies.sort()
    return {
        "p50_ms": latencies[len(latencies) // 2],
        "p95_ms": latencies[int(len(latencies) * 0.95)],
        "p99_ms": latencies[int(len(latencies) * 0.99)],
    }
Warning

필터 성능 벤치마크는 반드시 프로덕션과 유사한 데이터 분포에서 수행해야 합니다. 균일하게 분포된 테스트 데이터와 실제 데이터의 편향된 분포에서 성능 차이가 크게 날 수 있습니다.


정리

이번 장에서는 메타데이터 필터링의 전략과 고급 쿼리 패턴을 심층적으로 다루었습니다. 사전 필터링과 사후 필터링의 트레이드오프, 복합 필터와 지오 필터의 구현, 멀티테넌시 패턴, 성능 최적화 전략을 살펴보았습니다. 필터링은 벡터 검색의 실용성을 결정하는 핵심 요소이며, 인덱스 설계와 카디널리티를 고려한 최적화가 중요합니다.

다음 장에서는 시리즈의 마지막으로 운영, 모니터링, 스케일링 전략을 다룹니다. 프로덕션 환경에서의 벡터 데이터베이스 운영 노하우와 솔루션 선택 의사결정 프레임워크를 제시하겠습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#vector-database#ai#embedding#search#infrastructure

관련 글

AI / ML

11장: 운영, 모니터링, 스케일링 전략

벡터 데이터베이스의 수평/수직 스케일링, 샤딩, 레플리카, 백업 전략, 모니터링 메트릭, 비용 최적화, 솔루션 선택 의사결정 트리, 마이그레이션 가이드를 다룹니다.

2026년 3월 6일·20분
AI / ML

9장: 하이브리드 검색 구현

시맨틱 검색과 키워드 검색을 결합하는 하이브리드 검색의 원리, BM25+벡터 퓨전 전략, Reciprocal Rank Fusion, 리랭커 통합, 프레임워크별 구현 방법을 다룹니다.

2026년 3월 2일·14분
AI / ML

8장: Qdrant와 pgvector -- 특화 솔루션들

Rust 기반 고성능 벡터 엔진 Qdrant의 페이로드 필터링, 명명된 벡터, 하이브리드 배포를 분석하고, PostgreSQL 확장 pgvector의 트랜잭션 일관성과 pgvectorscale 성능을 비교합니다.

2026년 2월 28일·14분
이전 글9장: 하이브리드 검색 구현
다음 글11장: 운영, 모니터링, 스케일링 전략

댓글

목차

약 16분 남음
  • 학습 목표
  • 메타데이터 필터링이 중요한 이유
  • 사전 필터링 vs 사후 필터링
    • 사후 필터링 (Post-filtering)
    • 사전 필터링 (Pre-filtering)
    • 각 솔루션의 접근 방식
  • 필터 인덱스 설계
    • Qdrant의 페이로드 인덱스
    • Weaviate의 인덱스 설정
  • 복합 필터 조건
    • AND/OR/NOT 조합
    • 범위 쿼리
  • 지오 필터 (Geo Filtering)
  • 멀티테넌시 필터 패턴
    • 패턴 1: 메타데이터 필터 기반
    • 패턴 2: 네이티브 멀티테넌시
    • 패턴 3: 컬렉션 분리
  • 성능 최적화 전략
    • 1. 필터 인덱스 최적화
    • 2. 카디널리티 고려
    • 3. 과도한 필터 회피
    • 4. 벤치마크 기반 튜닝
  • 정리