DS DevShelfHub Projects · AI tools
Cheatsheets / Qdrant
Cheatsheet · AI frameworks

Qdrant Cheatsheet: Collections, Filters and Hybrid Search Reference

By DevShelfHub

Collections, points, payload filters, HNSW, quantization, sparse vectors, hybrid, snapshots — the Rust-backed vector DB as a one-page reference.

96 items 7 min Payload Quantization Hybrid

Start hereQuick start · 6 you’ll reach for daily

ClientQdrantClient(url="http://localhost:6333")
Create collectioncreate_collection(name, VectorParams(...))
Upsert pointsupsert(name, points=[PointStruct(...)])
Query (ANN)query_points(name, query=vec, limit=5)
Filtered queryquery_filter=Filter(must=[...])
Payload indexcreate_payload_index(name, "k", type)

Target versions · paceVersions

Targets: qdrant ≥ 1.10 qdrant-client ≥ 1.10 python ≥ 3.9

The search / search_groups / recommend family was unified into query_points in 1.10 — everything below pins to the new API. Sparse vectors + named-vector hybrid landed in 1.7; fusion queries (RRF / DBSF) in 1.10. Quantization (scalar / product / binary) is stable. Index type is HNSW, distance is per-collection. This sheet pins to Qdrant 1.10+.

Install · server · clientSetup

bash
# Docker — single-node server (HTTP :6333, gRPC :6334)
docker run -p 6333:6333 -p 6334:6334 \
  -v $(pwd)/qdrant_storage:/qdrant/storage \
  qdrant/qdrant:latest

# Python client
pip install "qdrant-client>=1.10"           # core sync + async
pip install "qdrant-client[fastembed]"      # bundled ONNX embedders

# JS / TS
npm install @qdrant/js-client-rest @qdrant/js-client-grpc

# Health check
curl http://localhost:6333/healthz
# → "healthz check passed"

# Web dashboard — http://localhost:6333/dashboard

Where things liveCommon imports

from qdrant_client import QdrantClient, AsyncQdrantClientSync + async clients.
from qdrant_client.models import VectorParams, Distance, PointStructSchema + write types.
from qdrant_client.models import Filter, FieldCondition, MatchValue, MatchAny, RangeFilter DSL building blocks.
from qdrant_client.models import SparseVector, SparseVectorParamsSparse / hybrid support.
from qdrant_client.models import Prefetch, Fusion, FusionQueryMulti-stage + fusion queries.
from qdrant_client.models import ScalarQuantization, ProductQuantization, BinaryQuantizationQuantization configs.
from qdrant_client.http.exceptions import UnexpectedResponseMost HTTP errors funnel through here.

REST · gRPC · in-memoryClients

QdrantClient(url="http://h:6333", api_key=...)REST. Preferred default.
QdrantClient(host="h", grpc_port=6334, prefer_grpc=True)gRPC for high-throughput upsert + query.
QdrantClient(":memory:")Embedded in-process store (Python only). Tests + notebooks.
QdrantClient(path="./local")Embedded with disk persistence. No server, single-process.
QdrantClient(url=..., timeout=30)Per-call timeout in seconds.
AsyncQdrantClient(url=...)Async variant. All same methods, awaited.
client.get_collections() / client.health()Inventory / ping.

Create · configure · inspectCollections

create_collection(name, VectorParams(size=384, distance=Distance.COSINE))Single unnamed vector slot.
create_collection(name, vectors_config={"dense": VectorParams(...), "img": VectorParams(...)})Named vectors — multiple per point.
sparse_vectors_config={"bm25": SparseVectorParams()}Adds sparse slots alongside dense.
Distance.COSINE | DOT | EUCLID | MANHATTANDistance metric. Match to your embedder.
hnsw_config=HnswConfigDiff(m=16, ef_construct=128)HNSW build knobs. m ≈ 8–32, ef_construct ≈ 64–256.
optimizers_config=OptimizersConfigDiff(memmap_threshold=20000)When to mmap segments to disk.
collection_exists(name)Idempotent guard before create.
get_collection(name)Stats: vectors_count, points_count, status.
update_collection(name, optimizers_config=...)Tune live collections.
delete_collection(name)Destructive.

Upsert · update · deletePoints (DML)

