fix(vector): persist database_id in vector index catalog entry - #263
Open
EnRaiha wants to merge 1 commit into
Open
fix(vector): persist database_id in vector index catalog entry#263EnRaiha wants to merge 1 commit into
EnRaiha wants to merge 1 commit into
Conversation
CREATE VECTOR INDEX keys everything under the session's real database id, but StoredVectorIndexParams never recorded it, so on boot the seed and the durable-store rebuild both hardcoded DatabaseId::DEFAULT (0). Any collection in a non-default database (e.g. db 'graph', id >= 1024) was never seeded, never rebuilt, and SEARCH returned 0 rows despite a successful CREATE. - add database_id as the 12th (last) field of StoredVectorIndexParams; zerompk structs serialize as arrays here, so field order is on-disk format and the new field is appended last - decode_vector_index_params() ladder: try 12-field struct, fall back to the legacy 11-field tuple filling database_id = 0, so existing catalog entries keep loading without a migration - seed_vector_index_params keys vector_params/index_configs/declared_dims on e.database_id instead of DatabaseId::DEFAULT - rebuild_vector_indexes_from_store groups targets by (database_id, tenant_id, collection) and scans the entry's real database - floats_from_value(): schemaless arms now also accept JSON-string embeddings (Value::String '[0.1,...]') transcoded from columnar TEXT/JSON, which the Array-only match previously dropped silently Verified live: strict vector(8) collection in db default rebuilds and SEARCHes (rebuilt=3, 3 rows); db 'graph' collection had no rebuild log until the catalog entry carried its database_id.
EnRaiha
force-pushed
the
fix/vector-database-id
branch
from
August 27, 2026 17:56
0058f5f to
03d7b6f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vector Index Rebuild Fails for Non-Default Databases; Schemaless JSON Embeddings Dropped
Branch:
fix/vector-rebuild-strict(basecc4a0a2a6)Files changed: 6 · +180 / −49
Status: Patch verified in live deployment (partial) — SEARCH returns rows for strict collections in the default database; non-default-database rebuild pending restart after re-embed.
TL;DR
Two independent defects prevent HNSW vector indexes from materializing for real workloads:
StoredVectorIndexParamscarries nodatabase_id. The boot-time seed and the durable-store rebuild both hardcodeDatabaseId::DEFAULT(0), so any collection living in a non-default database (e.g. thegraphdatabase, id ≥ 1024) is never seeded, never rebuilt, and itsSEARCHreturns 0 rows — even thoughCREATE VECTOR INDEXsucceeded and the catalog entry exists.Value::Arrayembeddings. Schemaless collections whose embedding column is stored/transcoded as a JSON string ("[0.1, 0.2, ...]") are silently skipped byapply_point_put_vector_indexes, so the durable-store rebuild indexes nothing for those collections.Root Cause 1 —
database_idis lost between CREATE and bootChain of custody
VShardId::from_collection_in_databasemixesdatabase_idinto the vshard hash (nodedb-types/src/id/vshard.rs:48), so at runtime the CREATE path keys everything under the real database id.schemaless_vector_field_namesandstrict_vector_fieldsthen look upvector_paramsunder(0, tid, ...), find nothing, and the HNSW stays empty.Evidence (live)
vtest(strict, vector(8))default(id 0)INFO vector_index_rebuild: rebuilt vector index from durable store core=0 collection=vtest rebuilt=3code2g_nodes(37,325 rows, 1024-dim)graph(id ≥ 1024)The same binary, the same boot, the same code path — the only difference is the database id. That isolates the defect beyond doubt.
Root Cause 2 — schemaless embeddings stored as JSON strings are dropped
apply_point_put_vector_indexes(strict and schemaless arms) matched only:But a schemaless body transcoded from a columnar TEXT/JSON column (or ingested via doc-object UPSERT where the column is projected as JSON) decodes to
Value::Stringcontaining"[0.1, 0.2, ...]". The pattern match fails, the document is silently skipped, and the rebuild completes with 0 vectors — indistinguishable from "no embeddings exist."Fix
1. Persist
database_idin the catalog entrynodedb-types/src/vector_index_params.rs:database_id: u64as the 12th field ofStoredVectorIndexParams.default-as-mapfeature is not enabled), so field order is part of the on-disk format — the new field is appended last to keep the legacy shape decodable.2. Legacy decode ladder (backward compatible)
nodedb/src/control/security/catalog/vector_index_params.rs:decode_vector_index_params(): tries the 12-field struct first; on failure falls back to the legacy 11-field tuple and fillsdatabase_id: 0.get_vector_index_paramsandlist_all_vector_index_paramsroute through it, so existing on-disk entries (written by older builds) keep loading — no catalog migration required.3. Seed uses the real database id
nodedb/src/data/executor/core_loop/vector_index_seed.rs:seed_vector_index_paramsnow keysvector_params/index_configs/declared_dimswithe.database_idinstead ofDatabaseId::DEFAULT.4. Rebuild scans the real database
nodedb/src/data/executor/core_loop/vector_index_rebuild.rs:rebuild_vector_indexes_from_storegroups targets by(database_id, tenant_id, collection)from the durable entries, and passes the entry'sdatabase_idintosparse_body_format,scan_documents_for_each, andapply_point_put_vector_indexes.let db = DatabaseId::DEFAULT.as_u64().VectorSidecarencodings (vector-primary collections have no field in the sidecar; their durability is served byreplay_direct_upsertinwal_replay_vector_extended.rs).5. Accept JSON-string embeddings in both put arms
nodedb/src/data/executor/handlers/point/apply_put/vector/put.rs:floats_from_value()helper: acceptsValue::Array(native msgpack) orValue::String(JSON array"[0.1,...]", or comma/whitespace-separated list).check_vector_width,RejectedConstrainton dim mismatch) unchanged.Test Plan
Unit (passing):
Live verification (vector-only build from this branch, deployed):
CREATE TABLE vtest (id int primary key, embedding vector(8))+ 3 INSERTs (default db) → restartrebuilt vector index from durable store core=0 collection=vtest rebuilt=3SEARCH vtest USING VECTOR(embedding, ARRAY[0.1,...], 3)→ 3 rows, distance 0.92SHOW VECTOR INDEX status ON vtest→ dimensions=8, metric=cosine, index_type=hnswfiles_written=1) and restored on next boot (loaded=1 vectors=9)code2g_nodes(dbgraph) now has a fresh catalog entry carryingdatabase_id=graph; a restart after the re-embed run triggers the rebuild of all 37k+ rows (in progress).Files
nodedb-types/src/vector_index_params.rsdatabase_idfield + roundtrip testsnodedb/src/control/security/catalog/vector_index_params.rsnodedb/src/control/server/shared/ddl/neutral/dsl/vector_index.rsdatabase_idinto stored paramsnodedb/src/data/executor/core_loop/vector_index_seed.rse.database_idnodedb/src/data/executor/core_loop/vector_index_rebuild.rsdatabase_idnodedb/src/data/executor/handlers/point/apply_put/vector/put.rsfloats_from_value(Array + JSON String) in both armsOut of scope / notes
VectorParamsrecord already carries the correctdatabase_id(it routes throughtask.request.database_id); only the durable catalog seed was losing it. No WAL format change.#[msgpack(map)]route was considered and rejected: switching the struct's zerompk representation from array to map would break decoding of every existing catalog entry, which is exactly what the ladder avoids.checkpoint_durable_lsnmay log avector checkpoint flush failed ... No such file or directorywarning for a dropped vector-primary collection's stale checkpoint path — benign (clamps LSN), pre-existing, unrelated to this change.