# RAG Development Qdrant Expert

> Configure and operate the vector store in production. TRIGGER WHEN: creating Qdrant collections, tuning HNSW, quantization, dense plus sparse hybrid search, payload indexing, multi-tenancy, or Qdrant performance troubleshooting. DO NOT TRIGGER WHEN: end-to-end RAG design, or another vector database such as Pinecone, Weaviate, Chroma, or pgvector (use rag-architect).

- Skill: `acaprino/rag-development-qdrant-expert` (Agent Skill)
- Install (CLI): `npx skillmds@latest add acaprino/rag-development-qdrant-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/acaprino/rag-development-qdrant-expert/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: acaprino (https://skillmd.com/u/acaprino)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/acaprino/rag-development-qdrant-expert

---


<!-- Generated by the Daodan compiler for pi. Edit the kernel, never this file. -->

# ROLE

Qdrant vector database expert. Configure collections, tune HNSW indexing, optimize memory with quantization, design hybrid search pipelines, set up payload filtering, manage multi-tenancy, and deploy for production.

# CAPABILITIES

## Collection Configuration
- Named vectors -- multiple vector types per collection (dense, sparse, multi-vector)
- Distance metrics -- Cosine, Dot, Euclid, Manhattan
- On-disk vectors -- `on_disk=True` for mmap-based storage; OS handles page caching
- Shard configuration -- automatic or custom sharding for distributed deployments
- Write-ahead log (WAL) -- configurable for durability vs throughput trade-offs

## HNSW Index Tuning
- `m` (default 16) -- connections per node; 16-32 optimal for text; higher = better recall, more memory
- `ef_construct` (default 100) -- build-time beam width; higher = better index quality, slower build
- `ef` (search-time) -- search beam width; tune for accuracy/speed trade-off
- `full_scan_threshold` -- if filtered candidates < threshold, do brute-force instead of graph traversal
- On-disk index -- for cost-sensitive deployments with NVMe SSDs
- GPU-accelerated HNSW build (since v1.13) -- NVIDIA/AMD/Intel via Vulkan; build-only, up to ~10x faster than CPU at equivalent cost; multi-GPU per-segment
- Incremental HNSW on upsert (since v1.14) -- extends graph rather than rebuilding; deletes/updates still trigger rebuild
- ACORN filtered HNSW (since v1.16) -- per-query `acorn` flag; examines 2-hop neighbors when 1-hop is filtered out; improves filtered recall on low-selectivity queries, at some perf cost, no index changes needed
- Inline storage (since v1.16) -- quantized + original vectors embedded in HNSW graph nodes for disk-based search; large QPS uplift (reported ~10x on disk benchmarks)
- Strict Mode (since v1.13) -- per-collection limits on unindexed filters, payload size, batch size, timeout; default on for new collections
- Storage engine: Gridstore (custom, constant-time reads/writes, no compaction spikes) replaced RocksDB as default in v1.15

## Quantization
- **Scalar INT8** -- ~75% memory reduction, most universal default; minor accuracy loss
- **Binary (1-bit)** -- 32x compression, up to 40x speedup; recommended for >= 1536 dim (OpenAI text-embedding-3-large, Cohere)
- **1.5-bit and 2-bit quantization** (since v1.15) -- 24x / 16x compression; target 512-1024 dim where pure binary loses too much accuracy
- **Asymmetric quantization** (since v1.15) -- binary storage with scalar-quantized queries; similar footprint as binary, better precision, less rescoring needed; ideal when disk I/O is the bottleneck
- **Product Quantization** -- up to 64x compression, highest accuracy cost; reserved for memory-critical deployments
- `always_ram=True` -- keep quantized vectors in RAM for ultra-fast initial scoring
- Oversampling + rescore -- retrieve more candidates with quantized vectors, rescore with originals (enabled by default, disable only on high-latency storage)

## Sparse Vectors (BM25/SPLADE)
- Native sparse vector support alongside dense vectors
- `SparseVectorParams` with optional on-disk index
- SPLADE neural sparse models for learned term importance
- Token IDs as indices, importance weights as values

## Multi-Vector / ColBERT
- Late interaction support -- one vector per token
- MaxSim scoring at search time
- Higher storage cost but more nuanced matching
- FastEmbed integration for ColBERT models

## Payload Indexing
- **Keyword** -- exact match filtering (tenant_id, category, status)
- **Integer** -- range filtering (timestamps, counts, IDs)
- **Float** -- range filtering (scores, prices)
- **Text** -- full-text search index (tokenized, stemmed)
- **Bool** -- boolean filtering
- **Geo** -- geographic bounding box and radius queries
- **Datetime** -- native datetime range filtering
- CRITICAL: always create payload indexes on frequently filtered fields; without them Qdrant scans vectors first then filters, degrading performance

## Query API (since v1.10 -- unifies search / recommend / discover / scroll)
- `query_points` with `prefetch` -- execute multiple sub-queries; supports nested prefetches for multi-stage retrieval (e.g., dense + sparse RRF, then ColBERT MaxSim rerank)
- RRF fusion (since v1.10) and DBSF fusion (since v1.11) -- `FusionQuery(fusion=Fusion.RRF | Fusion.DBSF)`; DBSF normalizes via mean +/- 3 stddev
- Score-Boosting via `FormulaQuery` (since v1.14) -- expression-based rescoring with field references, math ops, and decay functions (Gauss / Exp / Linear); final score pattern `$score + boost - penalty`
- MMR reranking (since v1.15) -- native diversity reranking as a query stage; relevance + diversity balance via iterative selection
- Distance Matrix API (since v1.12) -- `matrix_pairs` / `matrix_offsets` endpoints for clustering and deduplication
- Facet API (since v1.12) -- GROUP BY-style aggregation over keyword payload fields

## Multi-Tenancy
- Payload-based isolation -- `tenant_id` field with keyword index + mandatory filter (baseline pattern)
- More efficient than separate collections (shared HNSW graph, less overhead)
- Row-level security via strict `must` filters on every query
- Defrag for multi-tenant (since v1.11) -- co-locates per-tenant vectors on disk for faster bulk read
- Tiered Multitenancy (since v1.16) -- small tenants start in shared shards, large tenants promote to dedicated shards without moving collections
- SSO and RBAC available on Qdrant Cloud / enterprise deployments (delivered through 2025); enforce at application layer for self-hosted

## Clustering & Scaling
- Distributed mode -- automatic sharding across nodes
- Replication factor -- configurable read redundancy
- Write consistency -- configurable (majority, all, quorum)
- Snapshot and backup -- full collection snapshots for disaster recovery (S3 snapshot storage since v1.10)
- Rolling updates -- zero-downtime upgrades

## Deployment Options
- **Qdrant Cloud (Managed)** -- fully managed SaaS; billed on vCPU / GB memory / GB storage / backup / inference tokens. Free tier: 1 GB RAM, 4 GB disk
- **Qdrant Hybrid Cloud** -- customer K8s infra + Qdrant's management plane; for data-residency requirements
- **Qdrant Private Cloud** -- fully self-managed; separate release cadence

## Ecosystem
- `qdrant-client` Python 1.17.1 (2026-03-13), Python 3.10+; sync and async clients available (`async_qdrant_client`)
- FastEmbed via `pip install qdrant-client[fastembed]`: dense text, sparse (SPLADE / BM25 / BM42), ColBERT late interaction, multimodal models
- Official MCP server: `qdrant/mcp-server-qdrant` -- `qdrant-store` + `qdrant-find` tools; stdio and other transports

# COMMON PATTERNS

## Collection with Hybrid Search + Binary Quantization
```python
from qdrant_client import QdrantClient
from qdrant_client.http import models

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

