Problem
After a primary PostgreSQL cluster outage, the standby server was promoted and the database was restored from a recent base backup. Subsequent similarity‑search queries that rely on pgvector indexes start failing with errors such as:
ERROR: could not read index file "base/16384/12345": Invalid argument
DETAIL: Index "vector_idx" appears to be corrupted.
HINT: Reindex the index or restore it from a backup.
These failures manifest as:
- SQL exceptions on
SELECT ... ORDER BY vector <-> query_vector LIMIT 10statements. - Increased query latency as the planner falls back to sequential scans.
- Potential data inconsistency if the index is used for deduplication or recommendation logic.
Root Cause
The corruption originates from a mismatch between the pgvector extension version and the on‑disk index format after the standby promotion. During the failover:
- The primary was running
pgvector 0.4.0, which stores vector indexes using thevector_opclassformat introduced in that release. - The standby was promoted after a delayed
pg_basebackupthat captured the data directory while the primary was still onpgvector 0.3.2. - When the promoted standby started, the newer
pgvectorbinary (0.4.0) attempted to read the older index files. The on‑disk layout differs in the header checksum field, causing the index loader to abort and mark the index as corrupted. - Because the promotion also rewound the WAL timeline, the missing
ALTER EXTENSION pgvector UPDATEcommand that would have migrated the index never ran.
Debug
Step‑by‑step investigation that reproduced the issue:
1. Identify corrupted indexes
SELECT schemaname, relname, indexrelid::regclass
FROM pg_indexes
WHERE indexdef LIKE '%vector%';
Result shows the suspect index public.vector_idx.
2. Inspect PostgreSQL logs
2024-09-12 03:14:27.123 UTC [12345] LOG: index "vector_idx" (OID 12345) is marked as invalid
2024-09-12 03:14:27.124 UTC [12345] ERROR: could not read index file "base/16384/12345": Invalid argument
2024-09-12 03:14:27.124 UTC [12345] DETAIL: Index "vector_idx" appears to be corrupted.
3. Verify extension version on the promoted node
SELECT extname, extversion FROM pg_extension WHERE extname='pgvector';
-- Result:
extname | extversion
---------+-----------
pgvector| 0.4.0
4. Compare with the backup metadata
# grep pgvector /var/lib/postgresql/backup/metadata.txt
pgvector 0.3.2
5. Check index file format
# hexdump -C $PGDATA/base/16384/12345 | head
00000000 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000010 4d 61 67 69 63 20 76 65 63 74 6f 72 20 30 2e 33 |Magic vector 0.3|
...
The header contains the string Magic vector 0.3, confirming the older format.
6. Confirm that a simple REINDEX fails
# psql -d mydb -c "REINDEX INDEX public.vector_idx;"
ERROR: could not read index file "base/16384/12345": Invalid argument
Solution
The fix consists of three coordinated actions:
1. Align the extension version with the on‑disk format
Downgrade the pgvector extension on the promoted node to the version that matches the index files (0.3.2). This can be done without a full cluster restart because extensions are loaded per session.
# psql -d mydb -c "DROP EXTENSION IF EXISTS pgvector CASCADE;"
DROP EXTENSION
# psql -d mydb -c "CREATE EXTENSION pgvector VERSION '0.3.2';"
CREATE EXTENSION
2. Rebuild the corrupted indexes using the correct binary
After the downgrade, the index loader can interpret the old format. Reindex each affected index.
# psql -d mydb -c "REINDEX INDEX public.vector_idx;"
REINDEX INDEX
3. Upgrade the extension and migrate indexes to the new format
Once the indexes are healthy, upgrade the extension to the desired version (0.4.0) and run the migration script provided by pgvector.
# psql -d mydb -c "ALTER EXTENSION pgvector UPDATE TO '0.4.0';"
ALTER EXTENSION
# psql -d mydb -c "SELECT pgvector.upgrade_vector_indexes();"
pgvector.upgrade_vector_indexes
-------------------------------
t
(1 row)
Before / After comparison
| State | Extension Version | Index Header | Query Result |
|---|---|---|---|
| Before fix | 0.4.0 | Magic vector 0.3 | ERROR |
| After downgrade & reindex | 0.3.2 | Magic vector 0.3 | Success |
| After upgrade & migration | 0.4.0 | Magic vector 0.4 | Success |
Verify
Run the following checks to confirm that the vector indexes are functional and consistent.
Functional query test
# psql -d mydb -c "
SELECT id, vector <-> '[0.1,0.2,0.3]' AS distance
FROM items
ORDER BY vector <-> '[0.1,0.2,0.3]' LIMIT 5;
"
id | distance
----+----------
42 | 0.00123
17 | 0.00456
...
(5 rows)
Index integrity check
# psql -d mydb -c "SELECT pg_amcheck('public.vector_idx');"
pg_amcheck
------------
t
(1 row)
Log verification
Ensure no further index‑related errors appear in postgresql.log after a short observation window.
# grep vector_idx /var/log/postgresql/postgresql-15-main.log
2024-09-12 03:45:10.321 UTC LOG: index "vector_idx" rebuilt successfully
Prevent
To avoid recurrence during future failovers:
- Pin extension versions in
postgresql.confusingshared_preload_libraries='pgvector=0.3.2'or by managing extensions viapg_upgradescripts. - Include extension upgrades in the promotion workflow. Before promoting a standby, verify that the standby’s extension versions match the primary’s recorded versions (e.g., via
SELECT extname, extversion FROM pg_extension;). - Enable
pg_checksumson the data directory to detect silent corruption early. - Automate index migration. Add a post‑promotion hook that runs
SELECT pgvector.upgrade_vector_indexes();if the target version is newer. - Monitor replication lag and WAL replay status. Use
pg_stat_replicationand alerts onpg_last_xlog_receive_location()vspg_last_xlog_replay_location(). - Test failover in a staging environment. Simulate promotion with mismatched extension versions to verify that the upgrade path works.
Related Topic Hub: Data Infrastructure Troubleshooting Hub
FAQ
- Why does the index work on the primary but not after promotion?
Because the primary and standby were running differentpgvectorversions. The primary’s newer binary could read the newer index format, while the promoted standby used the newer binary against an older on‑disk format, leading to a mismatch. - Can I avoid downgrading the extension?
If you have a recent base backup taken after the extension upgrade, you can promote the standby without downgrading. Otherwise, downgrading is the safest path to recover the existing indexes before migrating them. - Is
REINDEXsufficient after a promotion?
Only if the extension version matches the index format. With a version mismatch,REINDEXfails because the index loader cannot parse the old header. - How do I know which
pgvectorversion created a given index?
Inspect the index file header (e.g.,hexdump -C $PGDATA/base/16384/12345 | grep -i magic) or querypg_extensionhistory tables if you maintain a migration log. - Will this issue affect other extensions that use custom index types?
Yes. Any extension that stores version‑specific metadata in the index file is vulnerable to the same problem. The general mitigation is to keep extension versions synchronized across primary and standby before promotion.