Files
clean-architecture-backend-…/docs/mongodb/advanced/search-vector.md
T
DongHyeonkaandClaude Opus 5 d57d2f62a0 feat(mongodb): implement the MongoDB document persistence platform
Implements the mongodb-superpowers-package design: Stable Tasks 1-50 and
Advanced Tasks 1-15.

The design assumes 19 Stable + 12 Advanced Gradle projects under
modules/mongodb*. This repository's fail-closed registry declares exactly 19
leaf identities, so those modules become package boundaries inside the
registered leaf :adapter:outbound:persistence-mongo, with the design's module
dependency table enforced by ten ArchUnit rules. The mapping and every
deviation are recorded in docs/mongodb/repository-adaptation.md.

Contract highlights, all enforced by tests rather than convention:

- Transaction body retry and commit retry are separate loops. A new session per
  body attempt; commit-only retry on an unknown commit. The body is never
  replayed after a commit ambiguity, so a failover cannot become a duplicate.
- MongoExecutionOutcome keeps both ambiguous outcomes distinct from success and
  failure, and MongoFailureContext records only the design-permitted fields.
- Failure classification reads server error labels before numeric codes.
- BSON representations come from a pinned manifest, never a library default,
  and a golden type-signature gate fails on any drift.
- Index and validator changes go through the manifest and the admin plane;
  metadata ownership gates every drop.
- Every Advanced capability refuses construction unless its flag is enabled.

Verified against real servers, not only unit tests. Running the lanes for the
first time exposed four defects that a green `check` had hidden:

- Four release lanes passed while executing zero tests; the gate now counts
  executed tests per lane and fails on zero.
- The "single replica set" fixture was a standalone, because Testcontainers 2.x
  needs withReplicaSet(); its test only asserted a connection string.
- The three-node fixture was three independent clusters, so no election could
  occur, and awaitNewPrimary() compared against the post-stop primary.
- The migration lease checked modifiedCount, so a same-millisecond refresh read
  as a lost lease.

scripts/verify-mongodb-platform.sh now reports:
  9 lanes, 0 skipped, 0 failed, every evidence category produced.

scripts/verify-mongodb-advanced.sh reports NOT PROMOTABLE: actual-topology
evidence (real sharded cluster, real KMS, real target deployment) is
unobtainable here, so it is named rather than assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:41:00 +09:00

4.5 KiB
Raw Blame History

Advanced — Search and Vector Search

Capabilities: MongoCapability.SEARCH, MongoCapability.VECTOR_SEARCH Properties: ca-skeleton.persistence-mongo.advanced.search.enabled, ca-skeleton.persistence-mongo.advanced.vector-search.enabled Status: Experimental (design §3.3). Hybrid search likewise.

Requirements

Topology A deployment with the search service. Atlas Local in a container is a pull-request convenience and is not release evidence.
Privilege MongoPrincipalRole.SEARCH_ADMIN for index management; the application role queries only.
Gate MongoAtlasCapabilityContractSuite on the actual target deployment.

1. Created is not ready

MongoSearchIndexState: CREATEDBUILDINGREADY, plus FAILED and DELETING.

MongoSearchReadinessGate.requireReady(state) refuses anything but READY. A search index is built asynchronously: the create call returns immediately and the index answers queries with partial results while building. Not an error, not empty — partial. A deployment that creates an index and starts querying serves incomplete results for as long as the build takes, and nothing reports it.

2. Search indexes are not application-owned

MongoSearchIndexDescriptor.metadataOwnership() is MongoMetadataOwnership.SEARCH_MANAGED, and droppableByApplicationDrift() is false. The index reconciliation described in ADR-MONGO-004 must not drop it.

3. Query guardrails

MongoSearchQuery binds an index, an allowlist of paths, the search text and a result limit.

  • requireAllowedPaths(allowed) raises MongoOperationRejectedException on a path outside the allowlist. Without it, a caller can search any indexed field, including ones indexed for a different purpose.
  • Search text length and result count are bounded at construction. An unbounded search text is a cost multiplier on someone else's service.

MongoVectorIndexDescriptor.cosine(path, dimensions) declares the index. MongoEmbedding.forIndex(index, values) binds an embedding to it and rejects a dimension mismatch — a 1536-dimension embedding against a 768-dimension index is not a runtime degradation, it is a category error, and catching it at construction beats catching it as a confusing server message.

MongoEmbedding copies its backing array in and out. A vector that shares an array with its caller can be mutated after the query is built, which produces a query nobody wrote.

MongoVectorQuery requires numCandidates > limit — searching 10 candidates to return 10 results is an exhaustive scan wearing an ANN index's name. MongoVectorQuery.nearest(embedding, 10) uses the standard 20× ratio (200 candidates for 10 results).

5. Relevance is the gate, not functionality

MongoVectorSearchBenchmarkGate.standard() requires recall alongside latency and index size. requiredEvidence() names recall explicitly.

This is the difference between search and everything else in the platform. A vector index can be functionally perfect — it accepts the index, accepts the query, returns k results, within the latency budget — and return the wrong k. A gate that measures only latency certifies a fast wrong answer. failures(recall, latencyMs, indexMb) reports which dimension failed so the finding is actionable.

6. Failure recovery

Symptom Cause Action
Incomplete results after a deploy Queried a BUILDING index Wait for READY. The gate prevents this; if it fired, something bypassed it.
MongoOperationRejectedException on a path Path not in the allowlist Add it deliberately, or fix the caller.
Dimension mismatch Model changed A new model means a new index. Build alongside, cut over, then retire.
Recall dropped without a code change The index was rebuilt with different parameters, or the data distribution shifted Re-run the benchmark gate; treat a recall regression like a failing test.
Index FAILED Build error on the search service Search-side diagnosis; the application must not fall back to a scan silently.

7. Promotion evidence

Per ADR-MONGO-ADV-001: the actual target deployment (not Atlas Local), security review of SEARCH_ADMIN, a rebuild path, failure cases (non-ready index refused, disallowed path refused, dimension mismatch refused), this document as the runbook, and relevance evidence. Search and vector search do not promote on functional success.