Changing an embedding model changes the coordinate system in which similarity is computed. Even when old and new models emit vectors with the same dimension, distances across model versions have no promised meaning.
An embedding upgrade is therefore not a configuration flip. It is a data migration with an online serving path, a backfill, a quality gate, and a rollback plan.
Version the representation #
Store enough identity to reproduce and interpret every vector:
CREATE TABLE document_embedding (
document_id bigint NOT NULL,
model_id text NOT NULL,
model_revision text NOT NULL,
chunker_version text NOT NULL,
content_hash text NOT NULL,
embedding vector NOT NULL,
created_at timestamptz NOT NULL,
PRIMARY KEY (document_id, model_id, model_revision, chunker_version)
);The chunker belongs in the version because changing chunk boundaries changes what each vector represents. A content hash makes backfill idempotent and prevents recomputing unchanged material.
Do not overwrite the old vector in place. The existing index is your rollback path and your experimental control.
Separate migration phases #
1. Offline evaluation #
Build a query set from real traffic, support failures, and important edge cases. Label relevant results. Compare recall, ranking metrics, latency, and downstream answer quality by cohort—not only as one average.
2. Dual write #
New or changed documents receive both representation versions. Observe generation failures and queue lag. The old read path remains authoritative.
3. Backfill #
Scan by stable cursor, claim bounded batches, rate-limit model calls, and record progress durably. Backfill must yield to live indexing traffic.
eligible = source updated AND target version absent
claim batch -> embed -> validate dimension -> persist -> checkpointRetries should use the same (document, model, revision, chunker) key so they converge rather than duplicate work.
4. Shadow retrieval #
Run the new retriever without serving its result. Log candidate overlap, latency, empty-result rate, and disagreements on a sampled fraction of traffic. Redact or hash sensitive query material.
5. Gradual cutover #
Route a small cohort to the new index. Hold a rollback switch at the retrieval-router boundary. Increase exposure only when technical and product metrics remain inside agreed thresholds.
6. Retire deliberately #
After the rollback window, remove old vectors and indexes through a retention job. Confirm that no consumer still requests the old version before reclaiming storage.
Failure boundaries #
| Failure | Safe behavior |
|---|---|
| model API unavailable | preserve old reads; retry bounded backfill |
| dimension mismatch | reject write before index insertion |
| partial backfill | route by complete version, not whichever row exists |
| new model quality regression | flip retrieval router back |
| source changes during backfill | compare content hash and requeue |
| index build fails | keep dual-written source rows for rebuild |
What to measure #
Track coverage by version, oldest unembedded update, backfill throughput, embedding cost, index size, query latency, recall@k, reranker outcomes, grounded-answer rate, and user-task success. An upgrade that improves a benchmark while worsening filtered retrieval or multilingual queries is not complete.
Trade-offs #
Dual storage temporarily increases cost. Shadow traffic consumes retrieval capacity. A fast in-place rewrite is cheaper in the short term but removes comparison and rollback. Versioning adds columns and routing logic, while turning an opaque model change into an observable, reversible operation.
Embedding systems are data systems. Give their migrations the same discipline as a database schema change.