Architecture meilisearch/meilisearch
Meilisearch Ranking Rules: How the Query Graph Becomes Ordered 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 the Codebase 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, sort, 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 deep dive 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); the exactness and sort rules are their own types (ExactAttribute, Exactness, Sort), 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, andPositionare allGraphBasedRankingRuleinstances that 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 aBucketSortOutputofdocuments_ids,document_scores(aScoreDetailsper rule, so a client can see why a document ranked where it did), and adegradedflag set when the search hit its time budget before the chain was exhausted.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 Search Indexing Components cluster (hub Interned, 812 symbols), which holds SearchContext, QueryGraph, Rank, and ScoreDetails. On the Meilisearch module map it is the connective layer between a parsed query and the index: it calls into the on-disk FieldId cluster 258 times, because every rule ultimately reads postings keyed by field id. The Search struct that kicks the whole thing off sits one cluster over, in Search and Filtering (hub FilterableAttributesRule), which calls into Interned 125 times, the edge that carries a query from the public API into the ranking graph.
The candidate documents that ranking orders, and the word and facet postings each rule reads, are produced by the write side: the indexing pipeline deep dive traces 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.77).
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, and Symvanta flags that module's visit_node, visit_condition, and visit_no_condition as one of the 12 mutually-recursive symbol groups in the codebase: the tree-walk that scores a path through the query graph. The cluster this lives in is one of 52 Meilisearch modules with zero dependency cycles between them.
Auto-generated by Symvanta from the public repo meilisearch/meilisearch at commit df81ba4 , licensed MIT .