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

Milvus Cheatsheet: Indexes, Hybrid Search and ANN Reference

By DevShelfHub

MilvusClient, schemas, indexes (HNSW/IVF/DiskANN), partitions, hybrid + multi-vector search, expressions, Milvus Lite — the day-to-day surface.

95 items 8 min ANN Hybrid DiskANN

Start hereQuick start · 6 you’ll reach for daily

ClientMilvusClient("./milvus.db")
Quick collectioncreate_collection(name, dimension=...)
Insertclient.insert(name, data=[{...}])
Load to RAMclient.load_collection(name)
Searchclient.search(name, data=[v], limit=5)
Filterfilter="source == 'docs'"

Target versions · paceVersions

Targets: milvus ≥ 2.4 pymilvus ≥ 2.4 python ≥ 3.8

MilvusClient is the modern entry point — replaces direct connections.connect() + ORM Collection(...) code from 2.x. Milvus Lite (embedded SQLite-backed binary) ships in pymilvus itself — just pass a file path. Multi-vector hybrid + sparse vectors + RRF / weighted rankers landed in 2.4. DiskANN and GPU indexes are stable. This sheet pins to Milvus 2.4+.

Install · server · LiteSetup

bash
# Milvus Lite — zero-ops, embedded in Python (file-based)
pip install "pymilvus>=2.4"
python -c "from pymilvus import MilvusClient; \
  print(MilvusClient('./milvus.db').list_collections())"

# Docker — standalone for dev (gRPC :19530, REST :9091)
curl -sf https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh | bash
bash standalone_embed.sh start

# Or production-grade compose
wget https://github.com/milvus-io/milvus/releases/latest/download/milvus-standalone-docker-compose.yml -O docker-compose.yml
docker compose up -d

# Helm chart (k8s, distributed mode)
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm install my-milvus milvus/milvus --set cluster.enabled=true

# JS / Go / Java clients
npm install @zilliz/milvus2-sdk-node

Where things liveCommon imports

from pymilvus import MilvusClientModern unified client.
from pymilvus import DataTypeField types — FLOAT_VECTOR, SPARSE_FLOAT_VECTOR, VARCHAR, JSON, etc.
from pymilvus import AnnSearchRequest, RRFRanker, WeightedRankerHybrid-search building blocks.
from pymilvus import Function, FunctionTypeServer-side analyzers (BM25 tokenizer, etc.).
from pymilvus import connections, CollectionLegacy ORM API. Use only for old code.
from pymilvus.model.dense import SentenceTransformerEmbeddingFunctionBundled embedders — install with pymilvus[model].
from pymilvus.exceptions import MilvusExceptionCatch-all client error.

Lite · server · cloudClients

MilvusClient("./milvus.db")Milvus Lite — file-based, no server. Dev / tests.
MilvusClient(uri="http://localhost:19530", token="root:Milvus")gRPC over HTTP/2 to a server. Default port.
MilvusClient(uri="https://...zilliz.com", token=API_KEY)Zilliz Cloud (managed Milvus).
MilvusClient(..., db_name="prod")Pick a database (databases are namespaces > collections).
client.create_database("prod") / client.drop_database("prod")Provision databases.
client.list_databases()Inventory.
client.use_database("prod")Switch the active db.
client.close()Release the channel. Always pair with creation in long-running apps.

Quick · explicit · loadCollections & schema

create_collection(name, dimension=384, metric_type="COSINE")Quick-start mode — fixed schema, dynamic fields on, auto-indexed.
schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=True)Build an explicit schema for production.
schema.add_field("id", DataType.INT64, is_primary=True)Primary key — INT64 or VARCHAR.
schema.add_field("vec", DataType.FLOAT_VECTOR, dim=384)Dense vector field.
DataType.SPARSE_FLOAT_VECTOR / BFLOAT16_VECTOR / BINARY_VECTORSparse / FP16 / binary vectors.
schema.add_field("tags", DataType.ARRAY, element_type=DataType.VARCHAR, max_capacity=16)Array field.
schema.add_field("meta", DataType.JSON)Embedded JSON column.
client.create_collection(name, schema=schema, index_params=idx)Create with explicit schema + indexes.
client.load_collection(name)Required — collections must be loaded into RAM to be searched.
client.release_collection(name)Free RAM when idle.
client.describe_collection(name)Inspect schema + indexes.
client.drop_collection(name)Destructive.
python
from pymilvus import MilvusClient, DataType

