Databases 14 min read

Milvus 3.0 Aggregation Inaccuracy: It's Missing Candidates, Not Safe Factor

The article explains that Milvus 3.0 search aggregation operates on ANN candidates, not full collections, so missing buckets stem from candidate budget limits (topK and TopHits.size), not the safe_factor parameter; it distinguishes candidate-signal counts from exact filtered counts and advises choosing the right query path for each need.

Shuge Unlimited
Shuge Unlimited
Shuge Unlimited
Milvus 3.0 Aggregation Inaccuracy: It's Missing Candidates, Not Safe Factor

When running post-retrieval analytics in Milvus — such as grouping by brand to count hits per brand — a brand that definitely exists in the collection may be absent from the aggregation result. It does not appear with count: 0; it simply disappears. The instinct is to blame the aggregation logic or a misconfigured parameter, but Milvus Search Aggregation does not perform a full-table GROUP BY. Its pipeline is: retrieve ANN candidates, build buckets from those candidates, then calculate and return results. Aggregation happens in the second half of the retrieval chain and never bypasses the initial ANN step to scan the whole collection.

01 Why Buckets Go Missing

In a traditional database, GROUP BY brand scans all matching rows and merges identical brands. If a brand is missing, we assume its count is zero or the implementation skipped it. Search Aggregation works differently: the entry point is vector search. First, ANN candidates close to the query vector are fetched; then those candidates are grouped into buckets; finally metrics are computed. The Milvus documentation describes this chain as Retrieve candidates → Build buckets → Calculate and return results.

The Milvus grouping-search documentation gives a concrete consequence: when a document is split into many chunks, ANN results may fill up with multiple chunks from the same document, crowding out other documents. Grouping merges those chunks under one key, consuming only one bucket slot, so other documents get a chance to appear. Grouping makes results more diverse, but it cannot make entities that never entered the ANN result set suddenly appear.

Standard filtering does not change this premise. Milvus filters entities first, then runs ANN on the filtered set, and returns the top K entities. Filtering narrows the search space but does not turn the subsequent ANN into a full scan of the filtered results.

The source code confirms the input boundary. SearchAggregationComputer receives SearchResultData that has already been cross-shard reduced. It then groups hierarchically, accumulates metrics, generates top hits and sub-aggregations. It processes only the rows that have reached the Proxy; there is no code path that goes back to the collection to fetch missing rows.

Bucket existence itself does not guarantee precision. If a key's rows never entered the ANN retrieval pool, that key is invisible — no bucket appears for it, and no empty bucket with count: 0 is emitted.

02 Candidate Budget Determines What You Are Counting

Accepting that aggregation only sees candidates, we must separate several quantities that are often conflated. ANN topK controls the retrieval gateway: how many candidates can enter the pipeline initially.

After entering the aggregation tree, how many candidates each full composite key can retain is governed by the largest TopHits.size in the entire aggregation tree — the candidate budget is allocated per key, not a fixed quota per bucket. One controls the gateway, the other controls the tree; they are not the same number.

After grouping and retention, the rows actually fed into computation are retained rows. The returned count and all metrics are derived from these rows. If no level (aggregation level/grouping level in the request) configures top_hits, the budget falls back to a minimum of 1: each full composite key retains only a single candidate row. Under default configuration, each key's count comes from that one retained row, reflecting a candidate signal, not the true row count for that key in the collection; therefore the default aggregation count can be very small. A deployment may configure top_hits at some level to enlarge this budget; whether it exists and its value must be verified against the deployed configuration.

Why does a brand that exists show a mismatched count? Its rows may never have entered the ANN pool, or they entered but lost the subsequent candidate-retention competition. Increasing TopHits.size only affects candidates already in the pipeline; it cannot pull in rows from the collection that never passed the ANN stage. sub_aggregation propagates the same limitation downward. Milvus continues grouping and computing inside each parent bucket; the recursive entry point in the source passes the current bucket's bucket.rows to the next level's computeLevel. If the parent bucket's rows are already incomplete, the child buckets can only see that subset. The Milvus design document calls this layered propagation "bias accumulation."

Nested results continue computing along the rows retained by the parent bucket: the parent keeps only a portion of candidates, so the sub-aggregation can only group and count those rows.

03 Safe Factor Only Makes Approximation More Stable

The Milvus design document defines group_count_safe_factor as a knob that makes Segcore fetch more composite-key tuples, letting the Proxy perform subsequent ordering and size trimming. This reduces the chance that small buckets are crowded out by large buckets, addressing insufficient tuple coverage or candidate competition. This is the intended use described in design materials; whether this key actually exists in your deployed release is a version-verification question (see Section 04).

However, it does not change the statistical object. Rows never recalled by ANN are invisible to safe factor; rows already in the pipeline do not automatically become a full sample just because the factor is increased. It can expand candidate coverage, but it cannot turn approximate statistics into exact statistics; used together with other knobs, it merely lowers the probability and severity of approximation error.

This also defines safe factor's applicability boundary: when small buckets are squeezed out by large ones, raising it is a reasonable mitigation; if the requirement is exact full count, avg, or sum, the question returns to the choice of statistical object.

04 Choose the Statistical Object First, Then Decide the Path

This boundary is not a Proxy oversight but a deliberate layered trade-off. The Milvus design document splits responsibilities: Segcore performs flat composite-key group-by; QueryNode/Delegator merges cross-segment results; metrics, ordering, nested levels, and tree-shaped results are handled by the Proxy. The Proxy can reconstruct more complex trees from flat composite keys, but it can only rebuild using the rows it received — it cannot magically know rows that never passed the ANN early-stop.

Therefore, the current Search Aggregation answers "grouping signal within vector candidates." It is suitable for inspecting brand or document distribution inside retrieval candidates, representative results, and low-latency relative ranking in recommendation or RAG scenarios. The count and metrics here must be interpreted as candidate signals, not as full-collection facts.

If the requirement is the true quantity of filtered full data, or auditable count, sum, avg for BI, finance, or operations reports, the problem has shifted: you must go back to your Milvus deployment version and verify the alternative query-side/scalar aggregation path. This article does not present that path as a confirmed API. At minimum, you must check whether filter conditions match, whether fields and aggregation functions are supported, whether nesting is supported, and what the final consistency semantics are.

The evidence boundary for version verification lies here as well: the design document gives a default of 4 for group_count_safe_factor, but that is only a default contract in design materials. In the source commit 82bcce8867c92e7d55f669e034a00dcb0f37d2bc, the file configs/milvus.yaml does not contain group_count_safe_factor. These research materials do not equal any user's running instance; the verification order is: first check your deployed version's configuration docs or example configs for this key, then confirm its default and whether a feature flag controls it. The design doc's default 4 and this article's inspected commit cannot answer for your deployed version.

Before deciding on a path, clarify the statistical object: do you need the candidates left by vector retrieval, or the full data satisfying the filter? The former can tolerate approximation from candidate budgets; the latter demands full filtered facts. Both are called "aggregation," but they correspond to two different paths in the system.

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.

vector databaseMilvustopKANNgroup bysearch aggregationcandidate budgetsafe factor
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.