Skip to main content

Zero-Downtime Reindexing in OpenSearch with Aliases

· 5 min read
FoundryDB Team
Engineering @ FoundryDB

OpenSearch index mappings are immutable once data is indexed. Changing a field type (for example, from text to keyword for a product SKU) or adding an analyzer to an existing field requires creating a new index and moving data into it. The challenge is doing this without taking your search API offline. The answer is aliases combined with the _reindex API. This post demonstrates the full pattern on a live OpenSearch 2.19.1 cluster managed by FoundryDB.

All commands use YOUR_OPENSEARCH_HOST and YOUR_PASSWORD as placeholders.

Prerequisites

  • A running FoundryDB OpenSearch cluster.
  • curl and jq installed locally.

Step 1: Create the Initial Index with an Alias

Start with a products-v1 index and a products alias pointing to it as the write target.

# Create v1 index
curl -u app_user:YOUR_PASSWORD -k \
-X PUT "https://YOUR_OPENSEARCH_HOST:9200/products-v1" \
-H "Content-Type: application/json" \
-d '{
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
"mappings": {
"properties": {
"sku": {"type": "text"},
"name": {"type": "text"},
"price": {"type": "float"}
}
}
}'

# Create the write alias
curl -u app_user:YOUR_PASSWORD -k \
-X POST "https://YOUR_OPENSEARCH_HOST:9200/_aliases" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{"add": {"index": "products-v1", "alias": "products", "is_write_index": true}}
]
}'

All application code writes to and reads from products, never directly to products-v1.

Step 2: Ingest Documents via the Alias

for doc in \
'{"sku":"KB-001","name":"Wireless Keyboard","price":79.99}' \
'{"sku":"HUB-007","name":"USB-C Hub 7-port","price":49.99}' \
'{"sku":"KB-002","name":"Mechanical Keyboard","price":129.99}'; do
curl -u app_user:YOUR_PASSWORD -k \
-X POST "https://YOUR_OPENSEARCH_HOST:9200/products/_doc" \
-H "Content-Type: application/json" \
-d "$doc"
done

All 3 documents land on products-v1, confirmed by the _index field in each response.

Step 3: Create the Improved v2 Index

The v2 index changes the sku field from text to keyword (enabling exact-match filtering and aggregations), adds an English analyzer to name, and introduces a new category field.

curl -u app_user:YOUR_PASSWORD -k \
-X PUT "https://YOUR_OPENSEARCH_HOST:9200/products-v2" \
-H "Content-Type: application/json" \
-d '{
"settings": {"number_of_shards": 1, "number_of_replicas": 0},
"mappings": {
"properties": {
"sku": {"type": "keyword"},
"name": {"type": "text", "analyzer": "english"},
"price": {"type": "float"},
"category": {"type": "keyword"}
}
}
}'

At this point, products-v2 exists but is empty. The products alias still points to products-v1. Reads and writes continue unaffected.

Step 4: Reindex Data from v1 to v2

curl -u app_user:YOUR_PASSWORD -k \
-X POST "https://YOUR_OPENSEARCH_HOST:9200/_reindex" \
-H "Content-Type: application/json" \
-d '{
"conflicts": "proceed",
"source": {"index": "products-v1"},
"dest": {"index": "products-v2"}
}'

Result from the test (3 documents):

{"took": 23, "total": 3, "created": 3, "updated": 0, "failures": 0}

The reindex completed in 23 ms. For larger indices this will take longer. The key point is that during the entire reindex operation, products-v1 continues to serve reads and writes through the alias. No downtime.

conflicts: proceed tells OpenSearch to skip documents that cause conflicts rather than aborting the entire reindex. For a new index with no existing data, there are no conflicts, but it is good practice to include it for safety.

Step 5: Verify Document Count Before the Swap

Before cutting over, confirm the document counts match. Call _refresh first because the default refresh interval is 1 second and scripts can outrun it.

curl -u app_user:YOUR_PASSWORD -k \
-X POST "https://YOUR_OPENSEARCH_HOST:9200/products-v2/_refresh"

curl -u app_user:YOUR_PASSWORD -k \
"https://YOUR_OPENSEARCH_HOST:9200/_cat/indices/products-v*?v&h=index,docs.count"

Expected output:

index        docs.count
products-v1 3
products-v2 3

Counts match. Safe to swap.

Step 6: Atomic Alias Swap

The alias API accepts multiple actions in a single request and executes them atomically. Remove the alias from v1 and add it to v2 in one call.

curl -u app_user:YOUR_PASSWORD -k \
-X POST "https://YOUR_OPENSEARCH_HOST:9200/_aliases" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{"remove": {"index": "products-v1", "alias": "products"}},
{"add": {"index": "products-v2", "alias": "products", "is_write_index": true}}
]
}'

Verify:

curl -u app_user:YOUR_PASSWORD -k \
"https://YOUR_OPENSEARCH_HOST:9200/_cat/aliases/products?v"
alias    index       is_write_index
products products-v2 true

Step 7: Confirm New Writes Land on v2

curl -u app_user:YOUR_PASSWORD -k \
-X POST "https://YOUR_OPENSEARCH_HOST:9200/products/_doc" \
-H "Content-Type: application/json" \
-d '{"sku": "DESK-MAT-01", "name": "Standing Desk Mat", "price": 39.99, "category": "accessories"}'

The _index in the response is products-v2. After a refresh, a search via the alias returns all 4 documents:

{"total": 4, "docs": [
{"id": "1", "index": "products-v2", "name": "Wireless Keyboard"},
{"id": "2", "index": "products-v2", "name": "USB-C Hub 7-port"},
{"id": "3", "index": "products-v2", "name": "Mechanical Keyboard"},
{"id": "4", "index": "products-v2", "name": "Standing Desk Mat"}
]}

Step 8: Clean Up the Old Index

Once you are confident v2 is correct, delete v1:

curl -u app_user:YOUR_PASSWORD -k \
-X DELETE "https://YOUR_OPENSEARCH_HOST:9200/products-v1"

Keep v1 around for at least one day in production before deleting, in case you need to roll back by swapping the alias back.

Important: Always Refresh Before Querying in Scripts

The default refresh interval is 1 second. In automated scripts that index and then immediately search, always call _refresh on the destination index before asserting document counts or querying results. The reindex API completes when all documents are written, not when they are searchable.

What's Next

Provision a FoundryDB OpenSearch cluster at foundrydb.com. Documentation at docs.foundrydb.com.