A long custom data migration¶
Situation: each order has full_name: "Ada Lovelace" and should get
customer: {first, last} instead. That's custom logic ctx.ops can't express, on 200,000
documents, and it must be safe to interrupt.
The migration¶
def upgrade(ctx):
from pymongo import UpdateOne
orders = ctx.collection("orders")
for batch in ctx.batches("orders", {"customer": {"$exists": False}},
projection={"full_name": 1}, batch_size=1000, transactional=True):
updates = []
for order in batch:
first, _, last = order["full_name"].partition(" ")
updates.append(UpdateOne(
{"_id": order["_id"]},
{"$set": {"customer": {"first": first, "last": last}}, "$unset": {"full_name": ""}},
))
orders.bulk_write(updates, session=batch.session)
def downgrade(ctx):
pass
ctx.batches(...)walks the matching documents in_idorder and checkpoints after each batch. If the run dies,mongomig resumecontinues after the last completed batch.bulk_writeper batch is one round trip per 1,000 documents instead of 1,000 round trips. In this example it was 10× faster thanupdate_oneper document (about 43,000 vs 4,400 documents/s).transactional=Truecommits each batch's writes together with its checkpoint, so a crash never leaves a half-processed batch (exactly-once). Passsession=batch.sessionto the writes. It needs a replica set (a single-node one is fine).- The filter
{"customer": {"$exists": False}}makes it re-runnable even without checkpoints.
Plan it¶
$ mongomig plan
Migration plan for orders_db
ab4e4d589e2a totals as numbers applied
0e7e4fd9869b split customer names pending
0e7e4fd9869b split customer names Risk: MEDIUM
orders batches custom loop (first batch simulated) ~200,000 docs · collection scan · custom code
orders bulk_write 1000 operations ~1,000 docs · custom code
reversible: yes · deletes data: no · resumable: yes (checkpointed)
why MEDIUM: ~200k documents (orders.batches); custom code: document counts are estimates
1 migration(s) · ~200,000 document writes · highest risk MEDIUM
Estimates only; nothing was changed.
In a dry run only the first batch is simulated, so plan stays fast; the loop's size comes
from a document count.
Run it¶
$ mongomig upgrade
Running upgrade ab4e4d589e2a -> 0e7e4fd9869b, split customer names
batches orders: 80,000 / ~200,000 (40.0%) · 39,957 docs/s · elapsed 2.0s · ETA 3.0s
batches orders: 162,000 / ~200,000 (81.0%) · 40,440 docs/s · elapsed 4.0s · ETA 940ms
batches orders: 200,000 / ~200,000 (100.0%) · 40,314 docs/s · elapsed 5.0s
• batches orders: 200,000 documents in 200 batches
✓ done in 5.0s
Applied 1 revision(s).
Result: orders with customer: 200000, with full_name left: 0.
Without transactions
Drop transactional=True on a standalone server. A batch interrupted mid-way is then
processed again on resume (at-least-once), so keep the per-document work repeatable, as
the filter above does.