Moving Off pgvector Without a Migration Weekend
Engineering 2 min read

Moving Off pgvector Without a Migration Weekend

A customer had 210 million embeddings in Postgres and a query path nobody wanted to touch. We never turned it off.

The setup on day one of the audit:

  • 210 million embeddings in a pgvector column on the primary database.
  • An IVFFlat index rebuilt by a nightly cron that took four hours.
  • Search competing with transactional load on the same machine, which is why p99 was 900 ms and why nobody wanted to be the person who touched it.

It worked, in the way a Jenga tower works.

The strangler approach

We did not migrate. We shadowed. Every write that touched the embedding column also went to a Nodeform collection, through a trigger and a queue. For three weeks both stores held the same data and only Postgres served traffic.

Then we mirrored reads. Every production query ran against both, Postgres answered the user, and we logged the two result sets side by side. That gave the team something a migration plan never does: a recall comparison on their own traffic, before anything was at stake.

Overlap@10 started at 91 percent. The gap was almost entirely queries where the IVFFlat index was returning worse answers than the new index, not better ones. That finding is what actually got the change approved.

The cutover

A config flag, one region at a time, with the mirror still running. Nothing was deleted for six weeks. The rollback was a flag flip for the entire period, which is the only reason anyone slept.

The trap

Do not let the shadow become permanent. We set a sunset date for the Postgres path in week one. One team asked for an extension for a compliance review and got it. One asked because they did not want to schedule the work and did not. The second team had moved by week nine.

Deadlines move work. Extensions move deadlines.

What stayed in Postgres

The rows. All of them. Nodeform holds vectors and the metadata needed to filter them; the source of truth never moved and was never going to. A retrieval layer that wants to own your database is not a retrieval layer.

[ related ]

More from the blog

Engineering deep-dives, product updates, and notes from the team.

View all posts