teachyou.ai academy
← All posts
Vector DatabasesQdrantWeaviateRAGembeddings

Qdrant vs Weaviate: Choosing a Vector Database

Pramod Dutta · Jun 22, 2026 · 14 min read

The short answer to Qdrant vs Weaviate: both are open-source vector databases that store embeddings and run approximate nearest-neighbor search over them, and both are fast enough for production RAG. Qdrant is a lean, single-purpose search engine written in Rust with a tight payload-filtering story; Weaviate is a broader knowledge-graph-flavored engine with built-in vectorizer modules, GraphQL, and cross-references between objects. Pick Qdrant when you want a focused, predictable vector store you feed yourself, and Weaviate when you want the database to own embedding generation and richer object relationships.

This guide compares Qdrant vs Weaviate the way you actually meet them: through client code, collection schemas, filtering, hybrid search, and operations. Everything below is runnable. Swap in your own embedding model and endpoints.

Qdrant vs Weaviate at a glance

Both databases solve the same core problem. You have text, images, or audio turned into dense vectors by an embedding model, and you need to find the vectors closest to a query vector, fast, over millions of rows. Both use HNSW (Hierarchical Navigable Small World) graphs as the default index, both support metadata filtering, both scale horizontally, and both have managed cloud offerings plus a self-hostable open-source core.

Where the Qdrant vs Weaviate decision actually splits:

  • Qdrant treats vectorization as your job. You bring vectors, Qdrant stores and searches them. It ships an official Python, JavaScript/TypeScript, Rust, and Go client, plus a REST and gRPC API. Its filtering language is expressive and applied during search, not as a slow post-filter.
  • Weaviate can own vectorization for you. You configure a vectorizer module (an embedding provider) on a collection, push raw objects, and Weaviate calls the model and stores the vectors. It exposes GraphQL alongside REST/gRPC and supports cross-references so objects can point at each other like a graph.
  • Qdrant's mental model is "a search engine with payloads." Weaviate's mental model is "an object database that happens to be vector-native."

Neither is a toy. The choice is about how much you want the database to do versus how much you want to control yourself.

Core concepts and vocabulary

The words differ, so line them up before writing code.

In Qdrant you create a collection, insert points, and each point has an id, a vector, and a payload (arbitrary JSON metadata). You query with a vector and an optional filter over the payload.

In Weaviate you create a collection (older docs call it a class), insert objects, and each object has properties (typed fields) and one or more vectors. You query with GraphQL or the client's search methods, filtering over properties.

So "point" maps roughly to "object," "payload" maps roughly to "properties," and "collection" means the same thing in both. The important structural difference is that Weaviate properties are typed and can include cross-references to other objects, while Qdrant payloads are schemaless JSON that you index selectively.

Setup: running each locally

You can run both with Docker in a minute, which is the fastest way to compare Qdrant vs Weaviate on your own machine.

Qdrant:

docker run -p 6333:6333 -p 6334:6334 \
  -v $(pwd)/qdrant_storage:/qdrant/storage \
  qdrant/qdrant

Port 6333 is REST, 6334 is gRPC. The dashboard lives at the 6333 root path in a browser.

Weaviate:

docker run -p 8080:8080 -p 50051:50051 \
  -e AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true \
  -e PERSISTENCE_DATA_PATH=/var/lib/weaviate \
  -v $(pwd)/weaviate_data:/var/lib/weaviate \
  cr.weaviate.io/semitechnologies/weaviate:latest

Port 8080 is REST/GraphQL, 50051 is gRPC. If you want Weaviate to generate embeddings, you also set the vectorizer environment variables and enable the relevant module, but the command above runs a bring-your-own-vector setup, which keeps the comparison fair.

Install the clients:

pip install qdrant-client weaviate-client

Creating a collection

Here is where the Qdrant vs Weaviate contrast becomes concrete. Assume 768-dimensional embeddings and cosine distance.

Qdrant, Python:

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="articles",
    vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)

That is the whole schema. Qdrant does not force you to declare payload fields ahead of time. You add points with whatever JSON you want, and later you create payload indexes only on the fields you filter by.

Weaviate, Python (v4 client, bring-your-own-vectors):

import weaviate
from weaviate.classes.config import Configure, Property, DataType

client = weaviate.connect_to_local()

