The most common RAG failure I see is not the model. It is retrieval returning the wrong passages. Ask a vector-only system about "ICD-10 J45.40" or a specific contract clause number, and it will happily return text that is semantically similar and factually useless.
The fix has a name, hybrid search, and MongoDB announced general availability of native support for it this year, through two aggregation stages: $rankFusion and $scoreFusion1. As a MongoDB Community Leader I get asked about this a lot, so here is how I think about it.
Why vector search alone isn't enough
Embeddings capture meaning. That is their strength and their weakness. They are good at "how do I cancel my subscription" matching "terminating your plan". They are poor at exact identifiers, rare names, codes and numbers, where a single character changes the answer.
Full-text search has the opposite profile: precise on terms, blind to paraphrase. Hybrid search runs both and merges the results into a single ranked list2. In domains full of codes, like healthcare, finance and legal, I now treat it as the default rather than an optimisation.
Two ways to merge
| Stage | How it merges | When I use it |
|---|---|---|
$rankFusion | By each document's position in each sub-pipeline, using reciprocal rank fusion | The default. Scores from vector and text search aren't comparable, and rank fusion doesn't need them to be |
$scoreFusion | By normalised scores, combined with weights | When you have an evaluation set and want finer control over how much each signal counts |
Reciprocal rank fusion gives each document a score based on the reciprocal of its rank in each list, offset by a constant, and sums across lists2. A document that ranks well in both lists rises to the top. It is simple, hard to break, and needs almost no tuning, which is why I start there.
What a hybrid query looks like
Each retrieval method is a named sub-pipeline. You can weight them, and the stage combines the results24. In outline:
db.chunks.aggregate([
{ $rankFusion: {
input: { pipelines: {
semantic: [
{ $vectorSearch: { index: "chunks_vec", path: "embedding",
queryVector: qv, numCandidates: 200, limit: 20 } }
],
lexical: [
{ $search: { index: "chunks_text",
text: { query: q, path: ["title", "body"] } } },
{ $limit: 20 }
]
} },
combination: { weights: { semantic: 1, lexical: 1 } }
} },
{ $match: { tenant_id: tenantId, acl: { $in: userGroups } } },
{ $limit: 8 }
])
Treat this as a sketch and check the documentation for exact options and version requirements. Two details matter more than the syntax. First, keep candidate counts generous in each sub-pipeline so fusion has something to work with. Second, apply permission filters inside the retrieval stages where your indexes support it, not only after, so users never retrieve what they couldn't open. The final $match above is a belt-and-braces check, not the main control.
Tuning without guessing
Hybrid search makes retrieval better on average. It does not tell you whether it made your retrieval better. That needs an evaluation set.
- Build 100 to 300 real questions with the passages that should answer them. Include exact-term questions on purpose.
- Measure recall at k for vector-only, text-only and hybrid. You want to see hybrid beat both, especially on the exact-term questions.
- Adjust weights only with evidence. If lexical matches dominate in a code-heavy domain, weight them up and re-measure.
- Consider a reranker after fusion. MongoDB's documentation describes reordering fused results by relevance as a further step2. It costs latency, so earn it with numbers.
This is the same discipline I describe in the RAG production checklist: no retrieval change ships without an evaluation run.
Data modelling still decides the outcome
Fusion can't rescue badly shaped data. A few modelling choices make the biggest difference in my experience:
- Chunk by structure, not by character count. Sections, clauses and table rows make better retrieval units than arbitrary windows.
- Store identifiers as fields as well as text. Codes, SKUs and clause numbers in their own fields can be matched exactly and filtered on.
- Keep provenance on every chunk. Source document, version, owner and access list travel with the chunk so answers can cite and permissions can be enforced.
- Link chunks to entities. When chunks reference concepts in a domain model, you can combine hybrid search with graph-style traversal. That is the bridge to the ideas in ontology, knowledge graph and semantic layer.
One database or two?
Running vector search, full-text search and operational data in one place removes a synchronisation problem that bites many RAG systems: the vector store lagging behind the source of truth. MongoDB has been pushing this single-platform approach for a while35. It is not always the right call. At very large scale or with specialised retrieval needs a dedicated engine can still win. For most teams I work with, fewer moving parts beats a marginal relevance gain.
How I can help
I design retrieval and data models for production RAG, from MongoDB schema and indexes to evaluation harnesses. See my AI architecture work, or analyse your challenge and bring the result to a call.
- 1Now GA: Hybrid Search with $rankFusion and $scoreFusionMongoDB · Jun 2026
- 2Perform hybrid search with MongoDB Vector Search and MongoDB SearchMongoDB documentation
- 3Boost search relevance with MongoDB Atlas native hybrid searchMongoDB blog · 2025
- 4
- 5Beyond keywords: hybrid search with Atlas and Vector Search (part 3)DEV Community, MongoDB