Every application should read and write through an alias rather than an index name, which turns a mapping change from an outage into a swap.
POST /_reindex
{ "source": { "index": "products-v3" },
"dest": { "index": "products-v4" } }
POST /_aliases
{ "actions": [
{ "remove": { "index": "products-v3", "alias": "products" } },
{ "add": { "index": "products-v4", "alias": "products" } }
] }
# both actions in one request: atomic. no window.
The two actions in a single request is what makes it atomic — doing them as two requests leaves a moment with no index behind the alias, which is a burst of 404s. Writes during the reindex are the remaining problem and need either a dual-write window or a second pass over anything modified since the reindex started.