client.collections.create(
    name="Articles",
    vector_config=Configure.Vectors.self_provided(),
    properties=[
        Property(name="title", data_type=DataType.TEXT),
        Property(name="body", data_type=DataType.TEXT),
        Property(name="category", data_type=DataType.TEXT),
        Property(name="views", data_type=DataType.INT),
    ],
)

Weaviate wants a typed schema up front. self_provided tells it you will supply vectors. If you instead configured a vectorizer here, Weaviate would call an embedding model for you at insert and query time. That single line is the biggest philosophical difference in the whole Qdrant vs Weaviate story.

Inserting data

Qdrant upserts points. You control ids and can batch thousands per call.

from qdrant_client.models import PointStruct

points = [
    PointStruct(
        id=1,
        vector=embed("How HNSW indexes work"),
        payload={"title": "HNSW explained", "category": "search", "views": 4200},
    ),
    PointStruct(
        id=2,
        vector=embed("Cosine vs dot product similarity"),
        payload={"title": "Similarity metrics", "category": "math", "views": 1900},
    ),
]

client.upsert(collection_name="articles", points=points)

embed() is your model call, any provider or a local sentence-transformer. Qdrant never sees your model.

Weaviate with self-provided vectors uses a batch context manager:

articles = client.collections.get("Articles")

with articles.batch.dynamic() as batch:
    batch.add_object(
        properties={"title": "HNSW explained", "category": "search", "views": 4200},
        vector=embed("How HNSW indexes work"),
    )
    batch.add_object(
        properties={"title": "Similarity metrics", "category": "math", "views": 1900},
        vector=embed("Cosine vs dot product similarity"),
    )

If a vectorizer were configured, you would drop the vector= argument and Weaviate would embed the properties itself. That is convenient for prototypes and removes an entire moving part from your pipeline, at the cost of coupling your database to a specific embedding provider and its latency.

Searching

The everyday query: give a vector, get the nearest neighbors.

Qdrant:

hits = client.query_points(
    collection_name="articles",
    query=embed("explain the navigable small world graph"),
    limit=5,
    with_payload=True,
).points

for h in hits:
    print(h.id, h.score, h.payload["title"])

Weaviate:

from weaviate.classes.query import MetadataQuery

res = articles.query.near_vector(
    near_vector=embed("explain the navigable small world graph"),
    limit=5,
    return_metadata=MetadataQuery(distance=True),
)

for o in res.objects:
    print(o.uuid, o.metadata.distance, o.properties["title"])

Both return ranked results with a score or distance. Note Qdrant reports a similarity score (higher is better with cosine), while Weaviate reports a distance (lower is closer). Keep that straight when you set thresholds, because it is a common source of "why is my cutoff backwards" bugs when teams port code between the two.

Filtering: the part that decides real projects

In production RAG you almost never search the whole collection. You filter by tenant, by document type, by recency. How each engine filters is a major axis of Qdrant vs Weaviate.

Qdrant applies filters inside the HNSW traversal (filterable HNSW), so a restrictive filter does not blow up recall or force a slow post-filter. You must create a payload index on any field you filter heavily.

from qdrant_client.models import Filter, FieldCondition, MatchValue, Range

client.create_payload_index(
    collection_name="articles",
    field_name="category",
    field_schema="keyword",
)

hits = client.query_points(
    collection_name="articles",
    query=embed("graph indexes"),
    query_filter=Filter(
        must=[
            FieldCondition(key="category", match=MatchValue(value="search")),
            FieldCondition(key="views", range=Range(gte=1000)),
        ]
    ),
    limit=5,
).points

Weaviate filters with a fluent Filter builder and applies filters efficiently against its inverted index plus vector index:

from weaviate.classes.query import Filter

res = articles.query.near_vector(
    near_vector=embed("graph indexes"),
    filters=(
        Filter.by_property("category").equal("search")
        & Filter.by_property("views").greater_or_equal(1000)
    ),
    limit=5,
)

Functionally similar results, different ergonomics. Qdrant's must / should / must_not structure maps cleanly to boolean logic and is easy to build programmatically from a dict. Weaviate's operator-overloaded builder reads nicely in Python. For heavy multi-tenant workloads, both have first-class support: Qdrant recommends a tenant field with a payload index, and Weaviate has explicit multi-tenancy where each tenant is an isolated shard, which can be a genuine deciding factor if you run thousands of tenants and want hard isolation.