client = MilvusClient("./milvus.db")        # Milvus Lite
# or MilvusClient(uri="http://localhost:19530", token="root:Milvus")

# Quick-start path — fixed schema, dynamic fields enabled
client.create_collection(
    collection_name="kb_quick",
    dimension=384,
    metric_type="COSINE",                   # COSINE | L2 | IP
)

# Explicit schema — for production, multi-vector, FP16, etc.
schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=True)
schema.add_field("id",      DataType.INT64, is_primary=True)
schema.add_field("vec",     DataType.FLOAT_VECTOR, dim=384)
schema.add_field("sparse",  DataType.SPARSE_FLOAT_VECTOR)        # BM25 / SPLADE
schema.add_field("title",   DataType.VARCHAR, max_length=256)
schema.add_field("tags",    DataType.ARRAY, element_type=DataType.VARCHAR,
                 max_capacity=16, max_length=64)
schema.add_field("year",    DataType.INT32)

# Per-vector index params — must be created before loading
idx = client.prepare_index_params()
idx.add_index(field_name="vec",    index_type="HNSW",
              metric_type="COSINE", params={"M": 16, "efConstruction": 200})
idx.add_index(field_name="sparse", index_type="SPARSE_INVERTED_INDEX",
              metric_type="IP")

client.create_collection("kb", schema=schema, index_params=idx)
client.load_collection("kb")                # must be loaded into memory to query

HNSW · IVF · DiskANN · GPUIndexes

idx = client.prepare_index_params()Start a parameter set — one collection can index many fields.
idx.add_index(field_name="vec", index_type="HNSW", params={"M":16, "efConstruction":200})HNSW — Preferred dense default.
index_type="IVF_FLAT" params={"nlist": 1024}IVF + flat probes. Cheap memory, decent recall.
index_type="IVF_PQ" params={"nlist":1024,"m":16,"nbits":8}IVF + product quantization. Smaller index, slower build.
index_type="DISKANN"SSD-resident. Use when working set won’t fit RAM.
index_type="GPU_CAGRA" / "GPU_IVF_FLAT"GPU-backed indexes (server build with CUDA).
index_type="SPARSE_INVERTED_INDEX" metric_type="IP"For sparse fields. Required before sparse search.
index_type="BIN_FLAT" / "BIN_IVF_FLAT" metric_type="HAMMING"Binary vectors.
metric_type="COSINE" | "L2" | "IP"Distance. Cosine = IP on L2-normalized vectors.
idx.add_index("year", index_type="STL_SORT")Scalar index for range filters.
idx.add_index("title", index_type="INVERTED")Inverted index for keyword filter / BM25 input.
client.create_index(name, index_params=idx)Apply after create. create_collection can take it directly.

insert · upsert · deleteInsert & DML

client.insert(name, data=[{"id": 1, "vec": [...]}])Insert rows. Auto-flush is configurable.
client.upsert(name, data=[{...}])Insert-or-replace by primary key. Preferred for idempotent ingest.
client.delete(name, ids=[1, 2, 3])Delete by primary key.
client.delete(name, filter="year < 2020")Delete by boolean expression.
client.flush(name)Force buffered inserts to disk. Rarely needed.
client.get(name, ids=[1,2], output_fields=["title"])Random access by id.
batch size ≤ ~10000 rowsSoft cap per RPC. Larger payloads time out.
data=[{"vec": [...], "$meta": {"k":"v"}}]Dynamic field — goes into the meta-JSON when enable_dynamic_field=True.
client.search(name, data=[vec], limit=5)k-NN. Returns list of lists (per query vector).
data=[v1, v2]Batched queries — one inner list per input vector.
anns_field="vec"Which vector field to search. Required when multiple exist.
search_params={"metric_type":"COSINE","params":{"ef":64}}Per-index runtime knob. HNSW: ef; IVF: nprobe.
filter="source == 'docs' and year >= 2024"Boolean expression on scalar fields. Pre-filter.
output_fields=["title","tags","$meta"]Fetch extra fields alongside hits.
consistency_level="Strong" | "Bounded" | "Eventually"Read freshness vs latency tradeoff.
client.query(name, filter="id in [1,2,3]", output_fields=["text"])Filter-only scan — no similarity.
limit=100, offset=0Pagination. Offset is bounded server-side.
group_by_field="doc_id"Top-k per group — great for dedup across chunks.

Scalar filter DSLFilter expressions

