8000 fix(aggs): fail weighted_avg on a multi-valued weight, like ES by amartya1523 · Pull Request #2981 · xerj-org/xerj · GitHub
[go: up one dir, main page]

Skip to content

fix(aggs): fail weighted_avg on a multi-valued weight, like ES - #2981

Open
amartya1523 wants to merge 2 commits into
xerj-org:mainfrom
amartya1523:fix/weighted-avg-multi-weight
Open

amartya1523 wants to merge 2 commits into
xerj-org:mainfrom
amartya1523:fix/weighted-avg-multi-weight

Conversation

@amartya1523
Copy link
Copy Markdown
Contributor

AI-assisted change, run and reviewed by @amartya1523.

What this changes, and why

weighted_avg pairs each document's value with one weight. In ES, a document that has a value and more than one weight makes WeightedAvgAggregator#collect throw. Since 8.12 that's a 400 (search_phase_execution_exception, root cause illegal_argument_exception):

Encountered more than one weight for a single document. Use a script to combine multiple weights-per-doc into a single value.

XERJ skipped that document and answered 200 with an average over the rest. The number looks plausible and is wrong for the caller's data. One catch-lenient.txt line is this case: aggregations/weighted_avg.yml · Multi weight field not allowed.

The run_weighted_avg doc comment said why: "The aggregation evaluator has no error channel." It does have one, but only for the bucket cap. A runner can embed {"error": reason, "__error_status__": 400} in its result slot. search_impl already scans for that, but it only acts when the reason is too_many_buckets; any other embedded reason went out inline under a 200.

Fix:

  • Engine (aggs.rs, run_weighted_avg). A document with more than one weight now embeds ES's failure, if it has a value or value.missing gives it one. ES only reads the weights of a document whose value source advances (docValues.advanceExact(doc) && docWeights.advanceExact(doc)), so a multi-weight document without a value is still skipped. The doc comment now describes this.
  • API (es_compat.rs), two changes to the existing channel:
    • The embedded-error scan answers any reason other than too_many_buckets with the existing search_shard_validation_error envelope (400 search_phase_execution_exception → illegal_argument_exception) instead of leaking the marker inline. Today only the bucket-cap paths (aggs.rs, fast_aggs.rs) produce the marker, and they keep their own branch, so they're unchanged.
    • merge_metric_agg lets an embedded failure win over another index's result. Before, merging a failure with a clean weighted_avg from another index could rebuild a "valid" average, which would hide the failure in a multi-index search. In ES, one shard throwing fails the search.

Unchanged: single-valued weights, a multi-valued value field (each value paired with the document's one weight), missing on either side, and the all-zero-weights null.

Evidence

Two new tests, run against main first:

# main 83c9f0d12
test aggs::tests::weighted_avg_refuses_a_multi_valued_weight ... FAILED
  left: Null
 right: "Encountered more than one weight for a single document. Use a script to combine multiple weights-per-doc into a single value."
test es_compat::weighted_avg_multi_weight_tests::a_multi_valued_weight_fails_the_search ... FAILED
  left: 200
 right: 400

# this branch
test aggs::tests::weighted_avg_refuses_a_multi_valued_weight ... ok
test es_compat::weighted_avg_multi_weight_tests::a_multi_valued_weight_fails_the_search ... ok
test es_compat::weighted_avg_multi_weight_tests::single_valued_weights_still_average ... ok
  • Engine test:
    • the marker for a document with a value and two weights
    • a multi-weight document with no value is skipped (the result is the other document's 10)
    • the same document fails once value.missing gives it a value
  • API tests:
    • /multi, /multi,single and /single,multi all answer 400 with the outer type, root-cause type and exact reason. The two multi-index orders cover the merge path from both sides.
    • /single still averages to 4.

Checks

  • cargo fmt -p xerj-api -p xerj-engine -- --check
  • cargo test --profile quick -p xerj-engine --lib: 941 passed, 2 failed. The two are snapshot_path_security_tests::location_inside_data_dir_is_allowed and ::explicit_allowlist_permits_an_external_root. They fail the same way with main's code on this macOS machine (re-run with aggs.rs reset to main); they don't touch aggregations. CI is Linux.
  • cargo test --profile quick -p xerj-api --lib: 432 passed, 0 failed
  • YAML case: the existing weighted_avg.yml · Multi weight field not allowed now enforces it; its catch-lenient line is deleted. Multi value field (multi-valued value, single weight) is unchanged.
  • Docs: the run_weighted_avg doc comment that described the old skip is updated.

Not run locally:

  • The ES-YAML suite, --release builds, clippy and the integration binaries. Disk on this machine is at 99%; CI runs all of these.

Provenance

  • Written by: AI-assisted, run and reviewed by @amartya1523
  • Verified: the test outputs above, on macOS 26 (aarch64), quick profile, based on main 83c9f0d12.
  • Assumed:
    • The reason text follows ES's WeightedAvgAggregator; the YAML only asserts bad_request.
    • ES fails only the shards that hold such a document, so a multi-index search where just one index has it would be a partial 200 with _shards.failures in ES. Here it's a 400, the same as too_many_buckets already behaves, since this path has no partial-failure response.

ES's WeightedAvgAggregator throws when a document that has a value
carries more than one weight ("Encountered more than one weight for a
single document. Use a script to combine multiple weights-per-doc into
a single value."), and since 8.12 that is a 400. XERJ skipped such a
document and answered 200 with an average over the rest, because the
aggregation evaluator had no way to fail a request.

It does now, through the slot marker the bucket-cap check already uses:
run_weighted_avg embeds the failure, the API answers any embedded
reason other than too_many_buckets with the shard-level
illegal_argument_exception 400 instead of leaking it inline, and
merge_metric_agg lets an embedded failure win over another index's
result. A multi-weight document without a value is still skipped, as
ES only reads the weights of a document it collects.

Retires `aggregations/weighted_avg.yml · Multi weight field not allowed`.
Engine: the marker for a multi-weight document with a value or value.missing, and the skip without one. API: the 400 alone and merged with a clean index in both orders; single weights still average.
@cla-bot cla-bot Bot added the cla-signed label Nov 5, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0