Skip to content

Commit 277e597

Browse files
committed
Extend Vec term storage to 20k with eager/lazy bucket ids
Raise MAX_NUM_TERMS_FOR_VEC to 20_000 so the dense Vec term storage (direct-indexed, no hashing/paging) is used for low/moderate cardinality both with and without sub-aggregations, not just the <100 low-card case. Split the old single threshold into MAX_NUM_TERMS_FOR_LOWCARD_SUBAGG (100, still gates the Vec + LowCard-buffer pairing) and MAX_NUM_TERMS_FOR_VEC. VecTermBuckets is now generic over a compile-time `const LAZY: bool`: - eager (default): assigns all ids up front in `new`, branchless term_entry - lazy: assigns on first occurrence, keeping the sub-agg bucket range down to the terms that actually occur (no blowup for sparse ordinal spaces) A new BucketIdSlot::ASSIGNS_ID const plus the LAZY const fully gate the first-seen branch out at monomorphization, so the only branchy path is the deliberately-lazy <BucketId, true> instantiation. The with-sub-agg Vec path picks eager below MAX_NUM_TERMS_FOR_EAGER_BUCKET_IDS (4096) and lazy above, avoiding the ~6-11% hot-loop regression eager fixes while keeping large sparse cases compact. Biggest impact on terms_zipf_1000_only with -40% ``` full terms_7 Memory: 46.5 KB Avg: 2.3566ms (+4.29%) Median: 2.3566ms (+4.29%) [2.3566ms .. 2.3566ms] terms_all_unique Memory: 11.5 MB Avg: 4.8532ms (-5.74%) Median: 4.8532ms (-5.74%) [4.8532ms .. 4.8532ms] terms_all_unique_order_by_key Memory: 11.5 MB Avg: 4.8448ms (-0.53%) Median: 4.8448ms (-0.53%) [4.8448ms .. 4.8448ms] terms_150_000 Memory: 2.7 MB Avg: 5.5295ms (+0.64%) Median: 5.5295ms (+0.64%) [5.5295ms .. 5.5295ms] terms_many_top_1000 Memory: 5.3 MB Avg: 8.4738ms (+0.58%) Median: 8.4738ms (+0.58%) [8.4738ms .. 8.4738ms] terms_many_order_by_term Memory: 2.7 MB (-0.01%) Avg: 4.5959ms (-0.15%) Median: 4.5959ms (-0.15%) [4.5959ms .. 4.5959ms] terms_many_with_top_hits Memory: 48.9 MB (-0.00%) Avg: 85.8206ms (-5.46%) Median: 85.8206ms (-5.46%) [85.8206ms .. 85.8206ms] terms_all_unique_with_avg_sub_agg Memory: 54.7 MB (-0.00%) Avg: 17.8747ms (+8.16%) Median: 17.8747ms (+8.16%) [17.8747ms .. 17.8747ms] terms_many_with_avg_sub_agg Memory: 13.5 MB (-0.00%) Avg: 14.7797ms (+2.54%) Median: 14.7797ms (+2.54%) [14.7797ms .. 14.7797ms] terms_status_with_avg_sub_agg Memory: 92.1 KB (-0.35%) Avg: 5.1467ms (+0.72%) Median: 5.1467ms (+0.72%) [5.1467ms .. 5.1467ms] terms_status_with_terms_zipf_1000_sub_agg Memory: 213.2 KB Avg: 3.9576ms (-2.73%) Median: 3.9576ms (-2.73%) [3.9576ms .. 3.9576ms] terms_zipf_1000_with_terms_status_sub_agg Memory: 726.5 KB (+0.28%) Avg: 12.1432ms (+3.30%) Median: 12.1432ms (+3.30%) [12.1432ms .. 12.1432ms] terms_status_with_histogram Memory: 139.0 KB Avg: 2.4717ms (+2.45%) Median: 2.4717ms (+2.45%) [2.4717ms .. 2.4717ms] terms_status_with_date_histogram Memory: 145.9 KB Avg: 2.3644ms (-0.44%) Median: 2.3644ms (-0.44%) [2.3644ms .. 2.3644ms] terms_status_with_date_histogram_hard_bounds Memory: 145.4 KB Avg: 2.5705ms (+4.70%) Median: 2.5705ms (+4.70%) [2.5705ms .. 2.5705ms] terms_status_with_date_histogram_and_sibling_terms Memory: 143.3 KB Avg: 3.8946ms (+1.61%) Median: 3.8946ms (+1.61%) [3.8946ms .. 3.8946ms] terms_zipf_1000_only Memory: 75.8 KB (-0.02%) Avg: 1.2684ms (-41.50%) Median: 1.2684ms (-41.50%) [1.2684ms .. 1.2684ms] terms_zipf_1000_with_histogram Memory: 1.2 MB (+0.17%) Avg: 20.9323ms (+4.08%) Median: 20.9323ms (+4.08%) [20.9323ms .. 20.9323ms] terms_zipf_1000_with_avg_sub_agg Memory: 486.7 KB (+2.95%) Avg: 8.5297ms (-1.66%) Median: 8.5297ms (-1.66%) [8.5297ms .. 8.5297ms] terms_many_json_mixed_type_with_avg_sub_agg Memory: 17.9 MB Avg: 27.0210ms (+1.81%) Median: 27.0210ms (+1.81%) [27.0210ms .. 27.0210ms] terms_status_with_cardinality_agg Memory: 93.9 KB Avg: 3.2022ms (-0.64%) Median: 3.2022ms (-0.64%) [3.2022ms .. 3.2022ms] terms_100_buckets_with_cardinality_agg Memory: 9.8 MB (+0.31%) Avg: 49.3188ms (-1.70%) Median: 49.3188ms (-1.70%) [49.3188ms .. 49.3188ms] terms_many_with_single_term_order_by_card Memory: 48.9 MB Avg: 77.2127ms (-5.02%) Median: 77.2127ms (-5.02%) [77.2127ms .. 77.2127ms] terms_many_with_single_term_2_order_by_card Memory: 40.6 MB (-0.02%) Avg: 45.8004ms (-17.57%) Median: 45.8004ms (-17.57%) [45.8004ms .. 45.8004ms] ```
1 parent fc84c0f commit 277e597

