Back to Home

Vector Search

HNSW indexing with SIMD-accelerated distance, correct filter-then-search

What if every search query could find the most relevant results across millions of items in under a millisecond?

2,398 QPSThroughput,99.7% @10Recall,3.16ms p50 / 4.91ms p95Latency,SIMD AcceleratedAcceleration

Traditional search works by matching keywords. You type "happy," and it finds documents containing the word "happy." But what about "joyful," "elated," or "over the moon"? Keyword search misses all of them because it matches characters, not meaning.

Vector search works differently. It converts every piece of data, whether text, images, or audio, into a numerical fingerprint called a vector. These fingerprints capture meaning, not just spelling. Words with similar meanings produce similar fingerprints. "Happy" and "joyful" end up close together in vector space, even though they share no letters.

SwarnDB stores these vectors and uses a data structure called HNSW (Hierarchical Navigable Small World) to find the closest matches blazingly fast. Instead of scanning every single vector in the database, HNSW builds a multi-layered navigation graph that lets searches skip directly to the right neighborhood. On our vector-only benchmark this reaches a peak of 2,398 QPS at recall@10 of 0.9974 (99.7%), with single-digit-millisecond latency. And because SwarnDB accelerates the core distance calculations with SIMD hardware instructions, every query squeezes maximum performance from your CPU.

How Vector Search Works

11

Embed

Convert your text, images, or any data into numerical vectors using any embedding model. OpenAI, Cohere, Sentence Transformers, or your own custom model. The vector captures the meaning of the original data in a fixed-size numerical array.

22

Index

SwarnDB builds an HNSW graph, a multi-layered navigation structure optimized for nearest-neighbor search. Each layer is progressively sparser, allowing searches to start broad and narrow down quickly. The index is built incrementally as vectors are inserted.

33

Query

When you search, SwarnDB navigates the HNSW graph from the top layer down, homing in on the closest vectors without scanning the entire dataset. The search path is logarithmic, not linear, which is why it scales to millions of vectors.

44

Filter

Optional metadata filters like price, category, date, or any custom field narrow results server-side. Filters are applied during the search, not after, so you get exactly the results you need without over-fetching.

What You Can Do

Capabilities

01

HNSW Indexing

A multi-layered graph where each layer is progressively sparser. Searches start at the top layer for fast, coarse navigation and drill down through denser layers for precision. Think of finding a book: first you pick the right library, then the right floor, then the right shelf, then the exact spot. HNSW does this in vector space, and it does it in microseconds.

hnsw_index.py
from swarndb import SwarnDBClient

with SwarnDBClient(host="localhost", port=50051) as client:
    # Create a collection with HNSW indexing
    client.collections.create(
        "products",
        dimension=1536,
        distance_metric="cosine"
    )
02

SIMD Distance Computation

Hardware-accelerated math using the CPU's vector processing units. Every search requires computing the distance between your query vector and candidate vectors in the index. This distance calculation is the inner loop of every search query, executed thousands of times per request. SwarnDB uses SSE4.1, AVX2, or NEON instructions depending on your hardware to compute these distances using parallel arithmetic. The result is up to 8x faster distance computation compared to standard floating-point math.

03

Metadata Filtering

Combine vector similarity with traditional filters in a single query. Find "the most similar products under $50 in the electronics category." Filters are applied server-side during the search traversal, not as a post-processing step. This means you don't waste time retrieving irrelevant results only to discard them. The filter language supports equality, range, set membership, and boolean combinations.

filtered_search.py
from swarndb import SwarnDBClient, Filter

with SwarnDBClient(host="localhost", port=50051) as client:
    # Vector search with metadata filters
    results = client.search.query(
        "products",
        vector=query_vector,
        k=10,
        filter=Filter.between("price", 0, 50)
    )
04

Batch Search

Submit multiple search queries in a single API call. Instead of making 100 separate requests for 100 users, send all 100 query vectors at once. SwarnDB processes them concurrently and returns all results together. This is essential for recommendation systems processing many users simultaneously, or for reranking pipelines that need to evaluate multiple candidates at once.

batch_search.py
# Batch search: multiple queries in one call
results = client.search.batch(
    "products",
    [user1_vec, user2_vec, user3_vec],
    k=10
)
# Returns results for all 3 queries
05

Configurable Precision

Tune the recall-speed tradeoff with the ef_search parameter. Higher values mean the search explores more candidates, yielding higher recall at the cost of latency. Lower values mean faster queries with slightly less precision. For most applications, the default balances both beautifully. For latency-critical applications, dial it down. For precision-critical applications like medical or legal search, dial it up.

Why Not Just Use Keywords?

Keyword search has a fundamental limitation: it matches strings, not meaning. If your user searches for "best Italian restaurant nearby," keyword search looks for documents containing those exact words. It misses "authentic pasta place around the corner" entirely, even though that's exactly what the user wants. The meanings are identical; the words are completely different.

This is called the semantic gap, the distance between what people mean and what they literally type. Every keyword search system battles this gap with synonyms, stemming, fuzzy matching, and increasingly complex query expansion rules. These are patches on a fundamentally limited approach.