upsert(name, points=[PointStruct(id, vector, payload)])Insert or replace by id. Preferred.
upsert(..., wait=True)Block until applied. Default False — async write.
id can be int or UUID-stringIntegers are cheaper. Use UUIDs only when externally meaningful.
vector={"dense": [...], "bm25": SparseVector(indices, values)}Named-vector form for multi-modal / hybrid.
set_payload(name, payload={...}, points=[ids])Merge payload fields onto existing points.
overwrite_payload(name, payload={...}, points=[ids])Replace entire payload.
delete_payload(name, keys=["k"], points=[ids])Drop specific keys.
delete(name, points_selector=PointIdsList(points=[ids]))Delete by id.
delete(name, points_selector=FilterSelector(filter=Filter(must=...)))Delete by filter.
retrieve(name, ids=[1,2], with_payload=True)Random access — no similarity.

query_points unified APIQuerying

query_points(name, query=vec, limit=5)Plain k-NN. Preferred — replaces old search.
query=NearestQuery(nearest=vec)Explicit nearest-neighbor query.
query=RecommendQuery(recommend=RecommendInput(positive=[...], negative=[...]))Recommend by example point ids or vectors.
query=DiscoverQuery(discover=DiscoverInput(target=v, context=[...]))Steer search toward a target with context pairs.
query=OrderByQuery(order_by="ts")Scan ordered by a payload field. No similarity step.
query_filter=Filter(must=[...])Apply payload filter pre / post ANN.
using="dense"Pick which named vector to search.
search_params=SearchParams(hnsw_ef=128, exact=False)Increase recall by raising ef; exact=True for brute-force test.
with_payload=True, with_vectors=FalseTrim what comes back over the wire.
scroll(name, scroll_filter=..., limit=100)Cursor-paginate without similarity.
query_points_groups(name, group_by="doc_id", limit=5, group_size=3)Top-k per group — great for de-dup across chunks of the same doc.

must · should · must_notPayload filters

Filter(must=[c1, c2])AND.
Filter(should=[c1, c2])OR (any one).
Filter(must_not=[c1])AND NOT.
FieldCondition(key="src", match=MatchValue(value="docs"))Equality.
match=MatchAny(any=["a","b"])IN.
match=MatchExcept(except_=["a"])NOT IN.
match=MatchText(text="vector database")Full-text on indexed string field.
range=Range(gte=10, lt=100)Numeric range.
geo_bounding_box / geo_radius / geo_polygonGeo filters on a {lat, lon} field.
is_empty=PayloadField(key="x") / is_null=...Field absence / null.
create_payload_index(name, "src", PayloadSchemaType.KEYWORD)Index the filtered field — without it, filters do full scans.
python
from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue, MatchAny, Range

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

flt = Filter(
    must=[                                 # AND
        FieldCondition(key="source", match=MatchValue(value="docs")),
        FieldCondition(key="year",   range=Range(gte=2024)),
    ],
    should=[                               # OR (any one)
        FieldCondition(key="tag", match=MatchAny(any=["rag", "embed"])),
    ],
    must_not=[                             # AND NOT
        FieldCondition(key="deleted", match=MatchValue(value=True)),
    ],
)

# Filtered ANN search
hits = client.query_points(
    collection_name="kb",
    query=[0.0]*384,                       # your query vector
    query_filter=flt,
    limit=5,
    with_payload=True,
)

# Filter-only scroll (no similarity — great for paginating tags)
points, next_page = client.scroll(
    collection_name="kb",
    scroll_filter=flt,
    limit=100,
    with_payload=True,
)

Dense + sparse · RRFHybrid & sparse search

sparse_vectors_config={"bm25": SparseVectorParams()}Add a sparse slot at create time.
SparseVector(indices=[7, 42], values=[1.0, 1.5])Sparse vector shape.
vector={"dense": [...], "bm25": SparseVector(...)}Upsert both at once.
prefetch=[Prefetch(query=vec, using="dense", limit=20), Prefetch(query=SparseVector(...), using="bm25", limit=20)]Two-stage retrieval.
query=FusionQuery(fusion=Fusion.RRF)Reciprocal-rank fusion — merges prefetch results server-side.
Fusion.DBSFDistribution-based score fusion. Use when scores are comparable.
multi-stage prefetch nestingPrefetch can contain its own prefetch — e.g. dense → rerank-by-MMR.
python
from qdrant_client import QdrantClient
from qdrant_client.models import (
    VectorParams, SparseVectorParams, Distance,
    PointStruct, SparseVector, NamedVector,
    Prefetch, Fusion, FusionQuery,
)

