Move Nest state
Place a Nest's durable state in a selected MongoDB database and move it without abandoning pending records
A Nest can keep its durable state in a selected MongoDB database. This includes its operator-state records, shape record, and dead letters.
By default, Nest uses tapstate.store.mongo.operator-state-database, whose
default value is tapstate_nest. Set state.database on a Nest step to override
that default:
- id: order_doc
type: nest
state:
database: order_doc_state
from: { o: orders, i: order_items }
root:
from: o
key: [id]
embed:
- from: i
on: { order_id: id }
as: array
path: itemsThe database uses the existing tapstate.store.mongo.uri connection and
credentials. This setting does not select a different MongoDB server or copy
existing state. Other Nest steps and other operators keep their own placement.
Before you begin
- Choose a nonempty MongoDB database name of at most 63 UTF-8 bytes. The control
database,
local, andconfigcannot hold operator state. - Give the server account
readWriteon every selected state database. - Record the pipeline ID, Nest step ID, old database, and new database.
- Plan downtime for every server process that can write this deployment.
- Take a restorable backup before moving existing state.
A rolling change is unsafe: different processes could write the same Nest to different databases. Restarting only the pipeline is insufficient because the server process retains the existing map configuration.
Preserve state and continue
- Stop the pipeline without clearing state. Use
stop <pipeline-id> --keep-statein an authenticated CLI session, or the REST stop operation with{"purgeState": false}. - Stop every tapstate server process that can run the deployment, then back up the old database.
- Copy the selected Nest's records using the procedure below. Compare document counts and indexes before continuing.
- If you are changing the server-wide default (
tapstate.store.mongo.operator-state-database), update your server configuration and restart all server processes. If you are only overriding placement for this specific Nest viastate.database, restart the server processes so they evict in-memory cached map configurations. Keep the pipeline stopped until all servers are healthy. - In your pipeline definition, add or update
state.databaseunder the Nest step. Apply the workspace and start the pipeline. - Check the startup log entry beginning
Nest state placement resolved before pipeline. Confirm every affected namespace resolves to the new database. - Verify target records, pending-child completion, and dead letters. Confirm new state writes reach only the new database. Keep the old database and backup until the pipeline has processed normal traffic without error.
A process that still has the old map placement refuses the change with
nest.state-database-changed-while-running.
Copy the selected state
Save the following script as move-nest-state.js and execute it with MongoDB 7 tools
(mongosh "$TAPSTATE_STORE_MONGO_URI" move-nest-state.js) while all writers are stopped.
Replace the four header variables with your recorded IDs and database names. Use a migration
account with read access to the old database and write access to the new one.
Do not paste connection credentials into a shared script.
const fromName = "tapstate_nest";
const toName = "order_doc_state";
const pipelineId = "orders";
const stepId = "order_doc";
const from = db.getSiblingDB(fromName);
const to = db.getSiblingDB(toName);
const escapeRegex = value => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const mapNamespaces = { $regex: `^${escapeRegex(`nest.${pipelineId}.${stepId}.`)}` };
const stateMatch = { $or: [
{ "_id.ns": mapNamespaces },
{ "_id.ns": `nest.shape.${pipelineId}`, "_id.k": stepId }
] };
const deadLetterMatch = { "_id.ns": mapNamespaces };
if (from.operator_state.countDocuments(stateMatch) === 0) {
throw new Error("No state matched. Check the pipeline and step IDs.");
}
for (const [name, match] of [
["operator_state", stateMatch],
["nest_dead_letters", deadLetterMatch]
]) {
from.getCollection(name).aggregate([
{ $match: match },
{ $merge: { into: { db: toName, coll: name }, on: "_id",
whenMatched: "replace", whenNotMatched: "insert" } }
]).toArray();
const before = from.getCollection(name).countDocuments(match);
const after = to.getCollection(name).countDocuments(match);
printjson({ collection: name, before, after });
if (before !== after) throw new Error("State copy verification failed.");
printjson({ sourceIndexes: from.getCollection(name).getIndexes(),
targetIndexes: to.getCollection(name).getIndexes() });
}This copies the selected records and shape record, not unrelated pipelines.
The collections use their automatic _id_ indexes. If the old database has
additional indexes, recreate and compare them before cutover; $merge does
not copy index definitions. Use a destination without conflicting state for
the same pipeline and step IDs.
Roll back
Stop all writers again. If no new events were processed after cutover, restore
the previous state.database setting (or server-wide default), restart all
server processes, and resume against the old database. If the new database has
received writes, migrate those changes back before resuming. Simply pointing
at the old copy would abandon newer state.
Start clean instead
A clean start deliberately discards the continuation. Back up first, then
clear the pipeline's state and position while the old servers are still
available: use stop <pipeline-id> -y, or REST with {"purgeState": true}.
Stop all servers, restart them with the new setting, apply the updated Nest,
and start a fresh load.
Do not keep an old checkpoint while selecting an empty state database: earlier acknowledged events would be skipped even though their Nest state is absent. Review stop behavior when other pipelines share the same capture.