Vector Filtering and Metadata

Pure vector similarity search is powerful, but real applications often need additional constraints. A shopper searching for "running shoes" usually also wants to filter by price range, brand, or size. This topic covers metadata storage and filtered search.

What Metadata Is

Metadata is structured information you attach to each vector alongside its embedding. Think of it as a small set of regular database fields stored next to the vector — searchable with exact-match logic, unlike the vector itself.

A SINGLE VECTOR RECORD:
─────────────────────────────────────────────────
{
  "id": "shoe-4521",
  "values": [0.23, 0.87, 0.45, ...],     ← the vector (1536 numbers)
  "metadata": {                           ← structured fields
    "name": "Trail Runner Pro",
    "brand": "Nike",
    "price": 89.99,
    "category": "running",
    "in_stock": true,
    "release_year": 2025
  }
}

Pre-Filtering vs. Post-Filtering

Vector databases apply filters in one of two ways, and the difference matters for both speed and correctness.

Post-Filtering (Naive Approach)

STEP 1: Run vector search → get top 10 results by similarity
STEP 2: Remove results that don't match the filter
STEP 3: Return whatever survives

PROBLEM:
  If 8 of the top 10 don't match the filter,
  you're left with only 2 results — even though
  better matches exist further down the ranking.

Pre-Filtering (Correct Approach)

STEP 1: Apply the filter FIRST → narrow the candidate pool
STEP 2: Run vector search ONLY within that filtered pool
STEP 3: Return top-k from the filtered, ranked results

RESULT: Always returns the best k matches that satisfy the filter

Modern vector databases (Pinecone, Weaviate, Qdrant, Milvus) all use pre-filtering by default. This is one of the key engineering challenges these platforms solve — efficiently combining metadata filters with approximate nearest neighbor search without losing speed.

Common Filter Operators

OperatorMeaningExample
$eqEqualscategory = "running"
$neNot equalbrand != "Nike"
$gt / $gteGreater than / or equalprice > 50
$lt / $lteLess than / or equalprice < 100
$inMatches any value in a listbrand in ["Nike", "Adidas"]
$and / $orCombine multiple conditionsprice < 100 AND in_stock = true

Filtered Search Example (Pinecone)

results = index.query(
    vector=query_vector,
    top_k=10,
    filter={
        "$and": [
            {"category": {"$eq": "running"}},
            {"price": {"$lte": 100}},
            {"in_stock": {"$eq": True}}
        ]
    },
    include_metadata=True
)

This query finds the closest vectors to your search query, restricted only to in-stock running shoes priced at $100 or below.

A Practical E-Commerce Example

USER SEARCH: "comfortable shoes for long walks"
USER FILTERS: Price under $80, Size 10, In stock only

QUERY FLOW:
─────────────────────────────────────────────────
1. Embed: "comfortable shoes for long walks"
       → query_vector

2. Apply filters BEFORE ranking:
       price <= 80 AND size = 10 AND in_stock = true
       → narrows 50,000 products to 1,200 candidates

3. Run vector similarity search WITHIN those 1,200
       → ranks by closeness to query_vector

4. Return top 10 most relevant matches
       → all guaranteed to satisfy price/size/stock filters

Multi-Tenancy Using Metadata

Metadata filtering also solves multi-tenancy — keeping different customers' data separate within one shared index. Add a tenant ID field and filter by it on every query.

# Storing data for tenant "company-A"
index.upsert(vectors=[{
    "id": "doc-1",
    "values": embedding,
    "metadata": {"tenant_id": "company-A", "text": "..."}
}])

# Querying only company-A's data
results = index.query(
    vector=query_vector,
    filter={"tenant_id": {"$eq": "company-A"}},
    top_k=5
)

This pattern is common in SaaS applications where many customers share the same underlying vector database, but each must only ever see their own data.

Choosing What to Store as Metadata

Store as MetadataEmbed Into the Vector
Price, category, date, status flagsProduct description, article text
User ID, tenant IDUser reviews, comments
Exact values you filter or sort byAnything you want to search by meaning

A simple rule guides this decision: if you need exact matching or range comparisons, use metadata. If you need fuzzy, meaning-based matching, embed it into the vector.

Metadata Size Limits

Most vector databases cap metadata size per record — commonly around 40KB. Store identifiers and short fields as metadata, and keep large text content in a separate database or file storage, referencing it by ID in the metadata instead of duplicating the full text.

Leave a Comment

Your email address will not be published. Required fields are marked *