"source == 'docs'"Equality. Strings are single-quoted.
"year >= 2024 and year < 2026"Numeric range with and.
"tag in ['rag','llm']"IN operator on scalar fields.
"tag not in ['draft']"NOT IN.
"title like 'rag%'"Prefix match. Needs an INVERTED index for speed.
"json_contains(meta['tags'], 'rag')"JSON membership.
"json_contains_any(meta['tags'], ['a','b'])"Any-of membership.
"ARRAY_CONTAINS(tags, 'rag')"Array column membership.
"meta['author'] == 'ada'"Path into a JSON column.
"exists(meta['rating'])"Field presence in dynamic JSON.

Multi-vector · RRF · weightedHybrid & multi-vector search

AnnSearchRequest(data=[vec], anns_field="vec", param={...}, limit=20)One sub-request per vector field.
RRFRanker(k=60)Reciprocal-rank fusion. Robust default.
WeightedRanker(0.7, 0.3)Linear blend — weights in sub-request order.
client.hybrid_search(name, reqs=[r1, r2], ranker=..., limit=5)Server-side fusion.
DataType.SPARSE_FLOAT_VECTORSparse field for BM25 / SPLADE vectors (dict form).
data=[{7: 1.5, 42: 0.8}]Sparse vector wire form: {index: value}.
drop_ratio_search=0.2 (sparse)Skip the smallest 20% of dimensions at query time — faster.
Function(function_type=FunctionType.BM25, input_field_names=["text"], output_field_names=["sparse"])Server-side BM25 tokenizer — raw text → sparse vector at write time.
python
from pymilvus import MilvusClient, AnnSearchRequest, RRFRanker, WeightedRanker

client = MilvusClient(uri="http://localhost:19530")
client.load_collection("kb")

# Two sub-requests — one dense, one sparse — same collection, different fields
dense_req = AnnSearchRequest(
    data=[[0.0]*384],
    anns_field="vec",
    param={"metric_type": "COSINE", "params": {"ef": 64}},
    limit=20,
)
sparse_req = AnnSearchRequest(
    data=[{7: 1.5, 42: 0.8}],               # sparse vector as {idx: val}
    anns_field="sparse",
    param={"metric_type": "IP", "params": {"drop_ratio_search": 0.2}},
    limit=20,
)

# Fuse — RRF or weighted
res = client.hybrid_search(
    collection_name="kb",
    reqs=[dense_req, sparse_req],
    ranker=RRFRanker(k=60),                 # or WeightedRanker(0.7, 0.3)
    limit=5,
    output_fields=["title", "tags"],
)
for hit in res[0]:
    print(f"{hit['distance']:.3f}  {hit['entity']['title']}")

Partitions · partition keysPartitions & multi-tenancy

client.create_partition(name, partition_name="2026-Q1")Static partition. Useful for time-bucketing.
client.list_partitions(name)Inventory.
client.insert(..., partition_name="2026-Q1")Route writes to a specific partition.
client.search(..., partition_names=["2026-Q1"])Restrict search to partitions.
schema.add_field("tenant", DataType.VARCHAR, max_length=64, is_partition_key=True)Partition key — Milvus auto-shards by hash of this field.
num_partitions=64 (at create)How many hash buckets for the partition key.
filter="tenant == 'acme'"Searches scoped by the partition key field automatically prune.
client.drop_partition(name, "2026-Q1")Cheap bulk delete — drops one partition.
For per-tenant isolation, partition keys (one collection, hashed shards) scale beyond manual partitions. Manual partitions cap at a few thousand per collection — partition keys handle millions of tenants.

LangChain · LlamaIndex · HaystackFramework integrations

from langchain_milvus import MilvusModern LangChain wrapper.
Milvus.from_documents(docs, embedding, connection_args={"uri":"http://..."})Build store from LangChain Documents.
store.as_retriever(search_kwargs={"k": 4, "expr": "year >= 2024"})Drop into an LCEL chain.
from langchain.vectorstores import MilvusLegacy import — superseded by langchain-milvus.
from llama_index.vector_stores.milvus import MilvusVectorStoreLlamaIndex adapter.
from haystack_integrations.document_stores.milvus import MilvusDocumentStoreHaystack 2 document store.

Lite + insert + filtered searchEnd-to-end · Minimal RAG store

Milvus Lite, OpenAI embeddings, scalar filter expression. Swap the URI for a server URL — the rest stays.

