Migrating to the OpenSearch k-NN index
The k-NN drivers (SignatureOpenSearchKNN / AsyncSignatureOpenSearchKNN)
search the signature vector directly with approximate nearest neighbours.
Records already store signature, so existing data needs no transformation —
only the index mapping must change (signature becomes a knn_vector
field and the index needs index.knn: true).
Because a field’s mapping cannot be changed on an existing index, migration
creates a new hybrid index: signature is mapped as knn_vector and
every other field keeps dynamic mapping. That means:
simple_word_*fields index as numbers just like before — the classic word drivers keep working on the migrated index, so you can roll back or run both drivers side by side.Document
_idvalues are preserved — re-running the migration is idempotent.The source index is never modified (unless you pass
--delete-sourceafter a successful verification).
Automated migration
tools/migrate_to_knn.py handles the copy: it scans the source index with a
scroll, validates that each document’s signature matches the expected
dimension, bulk-writes into the new k-NN index, then verifies the result.
Elasticsearch → OpenSearch:
$ uv run python tools/migrate_to_knn.py \
--source-url http://localhost:9200 --source-index images \
--target-url http://localhost:9201 --target-index images_knn
OpenSearch word index → OpenSearch k-NN index (same or different cluster):
$ uv run python tools/migrate_to_knn.py \
--source-url http://localhost:9201 --source-index images \
--target-url http://localhost:9201 --target-index images_knn
Options:
--dimensionsignature length, default
648(defaultn_grid=9)--engineknn engine:
lucene(default),faiss,nmslib—nmslibis rejected when the target runs OpenSearch 3+ (see below)--space-typevector space:
l2(default),cosinesimil,innerproduct, …--data-typeknn_vector data type:
float(default) orbyte— signatures are int8, sobyteis lossless and ~4x smaller in memory--batch-sizebulk request size (default 500)
--slicesparallel sliced scrolls over the source index (default 1). A reasonable ceiling is the source shard count — but on HDD-backed clusters each slice adds random read+write load: 8 slices saturated a two-node HDD cluster badly enough to evict the cluster-manager, while 4 ran unattended. Watch node io pressure rather than maximizing this.
--timeoutper-request timeout in seconds for both clients (default 30). Raise it (e.g. 180) for large shards — scroll fetches and bulk responses on a busy cluster can exceed 30 s.
--skip-existingresume mode:
_mget-filter each batch so already-migrated_ids are not re-indexed. After an interrupted run this turns the re-walk of the migrated range into cheap reads instead of tens of millions of redundant k-NN upserts (which also trigger HNSW merges).--delete-sourcedrop the source index after verification passes
Documents whose signature length doesn’t match --dimension are skipped and
reported, not fatal. If the target index already exists, documents are appended
into it (overwriting by _id). Transient bulk failures (shard
unavailability during merges or node restarts) are retried with exponential
backoff instead of aborting the run. The tool prints detected source/target
server type and version before copying.
Note
k-NN write amplification (Lucene merges + HNSW graph build) is the real
load, not the scroll. Plan capacity: the hybrid index is roughly 2.5–3×
the size of the source word index with float vectors. After the count
converges, verify recall before flipping the application — e.g. search
a few known images and confirm each returns itself at dist 0.0 (see
the warning in Other database backends).
Upgrading to OpenSearch 3
The same tool covers an OpenSearch 2 → 3 move — just point --target-url
at the OS3 cluster (word indexes copy verbatim; nothing about the word query
DSL changed):
$ uv run python tools/migrate_to_knn.py \
--source-url http://localhost:9201 --source-index images \
--target-url http://localhost:9202 --target-index images_knn
OpenSearch 3.0 removed several k-NN index settings (knn.algo_param.*) and
blocks creating new indexes with the ``nmslib`` engine. Implications:
knn_index_body()already emits the OS3-supported style — engine and space parameters live in the field mapping, not index settings.If your OS2 index used
nmslib, choose--engine faiss(or thelucenedefault) for the OS3 target. The tool fails fast rather than producing an index OS3 would reject.Existing nmslib indexes keep working on a cluster upgraded to OS3 — the block applies only to new index creation — but reindex/migration is the moment to switch engines.
index.knn: trueis still required on OS3; the tool sets it.
Manual alternative
You can do the same by hand: create the target index with
knn_index_body(dimension) (see Other database backends), then reindex. The
_reindex API alone cannot convert a field’s type, so a copy via
scroll + bulk (what the tool does) is required — any client-side copy works.
Alternatively, re-add all images with the new driver: add_image produces
the same record regardless of backend.
Switching over
Point your code at the new index and driver:
from image_match.opensearch_knn_driver import SignatureOpenSearchKNN
ses = SignatureOpenSearchKNN(client, index="images_knn")
If something goes wrong, switch back to the word driver on the same index — the word fields are still populated and indexed.