Repository navigation
mongodb: give every config its own collection and search index (#306) - #308
Merged
Merged
Conversation
The shipped `mongodb-single-node.json` sweep is 12 configs — M ∈ {16,32,64}
× EF_CONSTRUCTION ∈ {100,200,400,800}. All 12 addressed one hardcoded
`bench.vectors` / `vector_index`, because `collection_name` and `index_name`
were read straight from process-wide env with fixed defaults and MongoDB
never joined the per-config namespacing of #151-4.
Two things followed. `configure()` calls `collection.drop()`, so config B
destroyed config A's corpus — which also made the `--keep-data` note in
`experiment::run` ("keeps EVERY config's data resident simultaneously")
false for this engine alone. And `--skip-upload` skips `configure()`
entirely, which is the only caller of `create_vector_index` and therefore
the only place `hnsw_m`/`hnsw_ef_construction` are applied: every config
then queried whichever HNSW graph the first one happened to build. Twelve
result files, twelve distinct M/EF labels, one measurement, exit 0, with
the reuse check printing "server holds N of N" because `countDocuments`
answers cardinality and the corpus it counted really was the right size —
it just belonged to a different config.
The fix routes both names through `engine::index_naming::derive_index_name`,
exactly as redis/valkey/dragonfly/kividb/vectorsets do: `MONGODB_COLLECTION`
and `MONGODB_INDEX_NAME` remain overrides but are now BASES, with the
sanitized config name always appended so a pinned base cannot re-collapse
a sweep, and with the usual `_EXACT` escape hatch. `index_naming` itself is
untouched — no shared behaviour changed.
MongoDB needs one thing the Redis family does not: the server refuses a
`<db>.<collection>` namespace over 255 bytes (verified against
mongodb-atlas-local 8.0.17 — 200 bytes accepted, 250 rejected). So the
collection name is bounded to the budget left by the database name.
Bounding is `<head>~<16 hex FNV-1a-64 over the WHOLE name>`, not a
truncation: two configs sharing a long prefix and differing only in the
tail would otherwise land back on one collection, reinstating this exact
bug through the back door (#294's "untested sanitize-collision path" — it
is tested here). FNV rather than DefaultHasher because SipHash's output is
not guaranteed stable across Rust releases and the name has to resolve to
the same collection from a differently-built binary, or `--skip-upload`
cannot find the corpus it was promised. The search index name is left
unbounded: the same server accepted a 5000-byte one.
MongoDB also joins the #151-4 startup collision guard. Its destructible
namespace is the collection, so that — not the search index — is what the
guard compares, and `MONGODB_COLLECTION_EXACT` with more than one config is
now rejected instead of silently re-arming the shared-collection world.
`corpus_row_count` deliberately stays a pure `countDocuments` and gains no
identity marker (the issue's fallback suggestion). Per-config naming already
answers the question the marker would: a `--skip-upload` run against another
config's corpus now finds no collection of its own, counts 0, and is failed
by the reuse precondition. What remains is the DATASET dimension — one config
reused across two same-sized datasets still certifies — and that is #279,
identical on all 15 engines; a MongoDB-only marker would close it here and
hide it everywhere else.
The `--keep-data` claim at experiment.rs:298-305 is now true for MongoDB, and
there is a test that says so.
Tests (each verified RED by mutating the production code back):
* sweep_configs_derive_distinct_collections_and_indexes — literal names
* shipped_hnsw_sweep_gives_every_config_its_own_collection — reads the real
12-config sweep file, so a 13th colliding config fails here
* long_config_names_are_bounded_without_colliding — the truncation-collision
path; red under both a plain truncate and a full revert
* the_namespace_budget_accounts_for_the_database_name
* bounding_is_deterministic / _survives_a_budget_smaller_than_the_digest /
_cuts_on_a_char_boundary
* collection_env_override_is_a_base_not_a_pin
* test_binary_mongodb_skip_upload_against_another_configs_corpus_is_fatal
* test_binary_mongodb_exact_pin_with_two_configs_is_rejected_at_startup
* test_binary_mongodb_keep_data_keeps_every_configs_corpus
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 10, 2026
Docker Build ValidationPlatforms tested:
Tests performed:
|
…fig-index-306 # Conflicts: # src/bin/vector_db_benchmark/engine/mongodb_engine.rs # tests/integration_mongodb.rs
Docker Build ValidationPlatforms tested:
Tests performed:
|
3 tasks
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.
Closes #306.
The bug
MongoDB addressed one fixed
bench.vectors/vector_indexfor every config, and never adopted the per-config namespacing from #151-4 (used today by redis, valkey, dragonfly, kividb, vectorsets).Under
--skip-upload, the shipped 12-config HNSW sweep (M ∈ {16,32,64} × EF_CONSTRUCTION ∈ {100,200,400,800}) therefore produced 12 result files with 12 distinct HNSW labels, all measured against whichever index the first config built —--skip-uploadskipsconfigure(), andconfigure()is the only caller ofcreate_vector_index.The reuse precondition could not catch it:
corpus_row_countiscountDocuments({}), which interrogates the write store, while queries are served by the Atlas search index — a separate system that can be missing, stale, or built with different parameters while the count is perfect.The fix
collection_nameandindex_namenow derive fromengine::index_naming::derive_index_name, following the Redis-family convention exactly.MONGODB_COLLECTION/MONGODB_INDEX_NAMEremain overrides but are now bases with the sanitized config name appended, with the usual<BASE>_EXACT=1escape hatch — the same precedent #151-4 set forREDIS_INDEX_NAME.MongoDB-specific wrinkle: the server refuses a
<db>.<collection>namespace over 255 bytes (verified live againstmongodb-atlas-local:8.0.17— 200 bytes accepted, 250 rejected). Over-budget names become<head>~<16 hex FNV-1a-64 of the whole name>, hashed rather than truncated, so two configs sharing a long prefix cannot collapse back onto one collection. FNV rather thanDefaultHasherbecause SipHash is not guaranteed stable across Rust releases and the name must resolve identically from a later build for--skip-uploadto find the corpus. The search index name is left unbounded (a 5000-byte one was accepted).MongoDB also joins the #151-4 startup collision guard. Its destructible namespace is the collection, not the index —
configure()drops the collection and the search index lives inside it — so that is what the guard compares, and it calls the engine's own naming function rather than re-deriving, so the guard checks the name production actually uses.index_namingitself is untouched: a 255-byte namespace ceiling is MongoDB-specific knowledge with no Redis analogue, so the bound lives in the engine. Zero conflict surface with other in-flight branches there.Tests
Every test was verified RED by mutation — reverting derivation to
env_or, replacing the hash with plain truncation, droppingdb_namefrom the budget, and removing"mongodb"from the guard.sweep_configs_derive_distinct_collections_and_indexesshipped_hnsw_sweep_gives_every_config_its_own_collectionlong_config_names_are_bounded_without_collidingthe_namespace_budget_accounts_for_the_database_namedb_name.len()+1bounding_is_deterministicbounding_survives_a_budget_smaller_than_the_digestbounding_cuts_on_a_char_boundarycollection_env_override_is_a_base_not_a_pintest_binary_mongodb_skip_upload_against_another_configs_corpus_is_fataltest_binary_mongodb_exact_pin_with_two_configs_is_rejected_at_startup"mongodb"removed from the guard; asserts the error texttest_binary_mongodb_keep_data_keeps_every_configs_corpusexperiment.rs:298-305claim, which is now true for MongoDBVerification
make checkclean ·cargo test --bin vector-db-benchmark883 pass ·cargo test --lib129 pass ·MONGODB_PORT=27018 cargo test --test integration_mongodb25 pass ·overhead_invariants9 pass ·integration_vectorsets exact_pinpasses (its asserted substring is preserved).Reviewer notes — deliberate calls worth challenging
MONGODB_COLLECTION/MONGODB_INDEX_NAMEsemantics change from "exact name" to "base name". Anyone scripting against a fixed collection now getsvectors:<config>;_EXACT=1restores the old behaviour. This follows the Vertex benchmark reliability: 401 handling, token refresh, --engines-file no-op, Redis M×EFC index collision, search watchdog #151-4 precedent, but it is a user-visible break and no doc outside the engine mentions these vars.MONGODB_INDEX_NAME.corpus_row_count. Per-config naming makes a cross-config--skip-uploadcount 0 and fail, so a marker would answer the same question one layer later. The dataset dimension of --skip-upload verifies corpus SIZE, not corpus IDENTITY (cardinality is not identity) #279 (one config reused across two same-sized datasets) is deliberately left open — that is identical on all 15 engines and wants one cross-engine mechanism.Merge order
This conflicts with #305's branch in
mongodb_engine.rsandtests/integration_mongodb.rs(both edit the same file in different regions). #305 fixes a higher-severity bug — recall published against a ~1%-indexed index — so land #305 first and I will rebase this.🤖 Generated with Claude Code