Hybrid search

Pure vector search misses exact keyword matches (product codes, names, acronyms). Hybrid search blends dense vectors with sparse/keyword (BM25-style) scoring. This is increasingly table stakes, and both handle it.

Weaviate has hybrid search as a single call, fusing BM25 and vector scores with a tunable alpha:

res = articles.query.hybrid(
    query="HNSW graph",
    vector=embed("HNSW graph"),
    alpha=0.5,
    limit=5,
)

alpha=1 is pure vector, alpha=0 is pure keyword, 0.5 is an even blend. Because Weaviate can also own the embedding, a fully managed hybrid setup can be a one-liner without you computing the vector at all.

Qdrant does hybrid search through named vectors and its Query API, where you store a dense vector and a sparse vector per point, then fuse them:

from qdrant_client.models import Prefetch, FusionQuery, Fusion

results = client.query_points(
    collection_name="articles",
    prefetch=[
        Prefetch(query=sparse_vec, using="keyword", limit=20),
        Prefetch(query=dense_vec, using="dense", limit=20),
    ],
    query=FusionQuery(fusion=Fusion.RRF),
    limit=5,
)

Qdrant uses Reciprocal Rank Fusion to combine the two prefetch lists. It is more explicit: you generate the sparse vector yourself (for example with a SPLADE-style model or BM25 encoder) and store it. That is more wiring than Weaviate's alpha, but it hands you full control over how sparse retrieval works, which matters when you want a specific tokenizer or a domain sparse model.

Performance and indexing knobs

On raw speed, Qdrant vs Weaviate is close enough that your embedding model and network latency usually dominate. Both let you tune HNSW.

Shared HNSW parameters you will touch in either:

  • m: number of edges per node in the graph. Higher improves recall and memory use.
  • ef_construction: search breadth while building the index. Higher builds slower but yields better graphs.
  • ef (search time): breadth at query time. Higher improves recall at the cost of latency.

Qdrant exposes these in hnsw_config on the collection and also supports scalar and product quantization to shrink vectors in RAM, plus on-disk payload and vector storage for large datasets that do not fit in memory. Its Rust core and memory-mapped storage make the "bigger than RAM" case a documented, first-class path.

Weaviate exposes the same HNSW knobs per collection, supports product quantization and binary quantization for compression, and offers a flat index option for small collections where a brute-force scan beats graph overhead. It also has dynamic indexing that starts flat and switches to HNSW as a collection grows.

Do not trust any single benchmark number you read, including ones you might run yourself on a laptop. Recall, latency, and memory trade off against each other, and the honest way to compare Qdrant vs Weaviate for your workload is to load a representative slice of your real data, fix a recall target, and measure p95 latency and memory at that recall. A tool like the open-source vector database benchmark harness can automate the sweep, but your data distribution is what decides the winner.

Operations, scaling, and cost

Both are cloud-native and both offer managed services (Qdrant Cloud and Weaviate Cloud) alongside self-hosting.

Qdrant self-hosting is a single lightweight binary or container. Distributed mode uses sharding and replication with a Raft-based consensus for collection metadata. Because the process is lean and written in Rust, its baseline memory and CPU footprint is modest, which keeps small deployments cheap.

Weaviate self-hosting is also containerized and clusters via sharding and replication. Its footprint is larger because it bundles more: modules, GraphQL, the object store. That is the cost of the extra features. If you use vectorizer modules, remember the database now depends on an embedding provider being reachable, which is another failure mode to monitor.

On cost, avoid quoting prices that change. The durable point is structural: Qdrant's smaller footprint tends to mean lower baseline infrastructure for a bring-your-own-vector search service, while Weaviate can reduce your application code and infra elsewhere by absorbing embedding orchestration. Run both against your data and price the actual instances you would provision. Do not budget from a blog's numbers, including this one.

A decision checklist

Use this to turn the Qdrant vs Weaviate comparison into a choice.

Lean toward Qdrant when:

  • You already generate embeddings in your pipeline and want the database to just store and search them.
  • You want tight, programmatic payload filtering and filterable HNSW without post-filter recall loss.
  • You care about a small footprint, on-disk storage for larger-than-RAM datasets, and quantization control.
  • You want the sparse side of hybrid search fully under your control.

