Summary
REINDEX INDEX on a vchordrq index fails with missing chunk number N for toast value M in pg_toast_X after upgrading from 0.5.3 to 1.0.0, when the underlying table has dead tuples. The embeddings themselves are not corrupt — every row detoasts fine and the same reindex succeeds on 0.5.3. Running VACUUM on the table before the extension upgrade avoids the failure entirely.
Environment
- PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1), Debian 13 (trixie), amd64
vchord 0.5.3 → 1.0.0
vector (pgvector) 0.8.1 → 0.8.2
- Table with a TOASTed vector column and a
vchordrq index:
Table "public.smart_search"
Column | Type | Storage
-----------+--------------+----------
assetId | uuid | plain
embedding | vector(1152) | external
Indexes:
"smart_search_pkey" PRIMARY KEY, btree ("assetId")
"clip_index" vchordrq (embedding vector_cosine_ops) WITH (
residual_quantization = false,
build.internal.lists = [512],
spherical_centroids = true,
build_threads = 4,
sampling_factor = 1024)
~270k rows.
Steps to reproduce
- On vchord 0.5.3, have a table like the above with some accumulated dead tuples (normal insert/update churn without a recent vacuum/autovacuum pass).
- Install vchord 1.0.0, restart postgres, then run:
ALTER EXTENSION vector UPDATE;
ALTER EXTENSION vchord UPDATE;
REINDEX INDEX clip_index;
- Observe the reindex fail partway through clustering:
INFO: clustering: estimated memory usage is 2.26 GiB
ERROR: missing chunk number 0 for toast value 5517461 in pg_toast_17330
What we ruled out
- Not corrupt data. A full scan of every row (
SELECT embedding FROM smart_search per row, forcing detoast) completes with zero failures, both before and after the reported error.
- Not a fluke. Reproduced deterministically from an identical snapshot: same starting DB restored twice, reindex fails every time without a prior vacuum, and succeeds every time with one. The toast value/chunk number differs slightly between runs (5517461/chunk 0, 5517449/chunk 1 in our tests), consistent with it pointing at whichever dead tuples happen to be present rather than a single fixed corrupt row.
Workaround
Running this before ALTER EXTENSION vchord UPDATE (i.e. still on 0.5.3) reliably prevents the failure:
VACUUM (ANALYZE) smart_search;
Note the ordering matters and there's a bit of a catch-22: if you vacuum after the extension is already on 1.0.0 (with the index still in 0.5.3's on-disk format), VACUUM itself errors out:
ERROR: deserialization: bad version number; after upgrading VectorChord, please use REINDEX
So recovery has to happen in this order: vacuum while still on the old version → upgrade → reindex.
Expected behavior
REINDEX (or the upgrade path in general) shouldn't fail due to ordinary dead tuples in the heap — index builds normally tolerate this via the visibility map / MVCC snapshot rather than needing every dead row's TOAST data to still be intact. This came up via the Immich community-scripts LXC installer, where several users hit it on the 0.5.3 → 1.0.0 bump (community-scripts/ProxmoxVE#15588) and initially assumed it meant DB corruption.
Summary
REINDEX INDEXon avchordrqindex fails withmissing chunk number N for toast value M in pg_toast_Xafter upgrading from 0.5.3 to 1.0.0, when the underlying table has dead tuples. The embeddings themselves are not corrupt — every row detoasts fine and the same reindex succeeds on 0.5.3. RunningVACUUMon the table before the extension upgrade avoids the failure entirely.Environment
vchord0.5.3 → 1.0.0vector(pgvector) 0.8.1 → 0.8.2vchordrqindex:Steps to reproduce
What we ruled out
SELECT embedding FROM smart_searchper row, forcing detoast) completes with zero failures, both before and after the reported error.Workaround
Running this before
ALTER EXTENSION vchord UPDATE(i.e. still on 0.5.3) reliably prevents the failure:Note the ordering matters and there's a bit of a catch-22: if you vacuum after the extension is already on 1.0.0 (with the index still in 0.5.3's on-disk format),
VACUUMitself errors out:So recovery has to happen in this order: vacuum while still on the old version → upgrade → reindex.
Expected behavior
REINDEX(or the upgrade path in general) shouldn't fail due to ordinary dead tuples in the heap — index builds normally tolerate this via the visibility map / MVCC snapshot rather than needing every dead row's TOAST data to still be intact. This came up via the Immich community-scripts LXC installer, where several users hit it on the 0.5.3 → 1.0.0 bump (community-scripts/ProxmoxVE#15588) and initially assumed it meant DB corruption.