Vector search closes the semantic gap entirely. Because both queries are converted to vectors that capture meaning, "best Italian restaurant nearby" and "authentic pasta place around the corner" produce vectors that are close together in vector space. The search finds both, because it searches by meaning, not by spelling.

This also handles multilingual search, typo tolerance, and conceptual similarity without any additional configuration. A vector for "car" is close to "automobile," "vehicle," and even the French "voiture" if you use a multilingual embedding model.

Insight:Vector search doesn't just tolerate synonyms. It understands that "affordable" and "budget-friendly" and "won't break the bank" all mean the same thing, because their vectors are close together.

semantic_search.py
from swarndb import SwarnDBClient

with SwarnDBClient(host="localhost", port=50051) as client:
    # Keyword search: misses semantically similar results
    # "best Italian restaurant" =/= "authentic pasta place"

    # Vector search: finds meaning, not words
    query = embed("best Italian restaurant nearby")
    results = client.search.query("restaurants", vector=query, k=10)

    # Returns "Authentic pasta place around the corner"
    # Returns "Traditional trattoria in the neighborhood"
    # Returns "Family-run Italian kitchen on Main St"
    # All found by meaning, despite sharing no keywords

How HNSW Works (Simply)

Imagine a map with multiple zoom levels. At the highest zoom, you see continents. You can quickly determine whether your destination is in Europe or Asia with just a glance. Zoom in, and you see countries. Zoom in more: cities, then neighborhoods, then the exact street address.

HNSW works the same way, but in vector space. The top layer of the graph is very sparse, with just a few widely-spaced nodes acting as continental landmarks. When a search query arrives, it starts at the top and finds the nearest landmark. Then it drops to the next layer, which has more nodes (like countries on the map), and finds a closer match. Layer by layer, it drills down through increasingly dense neighborhoods until it reaches the bottom layer, where every vector lives, and identifies the exact nearest neighbors.

The beauty of this structure is that the search never needs to look at every vector. It navigates through the layers, making big jumps at the top and fine adjustments at the bottom. The total number of comparisons is logarithmic: searching 1 million vectors requires roughly the same number of steps as searching 10 million. This is why SwarnDB maintains sub-millisecond query times even as your dataset grows.

The index is also built incrementally. Every new vector is inserted into the appropriate layers, connecting to nearby nodes. There is no need to rebuild the entire index when data changes.

Insight:HNSW's multi-layer structure means that doubling your dataset doesn't double your search time. It barely changes it. That's the power of logarithmic scaling.

Combining Search with Filters

The real power of SwarnDB's search emerges when you combine semantic similarity with structured constraints. Traditional databases are excellent at filtering: give me all products under $50 in the electronics category created after January 2024. Vector databases are excellent at similarity: give me the 10 products most similar to this reference item. SwarnDB does both simultaneously.

This isn't a two-step process where you filter first and then search, or search first and then filter. Both operations happen together during the HNSW traversal. The search navigates the graph looking for the nearest neighbors that also satisfy your filter conditions. This is critical for performance: you never over-fetch results that will be discarded, and you never under-fetch by filtering too aggressively before the similarity search runs.

The filter language is expressive. You can combine equality checks, range queries, set membership tests, and boolean logic. Find the most similar products under $50 in the electronics or gadgets category that were added in the last 30 days. One query, one round trip, one set of results.

Insight:Filters during search, not after. This means you always get exactly k results that match your criteria, not k results minus the ones that didn't pass the filter.

combined_filters.py
from swarndb import SwarnDBClient, Filter

with SwarnDBClient(host="localhost", port=50051) as client:
    # Combine semantic similarity with structured filters
    results = client.search.query(
        "products",
        vector=query_vector,
        k=10,
        filter=Filter.and_(
            Filter.between("price", 0, 50),
            Filter.in_("category", ["electronics", "gadgets"]),
            Filter.gte("rating", 4.0)
        )
    )

    # One query: "most similar to my reference item,
    # under $50, in electronics or gadgets, rated 4+"

Complete Example

Everything above, in one script.

vector_search_complete.py
from swarndb import SwarnDBClient, Filter

with SwarnDBClient(host="localhost", port=50051) as client:
    # 1. Create a collection
    client.collections.create(
        "products",
        dimension=1536,
        distance_metric="cosine"
    )

    # 2. Insert vectors with metadata
    client.vectors.insert("products", vector=product_vector, metadata={
        "name": "Wireless Headphones",
        "price": 49.99,
        "category": "electronics",
        "rating": 4.7
    })

    # 3. Search by meaning with filters
    results = client.search.query(
        "products",
        vector=query_vector,
        k=10,
        filter=Filter.and_(
            Filter.between("price", 0, 100),
            Filter.eq("category", "electronics")
        )
    )

    # 4. Batch search for multiple users
    batch_results = client.search.batch(
        "products",
        [user1_query, user2_query, user3_query],
        k=10
    )

    for user_results in batch_results:
        print(f"Top match: {user_results[0].metadata['name']}")

Start building with Vector Search

Clone the repo and explore this feature in minutes.

View on GitHub