3 files changed

Lines changed: 197 additions & 50 deletions

File tree

src/aggregation/bucket/histogram/histogram.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ impl HistogramBounds {
256256
/// the zero-sized `()` when it does not. Without sub aggregations the id is never read, so storing
257257
/// `()` drops the id bytes per bucket and turns id assignment into a no-op.
258258
pub trait BucketIdSlot: Copy + Default + std::fmt::Debug + PartialEq + 'static {
259+
/// Whether this slot carries a real id, i.e. `assign` produces a meaningful value. `false` for
260+
/// the `()` slot, letting callers gate id-assignment code out at compile time (e.g. skip the
261+
/// lazy-assign branch in a hot loop entirely rather than relying on the optimizer).
262+
const ASSIGNS_ID: bool;
259263
/// Assigns the next id from the provider, called once when a bucket is first filled.
260264
fn assign(provider: &mut BucketIdProvider) -> Self;
261265
/// Resolves to the `BucketId` for sub-aggregation bookkeeping.
@@ -265,6 +269,7 @@ pub trait BucketIdSlot: Copy + Default + std::fmt::Debug + PartialEq + 'static {
265269
fn to_bucket_id(self) -> BucketId;
266270
}
267271
impl BucketIdSlot for BucketId {
272+
const ASSIGNS_ID: bool = true;
268273
#[inline(always)]
269274
fn assign(provider: &mut BucketIdProvider) -> Self {
270275
provider.next_bucket_id()
@@ -275,6 +280,7 @@ impl BucketIdSlot for BucketId {
275280
}
276281
}
277282
impl BucketIdSlot for () {
283+
const ASSIGNS_ID: bool = false;
278284
#[inline(always)]
279285
fn assign(_provider: &mut BucketIdProvider) -> Self {}
280286
#[inline(always)]

src/aggregation/bucket/term_agg/mod.rs

Lines changed: 189 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -335,15 +335,47 @@ impl TermsAggregationInternal {
335335
}
336336
}
337337

338-
/// The treshold for maximum number of terms to use a Vec-backed bucket storage.
338+
/// Threshold for using dense `Vec` storage ([`VecTermBuckets`]) for term buckets. Below this many
339+
/// term ordinals the `Vec` (direct-indexed, no hashing/paging) beats the paged/hashed storages.
340+
/// It preallocates `num_terms` slots, so the ceiling also bounds its memory
341+
/// (`num_terms * size_of::<Bucket>`, ≤ ~160KB here).
342+
///
339343
/// TODO: Benchmark to validate the threshold
340-
pub const MAX_NUM_TERMS_FOR_VEC: u64 = 100;
344+
pub const MAX_NUM_TERMS_FOR_VEC: u64 = 20_000;
345+
346+
/// Threshold below which a terms agg *with sub-aggregations* pairs the `Vec` storage with the
347+
/// [`LowCardSubAggBuffer`] (which buffers docs in a per-bucket `Vec`). That layout only pays off
348+
/// for a handful of buckets, so it is far lower than [`MAX_NUM_TERMS_FOR_VEC`]; between the two
349+
/// thresholds the `Vec` storage pairs with the partitioned `HighCard` buffer instead.
350+
pub const MAX_NUM_TERMS_FOR_LOWCARD_SUBAGG: u64 = 100;
351+
352+
/// Above this term count, generate sub-aggregation bucket IDs on first use to avoid IDs for unseen
353+
/// terms.
354+
///
355+
/// TODO: Benchmark this threshold.
356+
const LAZY_BUCKET_ID_GENERATION_THRESHOLD: u64 = 4_096;
341357

342358
/// Average docs-per-bucket below which term counts cluster too tightly (mostly 1s and 2s) for
343359
/// `select_nth_unstable` to beat `sort_unstable`'s adaptive paths, so we fall back to a full sort.
344360
/// This is very low on purpose, and meant to catch unique or mostly unique terms.
345361
const DOCS_PER_BUCKET_QUICKSELECT_THRESHOLD: u64 = 2;
346362

363+
/// Charge a `Vec`-backed term storage's eager, up-front allocation against the memory limit.
364+
///
365+
/// The paged/hashed storages grow lazily during `collect` and are charged there via deltas, but
366+
/// [`VecTermBuckets`] preallocates all `num_terms` slots at construction, so its memory would
367+
/// otherwise escape the limit entirely (relevant now that the `Vec` threshold reaches
368+
/// [`MAX_NUM_TERMS_FOR_VEC`]).
369+
fn add_memory_consumption<M: TermAggregationMap>(
370+
term_buckets: &M,
371+
req_data: &mut AggregationsSegmentCtx,
372+
) -> crate::Result<()> {
373+
req_data
374+
.context
375+
.limits
376+
.add_memory_consumed(term_buckets.get_memory_consumption() as u64)
377+
}
378+
347379
/// Build a concrete `SegmentTermCollector` with either a Vec- or HashMap-backed
348380
/// bucket storage, depending on the column type and aggregation level.
349381
pub(crate) fn build_segment_term_collector(
@@ -413,39 +445,37 @@ pub(crate) fn build_segment_term_collector(
413445
// `num_terms`. `saturating_add` guards the HashMap fallback, where `max_column_val` is a raw
414446
// numeric column value that can reach `u64::MAX`; term ordinals never come close.
415447
let num_terms = max_column_val.saturating_add(1);
448+
449+
// Very low cardinality *with* sub aggregations: dense `Vec` storage paired with the `LowCard`
450+
// buffer, which groups docs in a per-bucket `Vec`. That layout only fits a handful of buckets
451+
// (docs arrive in doc order, so the partitioned `HighCard` buffer would merely merge
452+
// consecutive same-bucket docs), so it is gated far below the general `Vec` threshold.
453+
if is_top_level && has_sub_aggregations && max_column_val < MAX_NUM_TERMS_FOR_LOWCARD_SUBAGG {
454+
let term_buckets = VecTermBuckets::<BucketId>::new(num_terms, &mut bucket_id_provider);
455+
add_memory_consumption(&term_buckets, req_data)?;
456+
let collector: SegmentTermCollector<_, LowCardSubAggBuffer> = SegmentTermCollector {
457+
parent_buckets: vec![term_buckets],
458+
sub_agg: sub_agg_collector.map(LowCardBufferedSubAggs::new),
459+
bucket_id_provider,
460+
max_term_id: max_column_val,
461+
terms_req_data,
462+
};
463+
return Ok(Box::new(collector));
464+
}
465+
466+
// Everything below uses the partitioned `HighCard` sub-agg buffer, so it is built once here.
467+
let sub_agg = sub_agg_collector.map(BufferedSubAggs::new);
416468
if is_top_level && max_column_val < MAX_NUM_TERMS_FOR_VEC {
417-
// Low cardinality: dense `Vec` storage. With sub aggregations it pairs with the `LowCard`
418-
// buffer, which groups docs in a per-bucket `Vec` — a better fit for the few buckets here
419-
// than the partitioned `HighCard` buffer the branches below use (docs arrive in doc order,
420-
// so `HighCard` would only merge consecutive same-bucket docs).
469+
// Low/moderate cardinality: dense `Vec` storage, direct-indexed by term ordinal — no
470+
// hashing or page indirection. Used both with and without sub aggregations.
421471
if has_sub_aggregations {
422-
let term_buckets = VecTermBuckets::<BucketId>::new(num_terms, &mut bucket_id_provider);
423-
let collector: SegmentTermCollector<_, LowCardSubAggBuffer> = SegmentTermCollector {
424-
parent_buckets: vec![term_buckets],
425-
sub_agg: sub_agg_collector.map(LowCardBufferedSubAggs::new),
426-
bucket_id_provider,
427-
max_term_id: max_column_val,
428-
terms_req_data,
429-
};
430-
Ok(Box::new(collector))
431-
} else {
432-
let term_buckets = VecTermBuckets::<()>::new(num_terms, &mut bucket_id_provider);
433-
Ok(boxed_high_card_collector(
434-
term_buckets,
435-
None,
436-
bucket_id_provider,
437-
max_column_val,
438-
terms_req_data,
439-
))
440-
}
441-
} else {
442-
// Higher cardinality: every remaining storage uses the partitioned `HighCard` sub-agg
443-
// buffer, so it is built once here.
444-
let sub_agg = sub_agg_collector.map(BufferedSubAggs::new);
445-
if max_column_val < 8_000_000 && is_top_level {
446-
if has_sub_aggregations {
472+
// Eager id assignment (branchless `term_entry`) while the bucket count is small;
473+
// switch to lazy above the threshold to keep the sub-agg bucket range compact for
474+
// large/sparse ordinal spaces. See `LAZY_BUCKET_ID_GENERATION_THRESHOLD`.
475+
if num_terms > LAZY_BUCKET_ID_GENERATION_THRESHOLD {
447476
let term_buckets =
448-
PagedTermMap::<BucketId>::new(num_terms, &mut bucket_id_provider);
477+
VecTermBuckets::<BucketId, true>::new(num_terms, &mut bucket_id_provider);
478+
add_memory_consumption(&term_buckets, req_data)?;
449479
Ok(boxed_high_card_collector(
450480
term_buckets,
451481
sub_agg,
@@ -454,7 +484,9 @@ pub(crate) fn build_segment_term_collector(
454484
terms_req_data,
455485
))
456486
} else {
457-
let term_buckets = PagedTermMap::<()>::new(num_terms, &mut bucket_id_provider);
487+
let term_buckets =
488+
VecTermBuckets::<BucketId, false>::new(num_terms, &mut bucket_id_provider);
489+
add_memory_consumption(&term_buckets, req_data)?;
458490
Ok(boxed_high_card_collector(
459491
term_buckets,
460492
sub_agg,
@@ -463,8 +495,22 @@ pub(crate) fn build_segment_term_collector(
463495
terms_req_data,
464496
))
465497
}
466-
} else if has_sub_aggregations {
467-
let term_buckets = HashMapTermBuckets::<BucketId>::default();
498+
} else {
499+
// No sub aggregations: the `()` slot carries no id, so eager vs lazy is moot and
500+
// `term_entry` is branchless either way.
501+
let term_buckets = VecTermBuckets::<()>::new(num_terms, &mut bucket_id_provider);
502+
add_memory_consumption(&term_buckets, req_data)?;
503+
Ok(boxed_high_card_collector(
504+
term_buckets,
505+
sub_agg,
506+
bucket_id_provider,
507+
max_column_val,
508+
terms_req_data,
509+
))
510+
}
511+
} else if is_top_level && max_column_val < 8_000_000 {
512+
if has_sub_aggregations {
513+
let term_buckets = PagedTermMap::<BucketId>::new(num_terms, &mut bucket_id_provider);
468514
Ok(boxed_high_card_collector(
469515
term_buckets,
470516
sub_agg,
@@ -473,7 +519,7 @@ pub(crate) fn build_segment_term_collector(
473519
terms_req_data,
474520
))
475521
} else {
476-
let term_buckets = HashMapTermBuckets::<()>::default();
522+
let term_buckets = PagedTermMap::<()>::new(num_terms, &mut bucket_id_provider);
477523
Ok(boxed_high_card_collector(
478524
term_buckets,
479525
sub_agg,
@@ -482,6 +528,24 @@ pub(crate) fn build_segment_term_collector(
482528
terms_req_data,
483529
))
484530
}
531+
} else if has_sub_aggregations {
532+
let term_buckets = HashMapTermBuckets::<BucketId>::default();
533+
Ok(boxed_high_card_collector(
534+
term_buckets,
535+
sub_agg,
536+
bucket_id_provider,
537+
max_column_val,
538+
terms_req_data,
539+
))
540+
} else {
541+
let term_buckets = HashMapTermBuckets::<()>::default();
542+
Ok(boxed_high_card_collector(
543+
term_buckets,
544+
sub_agg,
545+
bucket_id_provider,
546+
max_column_val,
547+
terms_req_data,
548+
))
485549
}
486550
}
487551

@@ -731,13 +795,17 @@ impl<B: BucketIdSlot> TermAggregationMap for HashMapTermBuckets<B> {
731795
}
732796
}
733797

734-
/// An optimized term map implementation for a compact set of term ordinals.
798+
/// A term map backed by a `Vec`, indexed directly by term ordinal.
799+
///
800+
/// `LAZY_BUCKET_ID_GENERATION` defers sub-aggregation bucket ID generation until first use.
735801
#[derive(Clone, Debug)]
736-
struct VecTermBuckets<B> {
802+
struct VecTermBuckets<B, const LAZY_BUCKET_ID_GENERATION: bool = false> {
737803
buckets: Vec<Bucket<B>>,
738804
}
739805

740-
impl<B: BucketIdSlot> TermAggregationMap for VecTermBuckets<B> {
806+
impl<B: BucketIdSlot, const LAZY_BUCKET_ID_GENERATION: bool> TermAggregationMap
807+
for VecTermBuckets<B, LAZY_BUCKET_ID_GENERATION>
808+
{
741809
type Slot = B;
742810

743811
const SORTED_BY_ORD: bool = true;
@@ -753,7 +821,7 @@ impl<B: BucketIdSlot> TermAggregationMap for VecTermBuckets<B> {
753821

754822
/// Add an occurrence of the given term id.
755823
#[inline(always)]
756-
fn term_entry(&mut self, term_id: u64, _bucket_id_provider: &mut BucketIdProvider) -> B {
824+
fn term_entry(&mut self, term_id: u64, bucket_id_provider: &mut BucketIdProvider) -> B {
757825
let term_id_usize = term_id as usize;
758826
debug_assert!(
759827
term_id_usize < self.buckets.len(),
@@ -762,6 +830,13 @@ impl<B: BucketIdSlot> TermAggregationMap for VecTermBuckets<B> {
762830
self.buckets.len()
763831
);
764832
let bucket = unsafe { self.buckets.get_unchecked_mut(term_id_usize) };
833+
// Lazy mode: assign the id the first time a term is seen (`count == 0`). Both
834+
// `LAZY_BUCKET_ID_GENERATION` and `B::ASSIGNS_ID` are consts, so in eager mode (or for the
835+
// `()` slot) the whole branch is gated out at monomorphization, leaving just the `count`
836+
// bump.
837+
if LAZY_BUCKET_ID_GENERATION && B::ASSIGNS_ID && bucket.count == 0 {
838+
bucket.bucket_id = B::assign(bucket_id_provider);
839+
}
765840
bucket.count += 1;
766841
bucket.bucket_id
767842
}
@@ -776,13 +851,17 @@ impl<B: BucketIdSlot> TermAggregationMap for VecTermBuckets<B> {
776851
}
777852

778853
fn new(num_terms: u64, bucket_id_provider: &mut BucketIdProvider) -> Self {
779-
VecTermBuckets {
780-
// Assigns an id per term up front from the shared provider; for the `()` slot this is a
781-
// no-op and leaves the provider untouched.
782-
buckets: std::iter::repeat_with(|| Bucket::new(bucket_id_provider))
783-
.take(num_terms as usize)
784-
.collect(),
785-
}
854+
let num_terms = num_terms as usize;
855+
let buckets = if LAZY_BUCKET_ID_GENERATION {
856+
// Empty buckets; ids are assigned lazily in `term_entry`, so the provider is untouched.
857+
vec![Bucket::default(); num_terms]
858+
} else {
859+
// Eager: assign an id to every ordinal up front (a no-op for the `()` slot).
860+
std::iter::repeat_with(|| Bucket::new(bucket_id_provider))
861+
.take(num_terms)
862+
.collect()
863+
};
864+
VecTermBuckets { buckets }
786865
}
787866
}
788867

@@ -2566,6 +2645,69 @@ mod tests {
25662645
Ok(())
25672646
}
25682647

2648+
// Eager bucket ids: below LAZY_BUCKET_ID_GENERATION_THRESHOLD but above
2649+
// MAX_NUM_TERMS_FOR_LOWCARD_SUBAGG, so the `Vec` + `HighCard` buffer path assigns ids eagerly.
2650+
#[test]
2651+
fn terms_aggregation_sub_agg_vec_highcard_eager_ids() -> crate::Result<()> {
2652+
terms_agg_sub_agg_bucket_id_linkage(500)
2653+
}
2654+
2655+
// Lazy bucket ids: above LAZY_BUCKET_ID_GENERATION_THRESHOLD (4096), still below
2656+
// MAX_NUM_TERMS_FOR_VEC, so the `Vec` + `HighCard` path assigns ids lazily on first occurrence.
2657+
#[test]
2658+
fn terms_aggregation_sub_agg_vec_highcard_lazy_ids() -> crate::Result<()> {
2659+
terms_agg_sub_agg_bucket_id_linkage(5000)
2660+
}
2661+
2662+
/// Builds `num_terms` distinct terms (each with a known score and a varying doc count),
2663+
/// inserted in reverse term order so any lazily-assigned bucket ids (first-seen order) are
2664+
/// fully de-correlated from the term ordinals (sorted order). Verifies per-term doc counts
2665+
/// and metric sub-agg values, guarding the bucket-id -> sub-agg linkage on the `Vec` +
2666+
/// `HighCard` path.
2667+
fn terms_agg_sub_agg_bucket_id_linkage(num_terms: u64) -> crate::Result<()> {
2668+
let mut docs: Vec<(f64, String)> = Vec::new();
2669+
for k in (0..num_terms).rev() {
2670+
let count = (k % 3) + 1;
2671+
for _ in 0..count {
2672+
docs.push((k as f64, format!("term_{k:06}")));
2673+
}
2674+
}
2675+
let index = get_test_index_from_values_and_terms(false, &[docs])?;
2676+
2677+
let agg_req: Aggregations = serde_json::from_value(json!({
2678+
"my_texts": {
2679+
"terms": {
2680+
"field": "string_id",
2681+
"size": num_terms,
2682+
"order": { "_key": "asc" }
2683+
},
2684+
"aggs": {
2685+
"sum_score": { "sum": { "field": "score" } },
2686+
"avg_score": { "avg": { "field": "score" } }
2687+
}
2688+
}
2689+
}))
2690+
.unwrap();
2691+
2692+
let res = exec_request(agg_req, &index)?;
2693+
let buckets = &res["my_texts"]["buckets"];
2694+
for k in 0..num_terms {
2695+
let count = (k % 3) + 1;
2696+
let bucket = &buckets[k as usize];
2697+
assert_eq!(bucket["key"], format!("term_{k:06}"), "key at {k}");
2698+
assert_eq!(bucket["doc_count"], count, "doc_count at {k}");
2699+
assert_eq!(
2700+
bucket["sum_score"]["value"],
2701+
(k * count) as f64,
2702+
"sum at {k}"
2703+
);
2704+
assert_eq!(bucket["avg_score"]["value"], k as f64, "avg at {k}");
2705+
}
2706+
assert_eq!(res["my_texts"]["sum_other_doc_count"], 0);
2707+
2708+
Ok(())
2709+
}
2710+
25692711
#[test]
25702712
fn terms_aggregation_different_tokenizer_on_ff_test() -> crate::Result<()> {
25712713
let terms = vec!["Hello Hello", "Hallo Hallo", "Hallo Hallo"];

src/aggregation/bucket/term_agg/term_histogram.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,8 @@ mod tests {
389389
Ok(())
390390
}
391391

392-
/// Term cardinality above the general path's `MAX_NUM_TERMS_FOR_VEC` (100) still fuses: the
393-
/// flat grid is bounded by the total cell count (`num_terms * num_time_buckets`), not the
394-
/// term count.
392+
/// Term cardinality is not what gates fusing: the flat grid is bounded by the total cell count
393+
/// (`num_terms * num_time_buckets`), not the term count, so many terms still fuse.
395394
#[test]
396395
fn fused_term_histogram_many_terms() -> crate::Result<()> {
397396
let num_terms = 150usize;

0 commit comments

Comments
 (0)