client = QdrantClient(":memory:")

# Named vectors: one collection holds both dense + sparse, named slots
client.create_collection(
    "kb-hybrid",
    vectors_config={"dense": VectorParams(size=384, distance=Distance.COSINE)},
    sparse_vectors_config={"bm25": SparseVectorParams()},
)

# Upsert one point with both
client.upsert("kb-hybrid", points=[PointStruct(
    id="d1",
    vector={
        "dense": [0.01]*384,
        "bm25":  SparseVector(indices=[7, 42], values=[0.8, 1.2]),
    },
    payload={"text": "..."},
)])

# Hybrid query — RRF fuses dense ANN + sparse top-k server-side
res = client.query_points(
    collection_name="kb-hybrid",
    prefetch=[
        Prefetch(query=[0.0]*384, using="dense", limit=20),
        Prefetch(query=SparseVector(indices=[7,42], values=[1.0,1.0]),
                 using="bm25", limit=20),
    ],
    query=FusionQuery(fusion=Fusion.RRF),   # reciprocal rank fusion
    limit=5,
)

Shrink the index, keep recallQuantization

ScalarQuantization(scalar=ScalarQuantizationConfig(type="int8"))4× smaller, ~1% recall drop. Preferred default.
ProductQuantization(product=ProductQuantizationConfig(compression="x16"))16× smaller. Slower build, more recall loss.
BinaryQuantization(binary=BinaryQuantizationConfig())32× smaller. Best for very large, normalized embeddings.
always_ram=TrueKeep quantized vectors in RAM, originals on disk.
SearchParams(quantization=QuantizationSearchParams(rescore=True))Rescore with original vectors — recovers recall.
oversampling=3.0Fetch 3× top_k from quantized, rerank with originals.

Snapshots · sharding · replicationOps & durability

create_snapshot(name)Point-in-time backup of a collection.
list_snapshots(name) / delete_snapshot(name, snapshot_name)Manage snapshots.
recover_snapshot(name, location)Restore from a local path or URL.
shard_number=N at createPre-shard. Can’t change later without reindex.
replication_factor=NReplicas per shard — needs a cluster.
write_consistency_factor=NAcks required before write returns.
on_disk=True (in VectorParams)Memory-map vectors instead of holding in RAM.
curl localhost:6333/clusterPeer + raft state.

LangChain · LlamaIndex · fastembedFramework integrations

from langchain_qdrant import QdrantVectorStoreModern LangChain wrapper.
QdrantVectorStore.from_documents(docs, emb, url=..., collection_name=...)Build store from LangChain Documents.
QdrantVectorStore(client, "kb", emb, retrieval_mode=RetrievalMode.HYBRID)Hybrid retrieval via the wrapper.
from langchain.vectorstores import QdrantLegacy import path.
from llama_index.vector_stores.qdrant import QdrantVectorStoreLlamaIndex adapter.
client.add(name, documents=[...]) # with fastembed extrasBuilt-in ONNX embedding — no separate embedder needed.

Index · query in 30 linesEnd-to-end · Minimal RAG store

Idempotent create + payload index + filtered ANN with OpenAI embeddings. Works against local Docker or Qdrant Cloud.

python
# Minimal Qdrant store — create, upsert, filtered query
import os
from qdrant_client import QdrantClient
from qdrant_client.models import (
    VectorParams, Distance, PointStruct,
    Filter, FieldCondition, MatchValue, PayloadSchemaType,
)
from openai import OpenAI

client = QdrantClient(url="http://localhost:6333",
                      api_key=os.getenv("QDRANT_API_KEY"))   # cloud / authed
oai = OpenAI()

NAME, DIM = "kb", 1536
if not client.collection_exists(NAME):
    client.create_collection(
        collection_name=NAME,
        vectors_config=VectorParams(size=DIM, distance=Distance.COSINE),
    )
    # Indexed payload field — without this, filters on "source" do full scans
    client.create_payload_index(NAME, "source", PayloadSchemaType.KEYWORD)

