Keyword search finds the exact model number; vector search finds the paraphrase. Hybrid search runs both and merges the lists, and server 8.0 added a stage for it. $rankFusion ignores scores and combines ranks with reciprocal rank fusion: each pipeline contributes weight / (60 + rank) to every document it returned. Needing only an ordering, it accepts any ranked pipelines and runs on plain Community.
db.articles.aggregate([
{ $rankFusion: {
input: { pipelines: {
text: [{ $match: { $text: { $search: 'docker mongodb volume' } } },
{ $sort: { score: { $meta: 'textScore' } } }],
popular: [{ $sort: { views: -1 } }, { $limit: 4 }] } },
combination: { weights: { text: 2, popular: 1 } }, scoreDetails: true } },
{ $project: { title: 1, views: 1, _id: 0, s: { $meta: 'scoreDetails' } } }
]).forEach(d => print(d.title + ' views ' + d.views + ' score ' + d.s.value.toFixed(5) +
' ' + d.s.details.map(x => x.inputPipelineName + ' rank ' + x.rank).join(', ')));Running MongoDB in Docker views 900 score 0.04918 popular rank 1, text rank 1 Volumes, mounts and backups views 300 score 0.04839 popular rank 2, text rank 2 Indexing strategies for MongoDB views 40 score 0.04737 popular rank 4, text rank 3 Sharding a busy cluster views 120 score 0.01587 popular rank 3, text rank NA
Check the first row: rank 1 in both pipelines gives 1/(60+1) + 2/(60+1), which is 3/61, or 0.04918. That fixed 60 makes RRF robust: adjacent ranks differ little, so no pipeline dominates through a runaway score, and weights shift emphasis without the score scales being comparable. Sharding a busy cluster falls behind because only the popularity pipeline returned it, text rank NA. When the scores are comparable, server 8.3 makes $scoreFusion generally available: the same input.pipelines, plus input.normalization (none, sigmoid, minMaxScaler) and combination.method avg or an expression. The $score stage from 8.2 feeds it any signal — { $score: { score: '$views', normalization: 'minMaxScaler' } } ranked those four articles 1.000, 0.302, 0.093 and 0.000 here. In production you fuse $search with $vectorSearch, which needs Atlas 1,815 .