Start hereQuick start · 6 you’ll reach for daily
Target versions · paceVersions
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
# 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, AsyncQdrantClient | Sync + async clients. |
| from qdrant_client.models import VectorParams, Distance, PointStruct | Schema + write types. |
| from qdrant_client.models import Filter, FieldCondition, MatchValue, MatchAny, Range | Filter DSL building blocks. |
| from qdrant_client.models import SparseVector, SparseVectorParams | Sparse / hybrid support. |
| from qdrant_client.models import Prefetch, Fusion, FusionQuery | Multi-stage + fusion queries. |
| from qdrant_client.models import ScalarQuantization, ProductQuantization, BinaryQuantization | Quantization configs. |
| from qdrant_client.http.exceptions import UnexpectedResponse | Most 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 | MANHATTAN | Distance 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-string | Integers 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=False | Trim 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_polygon | Geo 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. |
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.DBSF | Distribution-based score fusion. Use when scores are comparable. |
| multi-stage prefetch nesting | Prefetch can contain its own prefetch — e.g. dense → rerank-by-MMR. |
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=True | Keep quantized vectors in RAM, originals on disk. |
| SearchParams(quantization=QuantizationSearchParams(rescore=True)) | Rescore with original vectors — recovers recall. |
| oversampling=3.0 | Fetch 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 create | Pre-shard. Can’t change later without reindex. |
| replication_factor=N | Replicas per shard — needs a cluster. |
| write_consistency_factor=N | Acks required before write returns. |
| on_disk=True (in VectorParams) | Memory-map vectors instead of holding in RAM. |
| curl localhost:6333/cluster | Peer + raft state. |
LangChain · LlamaIndex · fastembedFramework integrations
| from langchain_qdrant import QdrantVectorStore | Modern 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 Qdrant | Legacy import path. |
| from llama_index.vector_stores.qdrant import QdrantVectorStore | LlamaIndex adapter. |
| client.add(name, documents=[...]) # with fastembed extras | Built-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.
# 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
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.
query_points, not search.
The old search, recommend, discover family is superseded. query_points covers all three plus hybrid + grouping — one shape to remember.
oversampling + rescore, you usually recover the recall too — cheap latency win.
Common trapsWatch out for
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.
upsert with a bare list against a collection with multiple slots throws. Always pass vector={"dense": [...]} when named vectors are configured.
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.