Lean toward Weaviate when:

  • You want the database to own vectorization through configurable modules so your app pushes raw text.
  • You want one-call hybrid search with a simple alpha blend.
  • You need strong built-in multi-tenancy with per-tenant shard isolation at large tenant counts.
  • Your data model benefits from typed properties and cross-references between objects, closer to a knowledge graph.

Both are safe long-term bets: active open-source projects, real communities, managed options, and standard client libraries. You will not get stuck with either. The migration path between them is also not scary, because both speak the same primitives (vectors, ids, metadata, filters), so a switch is mostly rewriting client calls and re-indexing, not rethinking your architecture.

A minimal RAG loop that works on either

To make the abstract concrete, here is the retrieval half of a RAG pipeline, written so only the store layer changes. Everything upstream (chunking, embedding) and downstream (prompt assembly, LLM call) stays identical.

def retrieve(query, store, k=5):
    qvec = embed(query)
    hits = store.search(qvec, k=k, where={"category": "search"})
    return [h.text for h in hits]

def answer(query, store, llm):
    context = "\n\n".join(retrieve(query, store))
    prompt = f"Answer using only the context.\n\nContext:\n{context}\n\nQ: {query}"
    return llm.complete(prompt)

Behind store.search you put either the Qdrant query_points call or the Weaviate near_vector call from earlier, mapping your where dict to each engine's filter type. Keeping this boundary clean is the single most useful thing you can do while you are still deciding Qdrant vs Weaviate, because it lets you swap the backend in an afternoon and measure both on your real traffic instead of arguing about it.

FAQ

Is Qdrant faster than Weaviate?

For most workloads the difference is small and swamped by your embedding model and network latency. Qdrant's Rust core gives it a lean footprint and strong larger-than-RAM behavior, but the only honest speed comparison is one you run on your own data at a fixed recall target, measuring p95 latency and memory. Do not decide on a generic benchmark.

Does Weaviate generate embeddings for me and Qdrant does not?

Yes, that is the cleanest one-line summary of the difference. Weaviate can run a vectorizer module that calls an embedding provider at insert and query time, so you push raw text. Qdrant expects you to bring vectors. Many teams prefer bringing their own vectors even on Weaviate for control and portability, but the built-in option is genuinely convenient for prototypes.

Which is better for hybrid search?

Both do hybrid search well. Weaviate makes it a single call with an alpha blend between BM25 and vector scores. Qdrant does it through named dense and sparse vectors fused with Reciprocal Rank Fusion, which is more wiring but gives you full control over the sparse model. Pick Weaviate for simplicity, Qdrant for control.

Which handles multi-tenant SaaS better?

Both support it. Qdrant uses a tenant field with a payload index and filterable HNSW. Weaviate offers explicit multi-tenancy where each tenant is an isolated shard, which is attractive when you run thousands of tenants and want hard isolation and per-tenant activation. If strict tenant isolation at scale is a hard requirement, evaluate Weaviate's multi-tenancy closely.

Can I switch from one to the other later?

Yes, and it is not a rewrite of your architecture. Both use the same primitives: vectors, ids, metadata, and filters. A migration means re-embedding is usually unnecessary (you can re-index the same vectors), rewriting client calls, and reloading data. If you keep a thin store abstraction in your code, the swap is an afternoon of work.

Do I need a dedicated vector database at all?

Not always. If you already run PostgreSQL and have modest scale, the pgvector extension may be enough. Dedicated engines like Qdrant and Weaviate earn their place when you have millions of vectors, need advanced filtering during search, want quantization and larger-than-RAM storage, or need hybrid search and multi-tenancy out of the box. Start simple and move to a dedicated store when you hit a real limit.

Which should a beginner pick to learn vector search?

Either teaches you the core ideas: embeddings, HNSW, distance metrics, filtering. Start Qdrant if you want to understand the full pipeline including embedding generation, because it forces you to own that step. Start Weaviate if you want to see an end-to-end semantic search work with less code first. Run both locally with the Docker commands above and build the same tiny RAG loop on each; that hands-on comparison teaches more than any Qdrant vs Weaviate article, including this one.

Qdrant vs Weaviate: Choosing a Vector Database · TeachYou Academy