Architecture meilisearch/meilisearch
Meilisearch Ranking Rules, Query to Results
How to read this page
Symvanta parsed this repository into a code graph: every function, class, and method is a node, and every call or import between them is an edge. Everything on this page is computed from that graph at the commit shown above. This page is a deep dive into one subsystem of Meilisearch Architecture: How It Actually Works; the hub page holds the whole-repo module map and glossary.
Once a Meilisearch query has narrowed the index to a set of candidate documents, something has to decide their order. That is the job of the ranking-rule chain: an ordered list of rules (words, typo, proximity, attribute rank, sort, word position, and exactness by default) applied one after another, each breaking the ties the previous rule left behind. The chain is not a hardcoded switch: it is built as a Vec of boxed trait objects and walked by a single function, bucket_sort, which is what makes the ordering configurable per index and per query. This page traces a query from execute_search through rule-chain construction into bucket_sort and out as ranked hits, using the graph generated by Symvanta.
The rules operate over a query graph, not raw strings. Meilisearch interns query terms (Interned, SearchContext) into a QueryGraph of alternative interpretations (typo variants, word splits, synonyms), and most ranking rules are GraphBasedRankingRules that differ only in which graph they walk. A hybrid search additionally blends this keyword ordering with vector similarity through a SemanticRatio. The postings these rules read are produced by the write side (the indexing pipeline trace covers that half).
The moving parts
execute_searchSearchContextInternedQueryGraphget_ranking_rules_for_query_graph_searchRankingRuleBoxRankingRuleGraphBasedRankingRulebucket_sortSemanticRatio
execute_search is the entry point for the whole ranking step, and SearchContext is the object it threads through everything: it holds the read transaction, the term Interned pool, and the QueryGraph. get_ranking_rules_for_query_graph_search is the builder that reads the index's configured criteria and produces the ordered rule chain. RankingRule is the trait every rule implements, and BoxRankingRule is the boxed-trait-object alias the chain is actually a Vec of. Most default rules are GraphBasedRankingRule instances parameterized by their graph (words, typo, proximity, field id, position, and exactness, which is a type alias for GraphBasedRankingRule<ExactnessGraph>); ExactAttribute and Sort are standalone types, and VectorSort and GeoSort cover semantic and geo ordering. bucket_sort is the walker that turns the chain into a ranked list, and SemanticRatio is the single knob that blends keyword and vector results in a hybrid search.
How it works
Here is the canonical path a text query takes through ranking, from execute_search into bucket_sort and back out.
execute_searchis the ranking entry point. It receives aSearchContext, an optional(QueryGraph, located terms)pair, the sort criteria, and auniversebitmap of the candidate documents, and returns aPartialSearchResultof ordered document ids and scores.SearchContextcarries the in-flight query. Terms are interned viaInternedso the ranking rules compare small integer ids instead of strings, and the query is expanded into aQueryGraphof alternative interpretations (typo variants, word splits, synonyms) that the graph-based rules walk.get_ranking_rules_for_query_graph_searchbuilds the chain by iterating the index's configured criteria (ctx.index.criteria) and pushing one or more rules per criterion. The default list (default_criteria) isWords,Typo,Proximity,AttributeRank,Sort,WordPosition,Exactness; attribute-rank pushes anFidrule, word-position pushes aPositionrule, and exactness pushes anExactAttributerule followed by anExactnessrule, so the short criteria list becomes a longerVec<BoxRankingRule>.get_ranking_rules_for_placeholder_searchbuilds the shorter chain for a filter-only browse with no query terms.RankingRuleis the trait every rule in thatVecimplements. Its interface is bucket-at-a-time (start_iteration,next_bucket,end_iteration): a rule yields its next bucket of equally-ranked documents on demand rather than sorting the whole set up front.Words,Typo,Proximity,Fid,Position, andExactnessare all aliases forGraphBasedRankingRulethat differ only in which ranking-rule graph they walk.bucket_sortwalks the chain. It asks the first rule for its next bucket of tied documents, and for each bucket recurses into the next rule to break the tie, down the chain, until it has collected enough documents to fill the requested window.apply_distinct_ruledrops duplicates by the configured distinct field as buckets are emitted.bucket_sortreturns aBucketSortOutputofdocids,scores(aScoreDetailsper rule, so a client can see why a document ranked where it did),all_candidates, and adegradedflag set when the search hit its time budget before the chain was exhausted.execute_searchmoves those into thedocuments_idsanddocument_scoresfields of thePartialSearchResultits caller receives.execute_hybridis the alternate top of the flow when a query sets a semantic ratio. It runs the keyword search above and a vector search, then blends the two orderings bySemanticRatio, a float in the range 0 to 1 where 0 is pure keyword and 1 is pure semantic.
Where it connects
The ranking engine lives in the Scheduler Runtime and Query Execution cluster (hub Result, 1413 symbols), which holds Search, SearchContext, QueryGraph, ScoreDetails, and every type that implements RankingRule. On the Meilisearch module map it is the connective layer between a parsed query and the index: it calls into Index Storage and Write Pipeline 253 times and into On-disk Codecs and Field Ids 128 times, because every rule ultimately reads postings keyed by field id. The interned terms those rules compare come from Query Term Interning and Bitmaps (hub Interned, 591 symbols), whose 162 calls back into the ranking cluster are its heaviest outbound edge.
The candidate documents that ranking orders, and the word and facet postings each rule reads, are produced by the write side: the indexing pipeline trace shows how extract_all and write_to_db build exactly the CboRoaringBitmapCodec postings that bucket_sort walks here. Both halves meet at the same LMDB Index, which is why the design invariant is simple: indexing writes postings under a write transaction, and ranking reads them under a read transaction, with no cycle between the two (the graph reports zero dependency cycles at modularity Q=0.78).
By the numbers
The ranking chain is assembled from seven default criteria that expand into a longer list of concrete rules, and bucket_sort (lines 23 through 343 of bucket_sort.rs) is the one function that walks all of them. Five distinct types implement the RankingRule trait directly (GraphBasedRankingRule, Sort, ExactAttribute, VectorSort, GeoSort), with the word, typo, proximity, field-id, and position rules all sharing the single GraphBasedRankingRule implementation. Each GraphBasedRankingRule is parameterized by a RankingRuleGraphTrait, one implementation per graph the chain walks: WordsGraph, TypoGraph, ProximityGraph, FidGraph, PositionGraph, and ExactnessGraph. Symvanta reports 7 sets of mutually recursive symbols across the codebase at this commit, and none of them sit in the ranking-rule graph module: the recursion that is left lives in the filter grammar, the JSON walkers, and the field-id map. The cluster this ranking chain belongs to is one of 61 Meilisearch modules with zero dependency cycles between them.
Auto-generated by Symvanta from the public repo meilisearch/meilisearch at commit 577f7af , licensed MIT .