def embed(texts):
    return [d.embedding for d in
            oai.embeddings.create(model="text-embedding-3-small", input=texts).data]

docs = [
    {"id": 1, "text": "Qdrant is a Rust vector DB.",     "source": "blog"},
    {"id": 2, "text": "RAG joins retrieval with gen.",   "source": "docs"},
    {"id": 3, "text": "Payload indexes speed filters.",  "source": "docs"},
]
vecs = embed([d["text"] for d in docs])

client.upsert(NAME, points=[
    PointStruct(id=d["id"], vector=v, payload=d) for d, v in zip(docs, vecs)
])

q = embed(["what is RAG?"])[0]
res = client.query_points(
    collection_name=NAME,
    query=q, limit=2,
    query_filter=Filter(must=[FieldCondition(
        key="source", match=MatchValue(value="docs"))]),
    with_payload=True,
)
for p in res.points:
    print(f"{p.score:.3f}  [{p.payload['source']}]  {p.payload['text']}")

Best practiceGood to know

Always create payload indexes for filtered fields. Without an index, FieldCondition falls back to a linear scan over payloads — latency rises with collection size. Index any field that appears in a filter you run often.
Use query_points, not search. The old search, recommend, discover family is superseded. query_points covers all three plus hybrid + grouping — one shape to remember.
Quantize early in production. Scalar (int8) quantization gives a 4× memory cut for ~1% recall loss. Combined with oversampling + rescore, you usually recover the recall too — cheap latency win.

Common trapsWatch out for

Default writes are async — reads can miss them. upsert(..., wait=False) (the default) returns before the WAL is flushed. Pass wait=True in tests or right before a query, or you’ll see flaky results.
Hybrid needs named vectors. Calling upsert with a bare list against a collection with multiple slots throws. Always pass vector={"dense": [...]} when named vectors are configured.
Cosine distance returns similarity, not distance. With Distance.COSINE the score is in [-1, 1] and higher is closer. With EUCLID the score is a distance, lower is closer. Mix them up and your ranking inverts.

Go deeperSee also

Qdrant FAQ

What is Qdrant used for?

Qdrant is a high-performance, Rust-based vector database for storing and searching dense and sparse vector embeddings. It is commonly used in semantic search, RAG pipelines, recommendation systems, and anomaly detection. Its payload filter system lets you combine ANN vector search with structured field conditions in a single query.

What is the difference between Qdrant and Chroma or Milvus?

All three are vector databases, but they differ in architecture and focus. Qdrant is Rust-native with strong payload filtering and a rich REST + gRPC API; it excels at filtered ANN at scale. Chroma is Python-first, lighter weight, and geared toward local/dev RAG. Milvus is the most feature-rich for enterprise scale with multi-vector and GPU index support.

How does payload filtering work in Qdrant?

Payload fields are JSON metadata stored alongside each vector point. Filter conditions use must/should/must_not (boolean logic) with match, range, geo, and nested object operators. Filters are applied as a post-filter after ANN retrieval or as a pre-filter using indexed payload fields. Create a payload index with client.create_payload_index() to speed up filtered queries.

What is quantization in Qdrant?

Quantization compresses stored vectors to reduce RAM usage and speed up search. Scalar quantization (SQ8) maps float32 values to uint8, cutting memory 4x with minimal accuracy loss. Product quantization (PQ) offers even higher compression. Binary quantization is the fastest option for compatible embedding models. Enable it in the collection config under quantization_config.

How does hybrid search work in Qdrant?

Hybrid search combines dense vector ANN results with sparse vector (BM25/BM42) keyword results, then reranks with RRF (Reciprocal Rank Fusion) or custom fusion. Create a sparse vector collection alongside your dense vectors, run both searches in parallel via the query API, and pass them to the fusion reranker. This improves recall for keyword-heavy queries.

Is Qdrant free and open source?

Yes. The Qdrant server is Apache-2.0 licensed and free to self-host. Qdrant Cloud is the managed SaaS option with a free tier (1 GB cluster). The Python, TypeScript, Go, and Rust client SDKs are all open source. No licence is needed for production self-hosted deployments.