Skip to content
API ReferenceZilliz CloudMilvusAttu

Index Management

Indexes are crucial for optimizing search performance in Milvus. This guide covers creating, managing, and optimizing indexes.

Create an index on a vector field:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'L2',
});

Specify index parameters:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'L2',
params: {
M: 16,
efConstruction: 200,
},
});

Best for high-dimensional vectors and high recall:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'L2',
params: {
M: 16, // Number of connections
efConstruction: 200, // Construction parameter
},
});

Good balance of speed and accuracy:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'IVF_FLAT',
metric_type: 'L2',
params: {
nlist: 1024, // Number of clusters
},
});

IVF_SQ8 (Inverted File Scalar Quantization)

Section titled “IVF_SQ8 (Inverted File Scalar Quantization)”

Memory-efficient version of IVF_FLAT:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'IVF_SQ8',
metric_type: 'L2',
params: {
nlist: 1024,
},
});

IVF_PQ (Inverted File Product Quantization)

Section titled “IVF_PQ (Inverted File Product Quantization)”

Most memory-efficient, good for large datasets:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'IVF_PQ',
metric_type: 'L2',
params: {
nlist: 1024,
m: 8, // Number of sub-vectors
nbits: 8, // Number of bits per sub-vector
},
});

Exact search, no index (for small datasets):

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'FLAT',
metric_type: 'L2',
});

Let Milvus choose the best index:

await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'AUTOINDEX',
metric_type: 'L2',
});

MINHASH_LSH is an index for binary vectors that uses MinHash locality-sensitive hashing. It is useful for approximate similarity search on binary signatures, sets, shingles, or other binary encodings where Jaccard-style similarity is appropriate.

await client.createCollection({
collection_name: 'binary_docs',
fields: [
{ name: 'id', data_type: DataType.Int64, is_primary_key: true },
{ name: 'binary_vector', data_type: DataType.BinaryVector, dim: 128 },
],
});
await client.createIndex({
collection_name: 'binary_docs',
field_name: 'binary_vector',
index_type: 'MINHASH_LSH',
metric_type: 'JACCARD',
});

For binary vectors, the dim value must be a multiple of 8, and inserted binary vector payloads use dim / 8 bytes.

Milvus 3.0 can generate MinHash binary signatures from a text field and create the bound index atomically:

import {
DataType,
FunctionType,
IndexType,
MetricType,
} from '@zilliz/milvus2-sdk-node';
await client.addFunctionField({
collection_name: 'documents',
field: {
name: 'minhash_vector',
data_type: DataType.BinaryVector,
dim: 512,
is_function_output: true,
},
function: {
name: 'body_minhash',
type: FunctionType.MINHASH,
input_field_names: ['body'],
output_field_names: ['minhash_vector'],
params: { num_hashes: 16, shingle_size: 3 },
},
index_name: 'minhash_index',
extra_params: {
index_type: IndexType.MINHASH_LSH,
metric_type: MetricType.MHJACCARD,
params: { mh_lsh_band: 8 },
},
});

Use MHJACCARD for MinHash function output. Plain binary-vector workflows can continue to use JACCARD when appropriate.

Choose the appropriate metric type for your use case:

  • L2: Euclidean distance (most common)
  • IP: Inner product
  • COSINE: Cosine similarity
  • HAMMING: Hamming distance (for binary vectors)
  • JACCARD: Jaccard distance (for binary vectors, including MinHash use cases)
// L2 distance
await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'L2',
});
// Cosine similarity
await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'COSINE',
});

Get index information:

const indexInfo = await client.describeIndex({
collection_name: 'my_collection',
field_name: 'vector',
});
console.log('Index type:', indexInfo.index_type);
console.log('Metric type:', indexInfo.metric_type);
console.log('Parameters:', indexInfo.params);

List all indexes in a collection:

const indexes = await client.listIndexes({
collection_name: 'my_collection',
});
console.log('Indexes:', indexes.index_descriptions);

Check if index is built:

const state = await client.getIndexState({
collection_name: 'my_collection',
field_name: 'vector',
});
console.log('Index state:', state.state); // 'IndexStateNone', 'IndexStateUnissued', 'IndexStateInProgress', 'IndexStateFinished', 'IndexStateFailed'

Monitor index build progress:

const progress = await client.getIndexBuildProgress({
collection_name: 'my_collection',
field_name: 'vector',
});
console.log('Indexed rows:', progress.indexed_rows);
console.log('Total rows:', progress.total_rows);

Modify index properties:

await client.alterIndexProperties({
collection_name: 'my_collection',
field_name: 'vector',
properties: {
'index.params.ef': 100,
},
});

Remove index properties:

await client.dropIndexProperties({
collection_name: 'my_collection',
field_name: 'vector',
property_names: ['index.params.ef'],
});

Delete an index:

await client.dropIndex({
collection_name: 'my_collection',
field_name: 'vector',
});
  1. Small datasets (< 1M vectors): Use FLAT for exact search
  2. Medium datasets (1M - 10M vectors): Use HNSW for high recall
  3. Large datasets (> 10M vectors): Use IVF_PQ for memory efficiency
  4. High-dimensional vectors: Prefer HNSW or IVF_FLAT
  5. Memory-constrained: Use IVF_SQ8 or IVF_PQ
  6. Binary signatures / set similarity: Use MINHASH_LSH with JACCARD

HNSW Parameters:

  • M: Higher values improve recall but increase memory (typical: 16-32)
  • efConstruction: Higher values improve index quality but slower build (typical: 100-500)

IVF Parameters:

  • nlist: Number of clusters (typical: sqrt(total_vectors) to total_vectors/10)

IVF_PQ Parameters:

  • m: Number of sub-vectors (typical: 8-16)
  • nbits: Bits per sub-vector (typical: 8)
  • L2: Use for embeddings trained with L2 distance
  • IP: Use for embeddings trained with inner product
  • COSINE: Use for normalized embeddings
  • HAMMING/JACCARD: Use for binary vectors
  • JACCARD + MINHASH_LSH: Use for binary signatures and set-similarity workloads
  1. Build after insertion: Create index after inserting data
  2. Monitor progress: Check build progress for large datasets
  3. Load after indexing: Load collection after index is built
  4. One index per vector field: Each vector field can have one index
// Create collection
await client.createCollection({
collection_name: 'my_collection',
fields: [
{
name: 'id',
data_type: DataType.Int64,
is_primary_key: true,
autoID: true,
},
{
name: 'vector',
data_type: DataType.FloatVector,
dim: 128,
},
],
});
// Insert data
await client.insert({
collection_name: 'my_collection',
data: [
/* ... */
],
});
// Create index
await client.createIndex({
collection_name: 'my_collection',
field_name: 'vector',
index_type: 'HNSW',
metric_type: 'L2',
params: {
M: 16,
efConstruction: 200,
},
});
// Wait for index to be built
let state;
do {
state = await client.getIndexState({
collection_name: 'my_collection',
field_name: 'vector',
});
await new Promise((resolve) => setTimeout(resolve, 1000));
} while (state.state !== 'IndexStateFinished');
// Load collection
await client.loadCollectionSync({
collection_name: 'my_collection',
});
// Now you can search
const results = await client.search({
collection_name: 'my_collection',
data: [
/* vector */
],
limit: 10,
});