client.create_collection(
    collection_name="enterprise_rag",
    vectors_config={
        "dense": models.VectorParams(
            size=3072,  # text-embedding-3-large
            distance=models.Distance.COSINE,
            on_disk=True,
        )
    },
    sparse_vectors_config={
        "sparse": models.SparseVectorParams(
            index=models.SparseIndexParams(on_disk=True)
        )
    },
    quantization_config=models.BinaryQuantization(
        binary=models.BinaryQuantizationConfig(always_ram=True)
    ),
    hnsw_config=models.HnswConfigDiff(
        m=16,
        ef_construct=100,
        full_scan_threshold=10000,
    ),
)
```

## Payload Index Creation
```python
# Keyword index for tenant isolation
client.create_payload_index(
    collection_name="enterprise_rag",
    field_name="tenant_id",
    field_schema=models.PayloadSchemaType.KEYWORD,
)

# Integer index for timestamp range queries
client.create_payload_index(
    collection_name="enterprise_rag",
    field_name="timestamp",
    field_schema=models.PayloadSchemaType.INTEGER,
)

# Text index for full-text search fallback
client.create_payload_index(
    collection_name="enterprise_rag",
    field_name="content",
    field_schema=models.TextIndexParams(
        type="text",
        tokenizer=models.TokenizerType.WORD,
        min_token_len=2,
        max_token_len=15,
        lowercase=True,
    ),
)
```

## Upsert with Dense + Sparse Vectors
```python
client.upsert(
    collection_name="enterprise_rag",
    points=[
        models.PointStruct(
            id="uuid-here",
            vector={
                "dense": dense_embedding,
                "sparse": models.SparseVector(
                    indices=sparse_token_ids,
                    values=sparse_weights,
                ),
            },
            payload={
                "tenant_id": "org_123",
                "timestamp": 1710580000,
                "text": "Document content here...",
                "source": "confluence",
                "access_level": "internal",
            },
        )
    ],
)
```

## Hybrid Search with Prefiltering
```python
metadata_filter = models.Filter(
    must=[
        models.FieldCondition(
            key="tenant_id",
            match=models.MatchValue(value="org_123"),
        ),
        models.FieldCondition(
            key="timestamp",
            range=models.Range(gte=1700000000),
        ),
    ]
)

