diff --git a/benches/agg_bench.rs b/benches/agg_bench.rs index 2c83461c8..17d0dcc23 100644 --- a/benches/agg_bench.rs +++ b/benches/agg_bench.rs @@ -1,5 +1,5 @@ use binggan::plugins::PeakMemAllocPlugin; -use binggan::{black_box, InputGroup, PeakMemAlloc, INSTRUMENTED_SYSTEM}; +use binggan::{black_box, BenchRunner, PeakMemAlloc, INSTRUMENTED_SYSTEM}; use rand::distr::weighted::WeightedIndex; use rand::rngs::StdRng; use rand::seq::IndexedRandom; @@ -15,255 +15,232 @@ use tantivy::{doc, DateTime, Index, Term}; #[global_allocator] pub static GLOBAL: &PeakMemAlloc = &INSTRUMENTED_SYSTEM; -/// Mini macro to register a function via its name -/// runner.register("average_u64", move |index| average_u64(index)); -macro_rules! register { - ($runner:expr, $func:ident) => { - $runner.register(stringify!($func), move |index| { - $func(index); - }) - }; - ($runner:expr, $func:ident, $filtered_func:ident) => { - register!($runner, $func); - register!($runner, $filtered_func); - }; -} +type AggregationRequest = serde_json::Value; +type AggregationExecutor = fn(&Index, AggregationRequest); +type BenchmarkConfig = (&'static str, AggregationRequest); +type BenchmarkGroup = (&'static str, AggregationExecutor, Vec); -macro_rules! define_multi_terms_benchmark { - ( - $(#[$meta:meta])* - $name:ident, - $filtered_name:ident, - $agg_req:expr $(,)? - ) => { - $(#[$meta])* - fn $name(index: &Index) { - execute_agg(index, $agg_req); - } - - fn $filtered_name(index: &Index) { - execute_agg_filtered(index, $agg_req); - } +macro_rules! benchmark_config { + ($request:ident) => { + (stringify!($request), $request()) }; } fn main() { - let inputs = vec![ - ("full", get_test_index_bench(Cardinality::Full).unwrap()), - ( - "dense", - get_test_index_bench(Cardinality::OptionalDense).unwrap(), - ), - ( - "sparse", - get_test_index_bench(Cardinality::OptionalSparse).unwrap(), - ), - ( - "multivalue", - get_test_index_bench(Cardinality::Multivalued).unwrap(), - ), + let inputs = [ + ("full", Cardinality::Full), + ("dense", Cardinality::OptionalDense), + ("sparse", Cardinality::OptionalSparse), + ("multivalue", Cardinality::Multivalued), ]; - bench_agg(InputGroup::new_with_inputs(inputs)); + let mut runner = BenchRunner::new(); + runner.add_plugin(PeakMemAllocPlugin::new(GLOBAL)); + + for (input_name, cardinality) in inputs { + let index = get_test_index_bench(cardinality).unwrap(); + // On sparse this will not effectively filter anything. This should simulate co-located + // data which are sparse. + // So for sparse aggregation, although the value is sparse all values in the aggregation + // that are fetched do exist. + // We want to make sure we perform well in these cases. + let execute_filtered: AggregationExecutor = if cardinality == Cardinality::OptionalSparse { + execute_agg_filtered_on_single_term + } else { + execute_agg_filtered + }; + runner.set_name(input_name); + bench_agg(&mut runner, &index, execute_filtered); + } } -fn bench_agg(mut group: InputGroup) { - group.add_plugin(PeakMemAllocPlugin::new(GLOBAL)); +fn bench_agg(runner: &mut BenchRunner, index: &Index, execute_filtered: AggregationExecutor) { + let multi_terms_vs_nested = vec![ + benchmark_config!(nested_terms_status_and_zipf_1000), + benchmark_config!(multi_terms_status_and_zipf_1000), + benchmark_config!(nested_terms_status_and_zipf_1000_with_missing), + benchmark_config!(multi_terms_status_and_zipf_1000_with_missing), + benchmark_config!(nested_terms_zipf_1000_and_status), + benchmark_config!(multi_terms_zipf_1000_and_status), + benchmark_config!(nested_terms_many_and_zipf_1000), + benchmark_config!(multi_terms_many_and_zipf_1000), + benchmark_config!(nested_terms_many_and_zipf_1000_and_status), + benchmark_config!(multi_terms_many_and_zipf_1000_and_status), + benchmark_config!(nested_terms_status_and_zipf_1000_avg_sub_agg), + benchmark_config!(multi_terms_status_and_zipf_1000_avg_sub_agg), + ]; - register!(group, average_u64); - register!(group, average_f64); - register!(group, average_f64_u64); - register!(group, stats_f64); - register!(group, extendedstats_f64); - register!(group, percentiles_f64); - register!(group, terms_7); - register!(group, terms_all_unique); - register!(group, terms_all_unique_order_by_key); - register!(group, terms_150_000); - register!(group, terms_many_top_1000); - register!(group, terms_many_order_by_term); - register!(group, terms_many_with_top_hits); - register!(group, terms_all_unique_with_avg_sub_agg); - register!(group, terms_many_with_avg_sub_agg); - register!(group, terms_status_with_avg_sub_agg); - register!(group, nested_terms_status_and_zipf_1000); - register!(group, nested_terms_status_and_zipf_1000_with_missing); - register!(group, nested_terms_zipf_1000_and_status); - register!(group, nested_terms_many_and_zipf_1000); - register!(group, nested_terms_many_and_zipf_1000_and_status); - register!(group, nested_terms_status_and_zipf_1000_avg_sub_agg); - register!(group, terms_status_with_histogram); - register!(group, terms_status_with_date_histogram); - register!(group, terms_status_with_date_histogram_hard_bounds); - register!(group, terms_status_with_date_histogram_and_sibling_terms); - register!(group, terms_zipf_1000_only); - register!(group, terms_zipf_1000_with_histogram); - register!(group, terms_zipf_1000_with_avg_sub_agg); - register!(group, terms_zipf_90); - register!(group, terms_zipf_90_with_sum_sub_agg); + let mut groups: Vec = Vec::new(); + groups.push(( + "metrics", + execute_agg, + vec![ + benchmark_config!(average_u64), + benchmark_config!(average_f64), + benchmark_config!(average_f64_u64), + benchmark_config!(stats_f64), + benchmark_config!(extendedstats_f64), + benchmark_config!(percentiles_f64), + benchmark_config!(cardinality_agg_low_card), + benchmark_config!(cardinality_agg), + benchmark_config!(cardinality_agg_high_card), + benchmark_config!(terms_status_with_cardinality_agg), + benchmark_config!(terms_100_buckets_with_cardinality_agg), + benchmark_config!(terms_many_with_single_term_order_by_card), + benchmark_config!(terms_many_with_single_term_2_order_by_card), + ], + )); + groups.push(( + "terms", + execute_agg, + vec![ + benchmark_config!(terms_7), + benchmark_config!(terms_zipf_1000), + benchmark_config!(terms_zipf_90), + benchmark_config!(terms_zipf_90_with_sum_sub_agg), + benchmark_config!(terms_150_000), + benchmark_config!(terms_many_top_1000), + benchmark_config!(terms_many_order_by_term), + benchmark_config!(terms_all_unique), + benchmark_config!(terms_all_unique_order_by_key), + benchmark_config!(terms_all_unique_with_avg_sub_agg), + benchmark_config!(terms_many_with_avg_sub_agg), + benchmark_config!(terms_status_with_avg_sub_agg), + benchmark_config!(terms_zipf_1000_with_avg_sub_agg), + benchmark_config!(terms_many_json_mixed_type_with_avg_sub_agg), + benchmark_config!(terms_many_with_top_hits), + benchmark_config!(terms_status_with_histogram), + benchmark_config!(terms_zipf_1000_with_histogram), + benchmark_config!(terms_status_with_date_histogram), + benchmark_config!(terms_status_with_date_histogram_hard_bounds), + benchmark_config!(terms_status_with_date_histogram_and_sibling_terms), + ], + )); + groups.push(( + "multi_terms_vs_nested", + execute_agg, + multi_terms_vs_nested.clone(), + )); + groups.push(( + "multi_terms_vs_nested_filtered", + execute_filtered, + multi_terms_vs_nested, + )); + groups.push(( + "range_and_histogram", + execute_agg, + vec![ + benchmark_config!(range_agg), + benchmark_config!(range_agg_with_avg_sub_agg), + benchmark_config!(range_agg_with_term_agg_status), + benchmark_config!(range_agg_with_term_agg_many), + benchmark_config!(avg_and_range_with_avg_sub_agg), + benchmark_config!(histogram), + benchmark_config!(histogram_hard_bounds), + benchmark_config!(histogram_with_avg_sub_agg), + benchmark_config!(histogram_with_term_agg_status), + benchmark_config!(composite_term_few), + benchmark_config!(composite_term_many_page_1000), + benchmark_config!(composite_term_many_page_1000_with_avg_sub_agg), + benchmark_config!(composite_histogram), + benchmark_config!(composite_histogram_calendar), + ], + )); + groups.push(( + "filter_agg", + execute_agg, + vec![ + benchmark_config!(filter_agg_all_query_count_agg), + benchmark_config!(filter_agg_term_query_count_agg), + benchmark_config!(filter_agg_all_query_with_sub_aggs), + benchmark_config!(filter_agg_term_query_with_sub_aggs), + ], + )); - register!(group, terms_many_json_mixed_type_with_avg_sub_agg); - - register!(group, composite_term_many_page_1000); - register!(group, composite_term_many_page_1000_with_avg_sub_agg); - register!(group, composite_term_few); - register!(group, composite_histogram); - register!(group, composite_histogram_calendar); - - // multi_terms aggregation benchmarks - register!( - group, - multi_terms_status_and_zipf_1000, - multi_terms_status_and_zipf_1000_filtered - ); - register!( - group, - multi_terms_status_and_zipf_1000_with_missing, - multi_terms_status_and_zipf_1000_with_missing_filtered - ); - register!( - group, - multi_terms_zipf_1000_and_status, - multi_terms_zipf_1000_and_status_filtered - ); - register!( - group, - multi_terms_many_and_zipf_1000, - multi_terms_many_and_zipf_1000_filtered - ); - register!( - group, - multi_terms_many_and_zipf_1000_and_status, - multi_terms_many_and_zipf_1000_and_status_filtered - ); - register!( - group, - multi_terms_status_and_zipf_1000_avg_sub_agg, - multi_terms_status_and_zipf_1000_avg_sub_agg_filtered - ); - - register!(group, cardinality_agg); - register!(group, cardinality_agg_high_card); - register!(group, cardinality_agg_low_card); - register!(group, terms_status_with_cardinality_agg); - register!(group, terms_100_buckets_with_cardinality_agg); - register!(group, terms_many_with_single_term_order_by_card); - register!(group, terms_many_with_single_term_2_order_by_card); - - register!(group, range_agg); - register!(group, range_agg_with_avg_sub_agg); - register!(group, range_agg_with_term_agg_status); - register!(group, range_agg_with_term_agg_many); - register!(group, histogram); - register!(group, histogram_hard_bounds); - register!(group, histogram_with_avg_sub_agg); - register!(group, histogram_with_term_agg_status); - register!(group, avg_and_range_with_avg_sub_agg); - - // Filter aggregation benchmarks - register!(group, filter_agg_all_query_count_agg); - register!(group, filter_agg_term_query_count_agg); - register!(group, filter_agg_all_query_with_sub_aggs); - register!(group, filter_agg_term_query_with_sub_aggs); - - group.run(); + for (group_name, execute, configs) in groups { + let mut group = runner.new_group(); + group.set_name(group_name); + for (benchmark_name, agg_req) in configs { + group.register_with_input(benchmark_name, index, move |index| { + execute(index, agg_req.clone()) + }); + } + group.run(); + } } -fn exec_term_with_agg(index: &Index, agg_req: serde_json::Value) { - let agg_req: Aggregations = serde_json::from_value(agg_req).unwrap(); - - let reader = index.reader().unwrap(); - let text_field = reader.searcher().schema().get_field("text").unwrap(); - let term_query = TermQuery::new( - Term::from_field_text(text_field, "cool"), - IndexRecordOption::Basic, - ); - let collector = get_collector(agg_req); - let searcher = reader.searcher(); - black_box(searcher.search(&term_query, &collector).unwrap()); -} - -fn average_u64(index: &Index) { - let agg_req = json!({ +fn average_u64() -> AggregationRequest { + json!({ "average": { "avg": { "field": "score", } } - }); - exec_term_with_agg(index, agg_req) + }) } -fn average_f64(index: &Index) { - let agg_req = json!({ +fn average_f64() -> AggregationRequest { + json!({ "average": { "avg": { "field": "score_f64", } } - }); - exec_term_with_agg(index, agg_req) + }) } -fn average_f64_u64(index: &Index) { - let agg_req = json!({ +fn average_f64_u64() -> AggregationRequest { + json!({ "average_f64": { "avg": { "field": "score_f64" } }, "average": { "avg": { "field": "score" } }, - }); - exec_term_with_agg(index, agg_req) + }) } -fn stats_f64(index: &Index) { - let agg_req = json!({ +fn stats_f64() -> AggregationRequest { + json!({ "average_f64": { "stats": { "field": "score_f64", } } - }); - exec_term_with_agg(index, agg_req) + }) } -fn extendedstats_f64(index: &Index) { - let agg_req = json!({ +fn extendedstats_f64() -> AggregationRequest { + json!({ "extendedstats_f64": { "extended_stats": { "field": "score_f64", } } - }); - exec_term_with_agg(index, agg_req) + }) } -fn percentiles_f64(index: &Index) { - let agg_req = json!({ +fn percentiles_f64() -> AggregationRequest { + json!({ "mypercentiles": { "percentiles": { "field": "score_f64", "percents": [ 95, 99, 99.9 ] } } - }); - execute_agg(index, agg_req); + }) } -fn cardinality_agg(index: &Index) { - let agg_req = json!({ +fn cardinality_agg() -> AggregationRequest { + json!({ "cardinality": { "cardinality": { "field": "text_many_terms" }, } - }); - execute_agg(index, agg_req); + }) } // Full-scan cardinality on a near-1M-cardinality string field. // Hits the dense (PagedBitset) path: every doc has a unique term, // so the bucket promotes from FxHashSet shortly into the scan. -fn cardinality_agg_high_card(index: &Index) { - let agg_req = json!({ +fn cardinality_agg_high_card() -> AggregationRequest { + json!({ "cardinality": { "cardinality": { "field": "text_all_unique_terms" }, } - }); - execute_agg(index, agg_req); + }) } // Full-scan cardinality on a tiny-cardinality string field (7 distinct // values). Stays on the FxHashSet path — the promotion threshold is // never crossed. Validates no regression on the sparse path. -fn cardinality_agg_low_card(index: &Index) { - let agg_req = json!({ +fn cardinality_agg_low_card() -> AggregationRequest { + json!({ "cardinality": { "cardinality": { "field": "text_few_terms_status" }, } - }); - execute_agg(index, agg_req); + }) } -fn terms_status_with_cardinality_agg(index: &Index) { - let agg_req = json!({ +fn terms_status_with_cardinality_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { @@ -274,12 +251,11 @@ fn terms_status_with_cardinality_agg(index: &Index) { } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_100_buckets_with_cardinality_agg(index: &Index) { - let agg_req = json!({ +fn terms_100_buckets_with_cardinality_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_1000_terms_zipf", "size": 100 }, "aggs": { @@ -290,12 +266,11 @@ fn terms_100_buckets_with_cardinality_agg(index: &Index) { } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_many_with_single_term_order_by_card(index: &Index) { - let agg_req = json!({ +fn terms_many_with_single_term_order_by_card() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms" }, "aggs": { @@ -312,15 +287,14 @@ fn terms_many_with_single_term_order_by_card(index: &Index) { } } }, - }); - execute_agg(index, agg_req); + }) } // Two-level terms ordered by cardinality at each level: a high-card outer terms // (text_many_terms) ordered by a cardinality sub-agg, with a nested low-card terms // (text_few_terms_status) also ordered by a cardinality sub-agg, plus an avg. -fn terms_many_with_single_term_2_order_by_card(index: &Index) { - let agg_req = json!({ +fn terms_many_with_single_term_2_order_by_card() -> AggregationRequest { + json!({ "by_ip": { "terms": { "field": "text_many_terms", @@ -342,50 +316,43 @@ fn terms_many_with_single_term_2_order_by_card(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) } -fn terms_7(index: &Index) { - let agg_req = json!({ +fn terms_7() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_all_unique(index: &Index) { - let agg_req = json!({ +fn terms_all_unique() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_all_unique_terms" } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_all_unique_order_by_key(index: &Index) { - let agg_req = json!({ +fn terms_all_unique_order_by_key() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_all_unique_terms", "order": { "_key": "asc" } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_150_000(index: &Index) { - let agg_req = json!({ +fn terms_150_000() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms" } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_many_top_1000(index: &Index) { - let agg_req = json!({ +fn terms_many_top_1000() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms", "size": 1000 } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_many_order_by_term(index: &Index) { - let agg_req = json!({ +fn terms_many_order_by_term() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms", "order": { "_key": "desc" } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_many_with_top_hits(index: &Index) { - let agg_req = json!({ +fn terms_many_with_top_hits() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms" }, "aggs": { @@ -400,45 +367,41 @@ fn terms_many_with_top_hits(index: &Index) { } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_many_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn terms_many_with_avg_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms" }, "aggs": { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_all_unique_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn terms_all_unique_with_avg_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_all_unique_terms" }, "aggs": { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn nested_terms_status_and_zipf_1000(index: &Index) { - let agg_req = json!({ +fn nested_terms_status_and_zipf_1000() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { "nested_terms": { "terms": { "field": "text_1000_terms_zipf" } } } } - }); - execute_agg(index, agg_req); + }) } -fn nested_terms_status_and_zipf_1000_with_missing(index: &Index) { - let agg_req = json!({ +fn nested_terms_status_and_zipf_1000_with_missing() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status", @@ -453,36 +416,33 @@ fn nested_terms_status_and_zipf_1000_with_missing(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) } -fn nested_terms_zipf_1000_and_status(index: &Index) { - let agg_req = json!({ +fn nested_terms_zipf_1000_and_status() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_1000_terms_zipf" }, "aggs": { "nested_terms": { "terms": { "field": "text_few_terms_status" } } } } - }); - execute_agg(index, agg_req); + }) } -fn nested_terms_many_and_zipf_1000(index: &Index) { - let agg_req = json!({ +fn nested_terms_many_and_zipf_1000() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms" }, "aggs": { "nested_terms": { "terms": { "field": "text_1000_terms_zipf" } } } } - }); - execute_agg(index, agg_req); + }) } -fn nested_terms_many_and_zipf_1000_and_status(index: &Index) { - let agg_req = json!({ +fn nested_terms_many_and_zipf_1000_and_status() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_many_terms" }, "aggs": { @@ -496,12 +456,11 @@ fn nested_terms_many_and_zipf_1000_and_status(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) } -fn nested_terms_status_and_zipf_1000_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn nested_terms_status_and_zipf_1000_avg_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { @@ -513,32 +472,29 @@ fn nested_terms_status_and_zipf_1000_avg_sub_agg(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) } -fn terms_status_with_histogram(index: &Index) { - let agg_req = json!({ +fn terms_status_with_histogram() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { "histo": {"histogram": { "field": "score_f64", "interval": 10 }} } } - }); - execute_agg(index, agg_req); + }) } -fn terms_status_with_date_histogram(index: &Index) { - let agg_req = json!({ +fn terms_status_with_date_histogram() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { "over_time": { "date_histogram": { "field": "timestamp", "fixed_interval": "1h" } } } } - }); - execute_agg(index, agg_req); + }) } /// Same fused terms × date_histogram, but with `hard_bounds`. The timestamps span 0..120h; the @@ -546,8 +502,8 @@ fn terms_status_with_date_histogram(index: &Index) { /// doc is in-bounds. This exercises the collector's hard-bounds path: `bounds.contains` runs per /// doc (the `all_docs_in_bounds` short-circuit is off) and the rare out-of-bounds doc takes the /// `term_counts` branch. -fn terms_status_with_date_histogram_hard_bounds(index: &Index) { - let agg_req = json!({ +fn terms_status_with_date_histogram_hard_bounds() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { @@ -560,15 +516,14 @@ fn terms_status_with_date_histogram_hard_bounds(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) } /// Same fused terms × date_histogram, but with a sibling terms aggregation next to it. The fused /// fast path should still trigger for `my_texts` (sibling aggregations are independent top-level /// aggregations, so they don't change its eligibility). -fn terms_status_with_date_histogram_and_sibling_terms(index: &Index) { - let agg_req = json!({ +fn terms_status_with_date_histogram_and_sibling_terms() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { @@ -576,88 +531,80 @@ fn terms_status_with_date_histogram_and_sibling_terms(index: &Index) { } }, "other_texts": { "terms": { "field": "text_few_terms" } } - }); - execute_agg(index, agg_req); + }) } -fn terms_zipf_1000_with_histogram(index: &Index) { - let agg_req = json!({ +fn terms_zipf_1000_with_histogram() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_1000_terms_zipf" }, "aggs": { "histo": {"histogram": { "field": "score_f64", "interval": 10 }} } } - }); - execute_agg(index, agg_req); + }) } -fn terms_status_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn terms_status_with_avg_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_few_terms_status" }, "aggs": { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_zipf_1000_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn terms_zipf_1000_with_avg_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_1000_terms_zipf" }, "aggs": { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_zipf_1000_only(index: &Index) { - let agg_req = json!({ +fn terms_zipf_1000() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_1000_terms_zipf" } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_zipf_90(index: &Index) { - let agg_req = json!({ +fn terms_zipf_90() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_90_terms_zipf", "size": 100 } }, - }); - execute_agg(index, agg_req); + }) } // 90-term (low-cardinality Vec path) terms agg with a metric sub-agg. The skewed distribution keeps // a dominant bucket crossing the sub-agg flush threshold, exercising the buffer flush path. -fn terms_zipf_90_with_sum_sub_agg(index: &Index) { - let agg_req = json!({ +fn terms_zipf_90_with_sum_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "text_90_terms_zipf", "size": 100 }, "aggs": { "sum_score": { "sum": { "field": "score" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn terms_many_json_mixed_type_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn terms_many_json_mixed_type_with_avg_sub_agg() -> AggregationRequest { + json!({ "my_texts": { "terms": { "field": "json.mixed_type" }, "aggs": { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn composite_term_few(index: &Index) { - let agg_req = json!({ +fn composite_term_few() -> AggregationRequest { + json!({ "my_ctf": { "composite": { "sources": [ @@ -666,11 +613,10 @@ fn composite_term_few(index: &Index) { "size": 1000 } }, - }); - execute_agg(index, agg_req); + }) } -fn composite_term_many_page_1000(index: &Index) { - let agg_req = json!({ +fn composite_term_many_page_1000() -> AggregationRequest { + json!({ "my_ctmp1000": { "composite": { "sources": [ @@ -679,11 +625,10 @@ fn composite_term_many_page_1000(index: &Index) { "size": 1000 } }, - }); - execute_agg(index, agg_req); + }) } -fn composite_term_many_page_1000_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn composite_term_many_page_1000_with_avg_sub_agg() -> AggregationRequest { + json!({ "my_ctmp1000wasa": { "composite": { "sources": [ @@ -695,11 +640,10 @@ fn composite_term_many_page_1000_with_avg_sub_agg(index: &Index) { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn composite_histogram(index: &Index) { - let agg_req = json!({ +fn composite_histogram() -> AggregationRequest { + json!({ "my_ch": { "composite": { "sources": [ @@ -708,11 +652,10 @@ fn composite_histogram(index: &Index) { "size": 1000 } }, - }); - execute_agg(index, agg_req); + }) } -fn composite_histogram_calendar(index: &Index) { - let agg_req = json!({ +fn composite_histogram_calendar() -> AggregationRequest { + json!({ "my_chc": { "composite": { "sources": [ @@ -721,15 +664,12 @@ fn composite_histogram_calendar(index: &Index) { "size": 1000 } }, - }); - execute_agg(index, agg_req); + }) } -define_multi_terms_benchmark!( - /// multi_terms equivalent of nested_terms_status_and_zipf_1000: - /// flat GroupBy(status, zipf_1000) vs nested terms(status) -> terms(zipf_1000) - multi_terms_status_and_zipf_1000, - multi_terms_status_and_zipf_1000_filtered, +/// multi_terms equivalent of nested_terms_status_and_zipf_1000: +/// flat GroupBy(status, zipf_1000) vs nested terms(status) -> terms(zipf_1000) +fn multi_terms_status_and_zipf_1000() -> AggregationRequest { json!({ "mt": { "multi_terms": { @@ -740,13 +680,11 @@ define_multi_terms_benchmark!( "size": 100 } } - }), -); + }) +} -define_multi_terms_benchmark!( - /// multi_terms equivalent of nested_terms_status_and_zipf_1000_with_missing. - multi_terms_status_and_zipf_1000_with_missing, - multi_terms_status_and_zipf_1000_with_missing_filtered, +/// multi_terms equivalent of nested_terms_status_and_zipf_1000_with_missing. +fn multi_terms_status_and_zipf_1000_with_missing() -> AggregationRequest { json!({ "mt": { "multi_terms": { @@ -757,14 +695,12 @@ define_multi_terms_benchmark!( "size": 100 } } - }), -); + }) +} -define_multi_terms_benchmark!( - /// multi_terms equivalent of nested_terms_zipf_1000_and_status: - /// flat GroupBy(zipf_1000, status) vs nested terms(zipf_1000) -> terms(status) - multi_terms_zipf_1000_and_status, - multi_terms_zipf_1000_and_status_filtered, +/// multi_terms equivalent of nested_terms_zipf_1000_and_status: +/// flat GroupBy(zipf_1000, status) vs nested terms(zipf_1000) -> terms(status) +fn multi_terms_zipf_1000_and_status() -> AggregationRequest { json!({ "mt": { "multi_terms": { @@ -775,14 +711,12 @@ define_multi_terms_benchmark!( "size": 100 } } - }), -); + }) +} -define_multi_terms_benchmark!( - /// multi_terms equivalent of nested_terms_many_and_zipf_1000: - /// flat GroupBy(many, zipf_1000) vs nested terms(many) -> terms(zipf_1000) - multi_terms_many_and_zipf_1000, - multi_terms_many_and_zipf_1000_filtered, +/// multi_terms equivalent of nested_terms_many_and_zipf_1000: +/// flat GroupBy(many, zipf_1000) vs nested terms(many) -> terms(zipf_1000) +fn multi_terms_many_and_zipf_1000() -> AggregationRequest { json!({ "mt": { "multi_terms": { @@ -793,14 +727,12 @@ define_multi_terms_benchmark!( "size": 100 } } - }), -); + }) +} -define_multi_terms_benchmark!( - /// multi_terms equivalent of nested_terms_many_and_zipf_1000_and_status: - /// flat GroupBy(many, zipf_1000, status) vs three nested terms levels - multi_terms_many_and_zipf_1000_and_status, - multi_terms_many_and_zipf_1000_and_status_filtered, +/// multi_terms equivalent of nested_terms_many_and_zipf_1000_and_status: +/// flat GroupBy(many, zipf_1000, status) vs three nested terms levels +fn multi_terms_many_and_zipf_1000_and_status() -> AggregationRequest { json!({ "mt": { "multi_terms": { @@ -812,13 +744,11 @@ define_multi_terms_benchmark!( "size": 100 } } - }), -); + }) +} -define_multi_terms_benchmark!( - /// multi_terms equivalent of nested_terms_status_and_zipf_1000_avg_sub_agg. - multi_terms_status_and_zipf_1000_avg_sub_agg, - multi_terms_status_and_zipf_1000_avg_sub_agg_filtered, +/// multi_terms equivalent of nested_terms_status_and_zipf_1000_avg_sub_agg. +fn multi_terms_status_and_zipf_1000_avg_sub_agg() -> AggregationRequest { json!({ "mt": { "multi_terms": { @@ -831,14 +761,14 @@ define_multi_terms_benchmark!( "average_f64": { "avg": { "field": "score_f64" } } } } - }), -); + }) +} -fn execute_agg(index: &Index, agg_req: serde_json::Value) { +fn execute_agg(index: &Index, agg_req: AggregationRequest) { execute_agg_with_query(index, agg_req, &AllQuery); } -fn execute_agg_filtered(index: &Index, agg_req: serde_json::Value) { +fn execute_agg_filtered(index: &Index, agg_req: AggregationRequest) { let filter_field = index.schema().get_field("filter_field").unwrap(); let filter_query = TermQuery::new( Term::from_field_text(filter_field, "a"), @@ -847,7 +777,16 @@ fn execute_agg_filtered(index: &Index, agg_req: serde_json::Value) { execute_agg_with_query(index, agg_req, &filter_query); } -fn execute_agg_with_query(index: &Index, agg_req: serde_json::Value, query: &dyn Query) { +fn execute_agg_filtered_on_single_term(index: &Index, agg_req: AggregationRequest) { + let filter_field = index.schema().get_field("single_term").unwrap(); + let filter_query = TermQuery::new( + Term::from_field_text(filter_field, "single_term"), + IndexRecordOption::Basic, + ); + execute_agg_with_query(index, agg_req, &filter_query); +} + +fn execute_agg_with_query(index: &Index, agg_req: AggregationRequest, query: &dyn Query) { let agg_req: Aggregations = serde_json::from_value(agg_req).unwrap(); let collector = get_collector(agg_req); @@ -856,8 +795,8 @@ fn execute_agg_with_query(index: &Index, agg_req: serde_json::Value, query: &dyn black_box(searcher.search(query, &collector).unwrap()); } -fn range_agg(index: &Index) { - let agg_req = json!({ +fn range_agg() -> AggregationRequest { + json!({ "range_f64": { "range": { "field": "score_f64", "ranges": [ { "from": 3, "to": 7000 }, { "from": 7000, "to": 20000 }, @@ -866,11 +805,10 @@ fn range_agg(index: &Index) { { "from": 40000, "to": 50000 }, { "from": 50000, "to": 60000 } ] } }, - }); - execute_agg(index, agg_req); + }) } -fn range_agg_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn range_agg_with_avg_sub_agg() -> AggregationRequest { + json!({ "rangef64": { "range": { "field": "score_f64", @@ -887,12 +825,11 @@ fn range_agg_with_avg_sub_agg(index: &Index) { "average_f64": { "avg": { "field": "score_f64" } } } }, - }); - execute_agg(index, agg_req); + }) } -fn range_agg_with_term_agg_status(index: &Index) { - let agg_req = json!({ +fn range_agg_with_term_agg_status() -> AggregationRequest { + json!({ "rangef64": { "range": { "field": "score_f64", @@ -909,11 +846,10 @@ fn range_agg_with_term_agg_status(index: &Index) { "my_texts": { "terms": { "field": "text_few_terms_status" } }, } }, - }); - execute_agg(index, agg_req); + }) } -fn range_agg_with_term_agg_many(index: &Index) { - let agg_req = json!({ +fn range_agg_with_term_agg_many() -> AggregationRequest { + json!({ "rangef64": { "range": { "field": "score_f64", @@ -930,51 +866,46 @@ fn range_agg_with_term_agg_many(index: &Index) { "my_texts": { "terms": { "field": "text_many_terms" } }, } }, - }); - execute_agg(index, agg_req); + }) } -fn histogram(index: &Index) { - let agg_req = json!({ +fn histogram() -> AggregationRequest { + json!({ "rangef64": { "histogram": { "field": "score_f64", "interval": 100 // 1000 buckets }, } - }); - execute_agg(index, agg_req); + }) } -fn histogram_hard_bounds(index: &Index) { - let agg_req = json!({ +fn histogram_hard_bounds() -> AggregationRequest { + json!({ "rangef64": { "histogram": { "field": "score_f64", "interval": 100, "hard_bounds": { "min": 1000, "max": 300000 } } }, - }); - execute_agg(index, agg_req); + }) } -fn histogram_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn histogram_with_avg_sub_agg() -> AggregationRequest { + json!({ "rangef64": { "histogram": { "field": "score_f64", "interval": 100 }, "aggs": { "average_f64": { "avg": { "field": "score_f64" } } } } - }); - execute_agg(index, agg_req); + }) } -fn histogram_with_term_agg_status(index: &Index) { - let agg_req = json!({ +fn histogram_with_term_agg_status() -> AggregationRequest { + json!({ "rangef64": { "histogram": { "field": "score_f64", "interval": 10 }, "aggs": { "my_texts": { "terms": { "field": "text_few_terms_status" } } } } - }); - execute_agg(index, agg_req); + }) } -fn avg_and_range_with_avg_sub_agg(index: &Index) { - let agg_req = json!({ +fn avg_and_range_with_avg_sub_agg() -> AggregationRequest { + json!({ "rangef64": { "range": { "field": "score_f64", @@ -989,8 +920,7 @@ fn avg_and_range_with_avg_sub_agg(index: &Index) { } }, "average": { "avg": { "field": "score" } } - }); - execute_agg(index, agg_req); + }) } #[derive(Clone, Copy, Hash, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -1018,9 +948,6 @@ fn get_test_index_bench(cardinality: Cardinality) -> tantivy::Result { .map(|v| v == "true") .unwrap_or(true); let index_dir = format!("agg_bench/{cardinality:?}"); - if reuse_index && std::path::Path::new(&index_dir).try_exists()? { - return Index::open_in_dir(&index_dir); - } let mut schema_builder = Schema::builder(); let text_fieldtype = tantivy::schema::TextOptions::default() .set_indexing_options( @@ -1029,7 +956,7 @@ fn get_test_index_bench(cardinality: Cardinality) -> tantivy::Result { .set_stored(); let text_field = schema_builder.add_text_field("text", text_fieldtype.clone()); let filter_field = schema_builder.add_text_field("filter_field", STRING); - let single_term = schema_builder.add_text_field("single_term", FAST); + let single_term = schema_builder.add_text_field("single_term", STRING | FAST); let json_field = schema_builder.add_json_field("json", FAST); let text_field_all_unique_terms = schema_builder.add_text_field("text_all_unique_terms", STRING | FAST); @@ -1046,12 +973,23 @@ fn get_test_index_bench(cardinality: Cardinality) -> tantivy::Result { let score_field_f64 = schema_builder.add_f64_field("score_f64", score_fieldtype.clone()); let score_field_i64 = schema_builder.add_i64_field("score_i64", score_fieldtype); let date_field = schema_builder.add_date_field("timestamp", FAST); + let schema = schema_builder.build(); + + if reuse_index && std::path::Path::new(&index_dir).try_exists()? { + let index = Index::open_in_dir(&index_dir)?; + if index.schema() == schema { + return Ok(index); + } + drop(index); + std::fs::remove_dir_all(&index_dir)?; + } + // use tmp dir let index = if reuse_index { std::fs::create_dir_all(&index_dir)?; - Index::create_in_dir(&index_dir, schema_builder.build())? + Index::create_in_dir(&index_dir, schema)? } else { - Index::create_from_tempdir(schema_builder.build())? + Index::create_from_tempdir(schema)? }; // Approximate log proportions let status_field_data = [ @@ -1191,32 +1129,30 @@ fn get_test_index_bench(cardinality: Cardinality) -> tantivy::Result { // Filter aggregation benchmarks -fn filter_agg_all_query_count_agg(index: &Index) { - let agg_req = json!({ +fn filter_agg_all_query_count_agg() -> AggregationRequest { + json!({ "filtered": { "filter": "*", "aggs": { "count": { "value_count": { "field": "score" } } } } - }); - execute_agg(index, agg_req); + }) } -fn filter_agg_term_query_count_agg(index: &Index) { - let agg_req = json!({ +fn filter_agg_term_query_count_agg() -> AggregationRequest { + json!({ "filtered": { "filter": "text:cool", "aggs": { "count": { "value_count": { "field": "score" } } } } - }); - execute_agg(index, agg_req); + }) } -fn filter_agg_all_query_with_sub_aggs(index: &Index) { - let agg_req = json!({ +fn filter_agg_all_query_with_sub_aggs() -> AggregationRequest { + json!({ "filtered": { "filter": "*", "aggs": { @@ -1227,12 +1163,11 @@ fn filter_agg_all_query_with_sub_aggs(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) } -fn filter_agg_term_query_with_sub_aggs(index: &Index) { - let agg_req = json!({ +fn filter_agg_term_query_with_sub_aggs() -> AggregationRequest { + json!({ "filtered": { "filter": "text:cool", "aggs": { @@ -1243,6 +1178,5 @@ fn filter_agg_term_query_with_sub_aggs(index: &Index) { } } } - }); - execute_agg(index, agg_req); + }) }