🔄 Encoder migration playbook¶
When: swapping the embedding model without taking retrieval down.
The failure mode that defines this job: a mixed-encoder index throws no exception. Cosine returns well-formed numbers for vectors that mean nothing to each other. Nothing errors, results stay plausible, and quality quietly falls.
The invariant¶
One index version has exactly one
embedder_tag. Always. No exceptions, including "just this one document".
Everything below exists to protect that sentence.
The sequence¶
1 · Prove the new encoder is better — before touching production.
- [ ] Build
v_{n+1}alongsidev_n. Never into it - [ ] Evaluate on the frozen slice, paired bootstrap
- [ ] Slice by query class. An encoder swap almost always helps some classes and hurts others; the aggregate hides it and the aggregate is what people quote
- [ ] Compare cost: dimension drives storage and RAM, and RAM is on the serving path
2 · Shadow.
- [ ] Serve from
v_n, evaluatev_{n+1}on live traffic - [ ] Compare on the query distribution you actually have, which is not your eval set
3 · Swap.
- [ ] Atomic alias swap. Not a rebuild, not a config deploy
- [ ]
mixed_version_check()on the new version before the alias moves - [ ] Rollback is the same swap in reverse — seconds
4 · After.
- [ ] Keep
v_nwarm for at least one full incident cycle - [ ] Invalidate every cache keyed on embeddings — a semantic cache keyed on old vectors will serve confidently wrong hits
- [ ] Re-tune
αif you fuse. A stronger dense leg moves the optimum, usually a lot; the oldαis fitted to the old encoder
What people forget, in order of how much it hurts¶
| Forgotten | Consequence |
|---|---|
Re-tune α |
The new encoder is better and the fusion weight still assumes it is weak |
| Cache invalidation | Confident wrong answers from vectors that no longer mean anything |
| Per-class evaluation | Aggregate improved, your most important class regressed |
| Dimension change | RAM and index size move; capacity planning was done for the old one |
| Backfill cost | Budgeted as a one-off, then incurred again at the next upgrade |
| Downstream thresholds | Abstention and cache thresholds were fitted to the old score distribution |
That last one is subtle: score distributions are not comparable across encoders. Every threshold anywhere in the system that was tuned against cosine values has to be re-fitted.
The check that catches the disaster¶
Run it in CI, and run it before every alias swap. It is the only thing standing between you and a failure that produces no error.
When to refuse the migration¶
- The new encoder wins on aggregate and loses on your highest-value class
- The re-embed cost exceeds the measured quality gain for a year
- You have no rollback path — no version aliasing, so a swap is a rebuild
The third is a reason to build aliasing first and migrate second.
Practise: EX-09 — Survive an encoder upgrade · Decision: ADR-0004