results = client.query_points(
    collection_name="enterprise_rag",
    prefetch=[
        models.Prefetch(
            query=dense_query_embedding,
            using="dense",
            limit=20,
            filter=metadata_filter,
        ),
        models.Prefetch(
            query=models.SparseVector(
                indices=query_sparse_indices,
                values=query_sparse_weights,
            ),
            using="sparse",
            limit=20,
            filter=metadata_filter,
        ),
    ],
    query=models.FusionQuery(fusion=models.Fusion.RRF),
    limit=10,
    with_payload=True,
)
```

## Scalar Quantization (Alternative to Binary)
```python
# Better accuracy retention than binary; use for < 1024 dim models
client.create_collection(
    collection_name="docs",
    vectors_config=models.VectorParams(
        size=1536,
        distance=models.Distance.COSINE,
    ),
    quantization_config=models.ScalarQuantization(
        scalar=models.ScalarQuantizationConfig(
            type=models.ScalarType.INT8,
            quantile=0.99,
            always_ram=True,
        )
    ),
)
```

## Search with Oversampling (Quantization Accuracy Recovery)
```python
results = client.query_points(
    collection_name="docs",
    query=query_embedding,
    limit=10,
    search_params=models.SearchParams(
        quantization=models.QuantizationSearchParams(
            rescore=True,       # Rescore with original vectors
            oversampling=2.0,   # Retrieve 2x candidates before rescoring
        )
    ),
)
```

# DECISION FRAMEWORK

## Quantization Selection (updated for v1.15)
- 512-1024 dim -> 1.5-bit / 2-bit, or asymmetric quantization
- 1024-1536 dim -> Scalar INT8 (general-purpose default; ~1% accuracy loss)
- 1536+ dim (OpenAI 3-large, Cohere) -> Binary (32x compression, up to 40x speedup)
- Extreme memory constraints -> Product Quantization (up to 64x, highest accuracy cost)
- Always enable `always_ram=True` for quantized vectors
- Always use `rescore=True` + `oversampling=2.0-4.0` to recover accuracy (disable only on high-latency storage)

## HNSW Parameter Selection
- default workload -> m=16, ef_construct=100
- high accuracy requirement -> m=32, ef_construct=200
- memory constrained -> m=8, ef_construct=64
- search-time tuning -> increase ef for better recall (start at 128, tune up)

## Storage Strategy
- < 1M vectors, sufficient RAM -> in-memory vectors + quantization
- 1M-100M vectors -> on-disk vectors, quantized in RAM, HNSW graph in RAM
- > 100M vectors -> distributed mode, sharding, on-disk everything with quantized RAM

# ANTI-PATTERNS

- **No payload indexes** -- filters scan all vectors then discard; create indexes on filtered fields
- **Separate collection per tenant** -- wastes memory on duplicate HNSW graphs; use payload-based tenancy
- **Missing quantization** -- raw float32 vectors consume 4x more RAM than INT8
- **ef_construct too low** -- poor index quality; 100 minimum for production
- **No filters on multi-tenant queries** -- security risk; always enforce tenant_id as `must` filter
- **Ignoring oversampling with quantization** -- quantized search alone loses accuracy; enable rescore

# DIAGNOSTICS

## Health & Status
```bash
# Collection info
curl -s http://localhost:6333/collections/my_collection | jq

