Change the stored model
migrate() converts stored vectors to a new model in place. Use it after the column is adopted and search on the current space is working.
The default strategy is convert: a local converter model or a direct UniVec hosted converter translates the stored vectors. On remote mode the converter lives on postvec-server. To keep stored vectors as they are and convert only each query, stay on search a retired space.
A migration waits at two operator steps: awaiting_finalize (column swap) and, if an ANN index existed, awaiting_index (rebuild then a second finalize).
SELECT postvec.migrate(
'public.docs', 'body',
new_model => 'baai-bge-m3'
) AS migration_id \gset
SELECT migration_id, resolved_via, state,
rows_done, rows_total, progress_pct, error
FROM postvec.migration_status(:migration_id);Repeat the status query until state = 'awaiting_finalize', then:
SELECT postvec.migration_finalize(:migration_id);Expected
After the first finalize, an entry that had an ANN index normally enters awaiting_index. The column swap is already done; the entry is live on the new model. Run the suggested concurrent index, then finalize again.
SELECT suggested_index_sql
FROM postvec.migration_status(:migration_id);
-- run that CREATE INDEX CONCURRENTLY in autocommit, then:
SELECT postvec.migration_finalize(:migration_id);
SELECT state FROM postvec.migration_status(:migration_id);In psql, \gexec executes suggested_index_sql.
Strategies
strategy | What it does |
|---|---|
convert (default) | Translate existing vectors. Needs a direct converter to the target. |
reembed | Embed current source text (renders the template) with the new model. |
auto | Convert when a route exists, otherwise re-embed. |
enable(), adopt() and migrate() emit a NOTICE when the target embed model is provider-backed. strategy => 'reembed' onto a provider sends every existing row as text. A direct hosted conversion emits a second NOTICE and sends stored vectors instead.
SELECT postvec.migrate(
'public.docs', 'body',
new_model => 'baai-bge-m3',
strategy => 'convert'
);SELECT postvec.migrate(
'public.docs', 'body',
new_model => 'baai-bge-m3',
strategy => 'reembed'
);SELECT postvec.migrate(
'public.docs', 'body',
new_model => 'baai-bge-m3',
strategy => 'auto'
);reindex is manual (default) or blocking. Use manual mode for tables that serve application traffic. On managed PostgreSQL, an entry with index_mode => 'auto' finishes awaiting_index by itself: the server builds the index concurrently and marks the migration done.
Local and hosted converter routes
Install the converter first: change the stored model (CLI) (model pull on an embedded host or postvec-server, or provider add univec --convert-to).
A kind = "convert" UniVec entry is a hosted direct route. The migration sends every stored vector to UniVec. migration_status().resolved_via names the converter:
{"kind":"direct","model":"univec-convert-snowflake-to-bge-m3"}A hosted converter is a direct route only. If more than one direct converter has the same source and target, postvec prefers a local converter, then the first hosted one by name.
Abort
SELECT postvec.migration_abort(:migration_id);migration_abort() is available before the swap. It drops the new column and keeps the original one. Writes made during the migration reached only the new column, so the rows it holds a vector for (and chunks re-chunked meanwhile) are queued again for the original model; their stored vectors stay until the new ones land.
Observed entries
An adopt(sync => false) entry has no write path. migrate() refuses it unless observed_writes_quiesced => true, with writes remaining stopped through finalization. Otherwise later writes can be missed.
Chunked entries
Counts refer to chunks. Conversion sends vectors. New writes during the migration embed with the new model into the scratch column.
Finalization constraints
migrate() checks column metadata (defaults, NOT NULL, constraints, comments, ACLs, stats/storage) before creating the _new scratch column. finalize takes ACCESS EXCLUSIVE and re-inspects. If dependent views or indexes appeared, it returns a retryable error. The inspection walks pg_partition_tree.
Validation constraints
Conversion requires correct source-model provenance
An incorrect model assertion at adopt() produces invalid converted vectors. Provenance must be confirmed first; otherwise use strategy => 'reembed'.
awaiting_index means the column is already live
The data is on the new model. Build the index, then finalize again.
Only one migration may be active per entry
migration_status() reports the active migration. It can be aborted before the column swap when a different target or strategy is required.