python
# Minimal Milvus RAG store — Milvus Lite, insert, filtered search
from pymilvus import MilvusClient
from openai import OpenAI

client = MilvusClient("./milvus.db")
oai    = OpenAI()
NAME, DIM = "kb", 1536

if NAME not in client.list_collections():
    client.create_collection(
        collection_name=NAME,
        dimension=DIM,
        metric_type="COSINE",
        primary_field_name="id",
        id_type="int",
        vector_field_name="vector",
        auto_id=False,
    )

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

docs = [
    {"id": 1, "text": "Milvus is an ANN vector DB.",     "source": "blog"},
    {"id": 2, "text": "RAG joins retrieval with gen.",   "source": "docs"},
    {"id": 3, "text": "DiskANN trades RAM for SSD.",     "source": "docs"},
]
vecs = embed([d["text"] for d in docs])
client.insert(NAME, [{**d, "vector": v} for d, v in zip(docs, vecs)])

q   = embed(["what is RAG?"])[0]
res = client.search(
    collection_name=NAME,
    data=[q], limit=2,
    filter="source == 'docs'",              # boolean expression on scalar fields
    output_fields=["text", "source"],
    search_params={"metric_type": "COSINE", "params": {"nprobe": 10}},
)
for hit in res[0]:
    e = hit["entity"]
    print(f"{hit['distance']:.3f}  [{e['source']}]  {e['text']}")

Best practiceGood to know

Start on Milvus Lite, graduate to standalone, then cluster. Same client, same code — switch the uri from a file path to http://... and you’re on the server. Cluster mode is the same SDK with a different deploy.
Use partition keys for multi-tenant deployments. Hashed partition keys scale to millions of tenants in one collection. Manual partitions are for time-bucketing or rotating windows, not customer isolation.
Tune ef / nprobe per query, not globally. The right value depends on recall target. Pass them in search_params; benchmark with consistency_level="Strong" until happy, then drop to "Bounded" in prod.

Common trapsWatch out for

You must load_collection before searching. A freshly created collection (or one after server restart) is on disk only. search errors with "collection not loaded" until you call load_collection — remember to release when done.
Default consistency is Bounded — reads may lag writes. Fresh inserts may not appear immediately. Use consistency_level="Strong" in tests; in prod, prefer write-then-flush or accept the lag.
Milvus Lite supports a subset — no partitions, no DiskANN, no GPU. Lite is great for dev + small embedded stores. Code paths that work on Lite may not exercise everything you need in production. Test against a standalone server before shipping.

Go deeperSee also

Milvus FAQ

What is Milvus used for?

Milvus is an open-source vector database built for storing and querying high-dimensional embeddings at scale. It is commonly used in RAG (retrieval-augmented generation) pipelines, semantic search, recommendation systems, and multimodal search. It supports billion-scale ANN search with HNSW, IVF, and DiskANN indexes.

What is the difference between Milvus Lite and the full Milvus server?

Milvus Lite (MilvusClient with a local .db path) embeds Milvus into your Python process — no Docker or server needed, great for development and small datasets. The full Milvus server runs as a standalone or distributed cluster via Docker Compose or Kubernetes, suitable for production workloads and multi-client access.

What ANN index types does Milvus support?

Milvus supports HNSW (in-memory graph, best latency), IVF_FLAT and IVF_SQ8 (inverted file with optional quantization, lower RAM), DiskANN (SSD-resident, handles billion-scale vectors cheaply), and SCANN among others. HNSW is the default for most RAG use cases; DiskANN is the pick when the dataset exceeds available RAM.

What is hybrid search in Milvus?

Hybrid search combines dense vector search (ANN) with sparse vector search (e.g., BM25) or scalar field filtering in a single query, then reranks results using RRF (Reciprocal Rank Fusion) or weighted scoring. It delivers better recall than pure ANN when keyword relevance also matters — a common pattern in RAG pipelines.

How do filter expressions work in Milvus?

Filter expressions are string predicates applied to scalar fields alongside the vector search: filter='source == "docs" and year > 2023'. They support comparison, logical, IN, LIKE, and JSON field operators. Filters prune the candidate set before ANN scoring, so indexed scalar fields speed up filtered queries significantly.

Is Milvus free and open source?

Yes. Milvus is Apache-2.0 licensed and free to self-host. Zilliz Cloud is the managed hosted version with usage-based pricing. The open-source version has no feature restrictions — all index types, hybrid search, and multi-tenancy are available.