Create a k-NN Index in OpenSearch

Validated on 27 Apr 2026 • Last edited on 27 Apr 2026

DigitalOcean Managed OpenSearch for vector search uses the same managed OpenSearch engine available under Managed Databases. It bundles the k-NN, ML Commons, and Neural Search plugins for vector similarity search, hybrid vector and keyword search, and remote embedding models.

A k-NN index is a regular OpenSearch index with one or more knn_vector fields and the index.knn setting enabled. The k-NN plugin, which is enabled by default on DigitalOcean Managed OpenSearch, builds a graph index over those fields so approximate nearest-neighbor queries run in sub-linear time.

This guide covers the three decisions to make when creating a k-NN index: which engine to use, which distance function to use, and how to tune HNSW parameters.

Prerequisites

  • A running OpenSearch cluster. See Create an OpenSearch Vector Database Cluster.
  • Credentials. The examples below assume the environment variables from the quickstart; $OS holds the base URL with auth baked in.
  • Your embedding dimensions. Common values are 384 (MiniLM), 768 (BERT-family), 1024 (bge-large, GTE-large), and 1536 (OpenAI text-embedding-3-small).

Choose an Engine

OpenSearch supports three k-NN engines, each implementing HNSW. The engine is set per knn_vector field in the mapping.

Engine When to use
Faiss (default in 2.19) Best general-purpose choice. High throughput at scale, supports product and scalar quantization for memory reduction, and (since k-NN 2.9) efficient filtering.
Lucene Native OpenSearch HNSW with tight Lucene segment integration. Good for pure-Java operations or segment-level restores. Tops out around 10M vectors per shard.
NMSLIB Deprecated. Keep only for migrating older indexes. Do not use for new workloads.

Both Faiss and Lucene apply k-NN filters during graph traversal, so even restrictive filters return an accurate top-k. Each engine falls back to an exact scan when a filter is very selective.

Choose a Space Type

The space_type determines the similarity metric and must match how your embedding model was trained.

space_type Similarity and use case
cosinesimil Cosine similarity. Standard for text embeddings (OpenAI, Cohere, sentence-transformers, bge, e5, Voyage). Magnitude-invariant.
innerproduct Dot product. Assumes vectors are L2-normalized. Use when your model recommends it.
l2 Euclidean distance. Common for image embeddings (CLIP, OpenCLIP) and models trained with L2 loss.
l1 Manhattan distance. Rare.

If you are unsure, check your embedding model’s documentation. See the OpenSearch k-NN spaces reference for details.

Tune the HNSW Parameters

HNSW has three parameters that trade recall against build time, memory, and query latency:

  • m: Bidirectional links per node. Larger m improves recall but increases RAM and build time. Range 8-64. Default 16.
  • ef_construction: Build-time candidate pool size. Larger values produce a higher-quality graph at the cost of ingestion time. Range 100-512. Default 128.
  • ef_search: Query-time candidate pool size. Set at the index level via index.knn.algo_param.ef_search or per query via method_parameters.ef_search. Default 100.

Defaults are a reasonable starting point. Only tune after measuring recall on a held-out query set. A typical progression is to raise ef_search first per-query (no re-index), promote the best value to the index setting, and only touch m or ef_construction (which do require a re-index) if query-time tuning is not enough.

Create a Production-Grade Index

This example creates an index for a semantic-search workload with 1024-dimensional vectors (bge-large, GTE-large, or similar), cosine similarity, and HNSW parameters tuned for quality over build time.

curl -X PUT "$OS/documents" -H 'Content-Type: application/json' -d '{
  "settings": {
    "index": {
      "knn": true,
      "knn.algo_param.ef_search": 200,
      "number_of_shards": 2,
      "number_of_replicas": 1,
      "refresh_interval": "30s"
    }
  },
  "mappings": {
    "properties": {
      "title":      { "type": "text" },
      "body":       { "type": "text" },
      "source":     { "type": "keyword" },
      "created_at": { "type": "date" },
      "embedding": {
        "type": "knn_vector",
        "dimension": 1024,
        "method": {
          "name": "hnsw",
          "engine": "faiss",
          "space_type": "cosinesimil",
          "parameters": {
            "m": 24,
            "ef_construction": 256
          }
        }
      }
    }
  }
}'

A few choices worth calling out:

  • number_of_shards: Start with 1-2. HNSW graphs are per-shard, so more shards mean lower recall unless you raise ef_search. Only increase shards when a single shard exceeds approximately 50 million documents or 50 GiB.
  • refresh_interval: Bumped from the default 1s to 30s to amortize HNSW segment builds. Reduces ingestion overhead meaningfully for batch indexing. Set to -1 during initial bulk loads and reset afterward.
  • keyword and date fields alongside the vector enable filtered k-NN queries.

Use Exact k-NN for Small Datasets

If you have fewer than about 10,000 vectors, or you only query a subset of vectors matching a restrictive filter, exact k-NN using a script_score query is simpler and has perfect recall. Omit the method block in the mapping:

"embedding": {
  "type": "knn_vector",
  "dimension": 1024
}

You can then query with knn_score as described in Index and Query Vectors via the OpenSearch API.

Update an Existing Mapping

Adding a new knn_vector field to an existing index is supported with PUT <index>/_mapping. Changing the engine, dimension, or space_type of an existing field is not. To change those, create a new index and reindex:

curl -X POST "$OS/_reindex" -H 'Content-Type: application/json' -d '{
  "source": { "index": "documents-v1" },
  "dest":   { "index": "documents-v2" }
}'
Warning
Reindexing with a new embedding model re-computes all vectors from scratch. Plan the cutover: create -v2, backfill, dual-write new documents, swap an index alias, then delete -v1 only after queries are stable.

We can't find any results for your search.

Try using different keywords or simplifying your search terms.