Dual writes to the new collection are already running. You now own the backfill: re-embed 500M documents through a rate limited embedding API and load them into the new collection over roughly two weeks, without destabilizing production. Walk through how you orchestrate it: batching and throughput math, checkpointing and idempotency, cost controls, verification that the backfill is complete and correct, and how you decide it is safe to cut reads over.
Range-partition the corpus, checkpoint per range, upsert idempotently keyed by doc ID plus model version, and gate cutover on count parity plus shadow-traffic recall.
Imagine repainting every car in a parking lot of half a million vehicles in two weeks, while new cars keep arriving and the lot stays open. You divide the lot into numbered rows, mark off each row as you finish, and label every car with the date you painted it. If a car arrives fresh-painted while you are working that row, you skip it instead of repainting over the new coat. You watch the paint budget so you do not run out mid-row. Before you tell drivers to use the new entrance, you check that every row is done, you spot-check a few cars look right, and you keep the old entrance open as a backup in case you have to switch back.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
A 500M-vector backfill is the kind of migration that goes either smoothly or badly, with very little middle ground. The smoothly-running version looks boring: workers pick up ranges, checkpoint progress, hit throughput targets, and the cutover is a one-line config change at the end of a quiet week. The badly-running version is dramatic: lost work from missing checkpoints, duplicate vectors from non-idempotent retries, a budget alarm at 3am, a cutover that fails recall gates after the team has already announced the new model.
This deep-dive walks through the five workstreams a production backfill needs (throughput, partitioning, idempotency, cost, verification) and the one workstream that catches almost everyone off guard: convergence with the live dual-write path. Dual writes are already running by the time you start the backfill, which means historical and live writes are racing, and getting the race right is the difference between a clean migration and a corrupted new collection.
Throughput math and capacity
Start with the arithmetic. 500 million documents over 14 days is 35.7 million per day, 1.49 million per hour, 413 per second sustained. You cannot run at 413; you have to run at 600 to 800 to absorb retries, rate-limit hiccups, and off-peak weighting.
Three capacity questions follow. Embedding API quota. Most managed APIs (OpenAI, Cohere, Voyage) negotiate higher rate limits for migration workloads. Open a ticket weeks in advance, share the throughput target, confirm the burst tolerance. Plan client-side concurrency to match (a fleet of N workers each running M concurrent calls). Vector database ingest. Bulk ingest endpoints exist for a reason; use Pinecone bulk import, Qdrant upload, or Milvus bulk load over single-row upsert APIs. Confirm the ingest path does not throttle live queries (some engines isolate them; others share resources). Network and storage. 500M vectors at, say, 6 KB each (1536 dims float32) is 3 TB of write data. Make sure the source-to-API-to-database path is provisioned for sustained 25 to 50 MB/sec.
Dynamic throttling is essential. Wire the backfill to live p99 monitoring: if production query latency creeps past budget, the worker pool slows down or pauses. Off-peak weighting (run 2x during low-traffic hours) keeps the average rate up without damaging tail latency.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- Pinecone bulk import is designed for exactly this case: rate-limited ingest into a new index with idempotent upserts keyed by ID
- Qdrant snapshot and bulk upload APIs let teams migrate collections at hundreds of MB/sec while keeping live queries up
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you handle a partial failure where 100 ranges have been written with the wrong model version?
Because writes are keyed by (doc_id, model_version), the wrong-version vectors live alongside correct ones. You can identify and delete them by querying on model_version, then mark the affected ranges as pending and reprocess. The model-version part of the key is what makes this recoverable.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Treating the backfill as a single long-running script with no checkpoints, then losing a day's work to a transient API error or a node restart.
60 second bullets to scan on the way to the call.
How to compute sustained throughput from the deadline and corpus size
Why per-range checkpoints are mandatory for multi-day backfills
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.