Databases 19 min read

How Milvus 3.0 Embeds Fine‑Grained Ranking: L0 Early Prune, L2 Re‑Rank, and Native XGBoost Scoring

The article dissects Milvus 3.0's Function Chain, explaining how fine‑grained ranking is moved into the engine with a two‑stage L0 early‑prune and L2 post‑reduction design, native XGBoost scoring, typed protobuf pipelines, and the trade‑offs revealed by source‑code analysis and community discussions.

Shuge Unlimited
Shuge Unlimited
Shuge Unlimited
How Milvus 3.0 Embeds Fine‑Grained Ranking: L0 Early Prune, L2 Re‑Rank, and Native XGBoost Scoring

Milvus users who enable the L0 XGBoost FunctionChain encounter a bug where search_iterator() returns the first page on the second page; the distance and raw XGBoost error remain correct, but pagination logic fails. The community hypothesizes that the proxy’s search iterator v2 uses the last returned score as last_bound, causing the cursor to point to a model output instead of a proper ANN score.

The official fix rejects the buggy continuation by making function rerank and search iterator mutually exclusive (PR #51334), highlighting a broader design choice: the new Function Chain API replaces the old function_score and ranker parameters, which only supported predefined scoring formulas.

1. Why the old entry was insufficient

The previous post‑processing entry could not express a multi‑stage retrieval pipeline such as computing freshness, combining ANN distance, freshness and popularity scores, optionally applying an external re‑rank model, rewriting the final score, and then sorting and cutting candidates. Because the old entry lacked ordered, composable steps, users often fetched all results to the client for custom processing.

Milvus 3.0’s release notes state the goal is to move the multi‑stage pipeline into the engine, reducing over‑fetching and eliminating dependence on external post‑processing services. A JSON‑string approach was rejected due to late parsing failures, weak typing, SDK inconsistencies, and ambiguous numeric types, leading to the adoption of a structured protobuf FunctionChain definition in schemapb.

The typed chain guarantees deterministic execution order, nested parameter types (bool, int64, double, string, array, object, bytes), explicit field dependencies, and a unified $score semantics that can be overridden by map operators.

2. Three operators, one chain

The first public release provides only three operators: map, sort, and limit. map evaluates an expression and writes the result to a column (temporary variable or system virtual column $score); sort orders by a column with optional tie‑break columns; limit trims candidates with optional offset.

Chains execute exactly as sent—no implicit tail operators are added. Sorting is explicit, and the engine no longer infers direction from the vector metric type; users must declare the order when $score is overridden.

from pymilvus import FunctionChain, FunctionChainStage
from pymilvus.function_chain import col, fn

chain = (
    FunctionChain(FunctionChainStage.L2_RERANK, name="fresh_popular_rerank")
    .map("freshness", fn.decay(col("published_at"), function="exp", origin=current_time, scale=86400, offset=0, decay=0.5))
    .map("$score", fn.num_combine(col("$score"), col("freshness"), col("popularity"), mode="weighted", weights=[0.7, 0.2, 0.1]))
    .map("$score", fn.round_decimal(col("$score"), decimal=4))
    .sort(col("$score"), desc=True, tie_break_col=col("$id"))
    .limit(10)
)

This chain first computes an exponential decay freshness factor, then combines the original score, freshness, and popularity with weighted averaging, rounds the result, sorts descending by the new $score, and finally returns the top ten.

3. L0 and L2: two rescoring stages

L0 runs on the QueryNode at the segment level before merging. Only map is allowed; the system automatically appends a Sort($score desc, $id) reduce contract to guarantee each segment’s output is already sorted. L0 and L2 are mutually exclusive with the legacy boost‑score entry, and attempting to use both yields the error “boost score and L0 rerank function chain cannot be used together”.

L2 runs on the Proxy after all shard results are merged. It supports the full map / sort / limit set and follows the rerankOperator path in search_pipeline.go. Only the ordinary SearchRequest.function_chains path uses L2, and the current release only supports the L2_RERANK stage; L1 is reported as “not supported yet”.

The design rationale is that L0 sacrifices precision for scalability—early pruning reduces network traffic—while L2 prioritizes precision with full‑result re‑ranking. L0’s restriction to map prevents per‑segment sorting that would break global order after merging.

4. Native XGBoost scoring

In the first release, XGBoost is the only model executable at L0. The design splits the workflow into four layers:

Expression layer ( xgboost_expr.go) validates required fields: a model_resource must be provided, output can be raw or default, at least one feature column is required, and the number of features must match the model.

Resource layer registers the model as a FileResource identified by a path ending in .ubj (binary JSON).

Cache layer ( xgboost_cache.go) caches models keyed by {resource.ID}:{resource.Path}, loads lazily, uses singleflight to avoid duplicate loads, and manages lifecycle with lease/refcount.

Bridge layer uses CGO and the Arrow C Data Interface to pass Arrow arrays to C++.

The C++ side ( xgboost_model_c.cpp) parses the UBJ model with nlohmann::json::from_ubjson, supports only reg:squarederror and binary:logistic objectives, and only the gbtree booster (single tree, no parallel trees, no categorical splits). Prediction iterates trees, adds leaf values, applies sigmoid for logistic output, and respects default handling of missing features.

5. Compatibility with the legacy path

The new chain coexists with the old function_score and legacy reranker by translating the legacy entry into an internal chain representation. Validation rules (in function_chain_validator.go) enforce mutual exclusion between function_score and function_chains, reject hybrid search with function chains, limit each stage to a single occurrence, and restrict system inputs/outputs ( $id, $score) at L2.

Column pruning is performed: only columns referenced by the chain are retained, reducing data movement. Input planning walks the required inputs, resolves schema field IDs for non‑system fields, and treats temporary variables as outputs of previous operators.

Conclusion

Milvus 3.0’s Function Chain moves fine‑grained ranking from the client into the engine, introduces a typed, ordered pipeline, and separates early (L0) and post‑reduction (L2) rescoring to balance scale and accuracy. While the design choices are sound, the first draft leaves open issues such as limited XGBoost booster support, lack of cache capacity management, and the incompatibility of hybrid search with function chains. Future releases that solidify UDF support and address these gaps will determine the long‑term robustness of the approach.

Milvus 3.0 Function Chain information diagram
Milvus 3.0 Function Chain information diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

rankingMilvusvector searchXGBoostL2L0Function Chain
Shuge Unlimited
Written by

Shuge Unlimited

Formerly "Ops with Skill", now officially upgraded. Fully dedicated to AI, we share both the why (fundamental insights) and the how (practical implementation). From technical operations to breakthrough thinking, we help you understand AI's transformation and master the core abilities needed to shape the future. ShugeX: boundless exploration, skillful execution.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.