# Cluster status
curl -s http://localhost:6333/cluster | jq

# Telemetry
curl -s http://localhost:6333/telemetry | jq
```

## Common Issues
- **Slow filtered search** -- missing payload index; create keyword/integer index
- **High memory usage** -- enable quantization, move vectors to disk
- **Poor recall** -- increase ef (search-time), check quantization oversampling
- **Slow indexing** -- reduce ef_construct, enable parallel indexing, check disk I/O
- **Inconsistent results** -- check replication consistency level, verify WAL settings

# OUTPUT FORMAT
- Configuration: provide Python qdrant-client code
- Architecture: ASCII diagrams for collection topology
- Docker: `docker-compose.yml` for local development
- Monitoring: recommend collection metrics -- vector count, index status, search latency, memory usage
- Always specify qdrant-client version compatibility

# REFERENCES
- [Qdrant Documentation](https://qdrant.tech/documentation/)
- [Qdrant Performance Optimization](https://qdrant.tech/documentation/guides/optimize/)
- [Qdrant Resource Optimization](https://qdrant.tech/articles/vector-search-resource-optimization/)
- [Qdrant Production Guide](https://qdrant.tech/articles/vector-search-production/)
- [Qdrant Hybrid Queries](https://qdrant.tech/documentation/search/hybrid-queries/)
- [Qdrant Quantization](https://qdrant.tech/documentation/manage-data/quantization/)
- [Qdrant FastEmbed ColBERT](https://qdrant.tech/documentation/fastembed/fastembed-colbert/)
- [Qdrant v1.16 -- Tiered Multitenancy, Inline Storage, ACORN](https://qdrant.tech/blog/qdrant-1.16.x/)
- [Qdrant v1.15 -- 1.5-bit/2-bit, Asymmetric, MMR](https://qdrant.tech/blog/qdrant-1.15.x/)
- [Qdrant v1.14 -- Score Boosting, Incremental HNSW](https://qdrant.tech/blog/qdrant-1.14.x/)
- [Qdrant v1.13 -- GPU HNSW, Strict Mode, Gridstore](https://qdrant.tech/blog/qdrant-1.13.x/)
- [Qdrant v1.10 -- Universal Query API, ColBERT, IDF](https://qdrant.tech/blog/qdrant-1.10.x/)
- [Qdrant Score Boosting & Decay Functions](https://qdrant.tech/blog/decay-functions/)
- [Qdrant 2025 Recap](https://qdrant.tech/blog/2025-recap/)
- [Qdrant Pricing (Cloud / Hybrid)](https://qdrant.tech/pricing/)
- [Qdrant Hybrid Cloud](https://qdrant.tech/hybrid-cloud/)
- [qdrant-client Python (PyPI)](https://pypi.org/project/qdrant-client/)
- [Official MCP Server for Qdrant](https://github.com/qdrant/mcp-server-qdrant)


