1use std::future::Future;
18use std::mem;
19use std::sync::Arc;
20
21#[cfg(any(test, feature = "test"))]
22use async_stream::try_stream;
23use common_time::Timestamp;
24use common_time::range::TimestampRange;
25use common_time::timestamp::TimeUnit;
26use datafusion::execution::memory_pool::MemoryPool;
27use datafusion_expr::expr::Expr;
28use datafusion_expr::{Between, BinaryExpr, Operator};
29#[cfg(test)]
30use datatypes::arrow::record_batch::RecordBatch;
31use datatypes::prelude::ConcreteDataType;
32use datatypes::value::scalar_value_to_timestamp;
33#[cfg(any(test, feature = "test"))]
34use futures::TryStreamExt;
35use store_api::region_engine::PartitionRange;
36use store_api::storage::{FileId, RegionId, SequenceRange, TimeSeriesRowSelector};
37use table::predicate::is_string_timestamp_literal;
38
39use crate::cache::CacheStrategy;
40use crate::cache::range_cache::RangeCacheScope;
41#[cfg(test)]
42use crate::cache::range_cache::{RANGE_CACHE_COMPACT_THRESHOLD_BYTES, RangeScanCacheValue};
43use crate::error::Result;
44use crate::read::BoxedRecordBatchStream;
45use crate::read::pruner::PartitionPruner;
46use crate::read::read_columns::ReadColumns;
47use crate::read::scan_region::StreamContext;
48use crate::read::scan_util::PartitionMetrics;
49use crate::read::series_reader::SeriesRange;
50use crate::region::options::MergeMode;
51#[cfg(any(test, feature = "test"))]
52use crate::sst::parquet::DEFAULT_READ_BATCH_SIZE;
53use crate::sst::parquet::file_range::PreFilterMode;
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub(crate) struct ScanRequestFingerprint {
63 inner: Arc<SharedScanRequestFingerprint>,
65 time_filters: Option<Arc<Vec<String>>>,
67 series_row_selector: Option<TimeSeriesRowSelector>,
68 append_mode: bool,
69 filter_deleted: bool,
70 merge_mode: MergeMode,
71 sequence_range: Option<SequenceRange>,
75 stage: RangeScanStage,
76 file_reads: Option<Arc<Vec<FileReadFingerprint>>>,
78 partition_expr_version: u64,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84enum RangeScanStage {
85 Data,
86 DataForCrossRangeMerge,
89 CandidateSeries,
90 SeriesData(SeriesRange),
91}
92
93#[derive(Debug)]
94pub(crate) struct ScanRequestFingerprintBuilder {
95 pub(crate) read_columns: ReadColumns,
96 pub(crate) read_column_types: Vec<Option<ConcreteDataType>>,
97 pub(crate) filters: Vec<String>,
98 pub(crate) time_filters: Vec<String>,
99 pub(crate) series_row_selector: Option<TimeSeriesRowSelector>,
100 pub(crate) append_mode: bool,
101 pub(crate) filter_deleted: bool,
102 pub(crate) merge_mode: MergeMode,
103 pub(crate) sequence_range: Option<SequenceRange>,
104 pub(crate) partition_expr_version: u64,
105}
106
107impl ScanRequestFingerprintBuilder {
108 pub(crate) fn build(self) -> ScanRequestFingerprint {
109 let Self {
110 read_columns,
111 read_column_types,
112 filters,
113 time_filters,
114 series_row_selector,
115 append_mode,
116 filter_deleted,
117 merge_mode,
118 sequence_range,
119 partition_expr_version,
120 } = self;
121
122 ScanRequestFingerprint {
123 inner: Arc::new(SharedScanRequestFingerprint {
124 read_columns,
125 read_column_types,
126 filters,
127 }),
128 time_filters: (!time_filters.is_empty()).then(|| Arc::new(time_filters)),
129 series_row_selector,
130 append_mode,
131 filter_deleted,
132 merge_mode,
133 sequence_range,
134 stage: RangeScanStage::Data,
135 file_reads: None,
136 partition_expr_version,
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Hash)]
143struct FileReadFingerprint {
144 target_schema_version: u64,
145 source_region: RegionId,
146 file_sequence: Option<u64>,
147 preserve_row_sequence: bool,
148 snapshot_sequence: Option<u64>,
149 pre_filter_mode: PreFilterMode,
150}
151
152#[derive(Debug, PartialEq, Eq, Hash)]
154struct SharedScanRequestFingerprint {
155 read_columns: ReadColumns,
157 read_column_types: Vec<Option<ConcreteDataType>>,
160 filters: Vec<String>,
162}
163
164impl ScanRequestFingerprint {
165 #[cfg(test)]
166 pub(crate) fn read_columns(&self) -> &ReadColumns {
167 &self.inner.read_columns
168 }
169
170 #[cfg(test)]
171 pub(crate) fn read_column_types(&self) -> &[Option<ConcreteDataType>] {
172 &self.inner.read_column_types
173 }
174
175 #[cfg(test)]
176 pub(crate) fn filters(&self) -> &[String] {
177 &self.inner.filters
178 }
179
180 #[cfg(test)]
181 pub(crate) fn time_filters(&self) -> &[String] {
182 self.time_filters
183 .as_deref()
184 .map(Vec::as_slice)
185 .unwrap_or(&[])
186 }
187
188 pub(crate) fn without_time_filters(&self) -> Self {
189 Self {
190 inner: Arc::clone(&self.inner),
191 time_filters: None,
192 series_row_selector: self.series_row_selector,
193 append_mode: self.append_mode,
194 filter_deleted: self.filter_deleted,
195 merge_mode: self.merge_mode,
196 sequence_range: self.sequence_range,
197 file_reads: self.file_reads.clone(),
198 stage: self.stage,
199 partition_expr_version: self.partition_expr_version,
200 }
201 }
202
203 fn for_candidate_series(&self) -> Self {
204 Self {
205 inner: Arc::clone(&self.inner),
206 time_filters: self.time_filters.clone(),
207 series_row_selector: self.series_row_selector,
208 append_mode: self.append_mode,
209 filter_deleted: self.filter_deleted,
210 merge_mode: self.merge_mode,
211 sequence_range: self.sequence_range,
212 file_reads: self.file_reads.clone(),
213 stage: RangeScanStage::CandidateSeries,
214 partition_expr_version: self.partition_expr_version,
215 }
216 }
217
218 fn for_series_data(&self, range: SeriesRange) -> Self {
219 Self {
220 inner: Arc::clone(&self.inner),
221 time_filters: self.time_filters.clone(),
222 series_row_selector: self.series_row_selector,
223 append_mode: self.append_mode,
224 filter_deleted: self.filter_deleted,
225 merge_mode: self.merge_mode,
226 sequence_range: self.sequence_range,
227 file_reads: self.file_reads.clone(),
228 stage: RangeScanStage::SeriesData(range),
229 partition_expr_version: self.partition_expr_version,
230 }
231 }
232
233 pub(crate) fn estimated_size(&self) -> usize {
234 mem::size_of::<SharedScanRequestFingerprint>()
235 + self.file_reads.as_ref().map_or(0, |files| {
236 mem::size_of::<Vec<FileReadFingerprint>>()
237 + files.capacity() * mem::size_of::<FileReadFingerprint>()
238 })
239 + self.inner.read_columns.estimated_size()
240 + self.inner.read_column_types.capacity() * mem::size_of::<Option<ConcreteDataType>>()
241 + self.inner.filters.capacity() * mem::size_of::<String>()
242 + self
243 .inner
244 .filters
245 .iter()
246 .map(|filter| filter.capacity())
247 .sum::<usize>()
248 + self.time_filters.as_ref().map_or(0, |filters| {
249 mem::size_of::<Vec<String>>()
250 + filters.capacity() * mem::size_of::<String>()
251 + filters
252 .iter()
253 .map(|filter| filter.capacity())
254 .sum::<usize>()
255 })
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Hash)]
261pub(crate) struct RangeScanCacheKey {
262 pub(crate) region_id: RegionId,
263 pub(crate) row_groups: Vec<(FileId, i64)>,
265 pub(crate) scan: ScanRequestFingerprint,
266}
267
268impl RangeScanCacheKey {
269 pub(crate) fn for_cross_range_merge(mut self) -> Self {
270 debug_assert_eq!(self.scan.stage, RangeScanStage::Data);
271 self.scan.stage = RangeScanStage::DataForCrossRangeMerge;
272 self
273 }
274
275 pub(crate) fn estimated_size(&self) -> usize {
276 mem::size_of::<Self>()
277 + self.row_groups.capacity() * mem::size_of::<(FileId, i64)>()
278 + self.scan.estimated_size()
279 }
280}
281
282pub(crate) fn implied_time_range_from_exprs(
300 ts_col_name: &str,
301 ts_col_unit: TimeUnit,
302 exprs: &[&Expr],
303) -> Option<TimestampRange> {
304 if exprs.is_empty() {
305 return None;
306 }
307
308 let mut acc = TimestampRange::min_to_max();
309 for expr in exprs {
310 let r = implied_time_range_from_expr(ts_col_name, ts_col_unit, expr)?;
311 acc = acc.and(&r);
312 }
313 Some(acc)
314}
315
316fn implied_time_range_from_expr(
317 ts_col_name: &str,
318 ts_col_unit: TimeUnit,
319 expr: &Expr,
320) -> Option<TimestampRange> {
321 match expr {
322 Expr::BinaryExpr(BinaryExpr { left, op, right }) => match op {
323 Operator::And => {
324 let l = implied_time_range_from_expr(ts_col_name, ts_col_unit, left)?;
325 let r = implied_time_range_from_expr(ts_col_name, ts_col_unit, right)?;
326 Some(l.and(&r))
327 }
328 Operator::Eq | Operator::Lt | Operator::LtEq | Operator::Gt | Operator::GtEq => {
329 implied_from_cmp(ts_col_name, ts_col_unit, left, *op, right)
330 }
331 _ => None,
335 },
336 Expr::Between(Between {
337 expr,
338 negated,
339 low,
340 high,
341 }) => {
342 if *negated {
343 return None;
344 }
345 implied_from_between(ts_col_name, ts_col_unit, expr, low, high)
346 }
347 _ => None,
349 }
350}
351
352fn match_ts_column_literal<'a>(
353 ts_col_name: &str,
354 left: &'a Expr,
355 right: &'a Expr,
356) -> Option<(Timestamp, bool)> {
357 let (col, scalar, reverse) = match (left, right) {
358 (Expr::Column(c), Expr::Literal(s, _)) => (c, s, false),
359 (Expr::Literal(s, _), Expr::Column(c)) => (c, s, true),
360 _ => return None,
361 };
362 if col.name != ts_col_name {
363 return None;
364 }
365 if is_string_timestamp_literal(scalar) {
368 return None;
369 }
370 scalar_value_to_timestamp(scalar, None).map(|t| (t, reverse))
371}
372
373fn implied_from_cmp(
374 ts_col_name: &str,
375 ts_col_unit: TimeUnit,
376 left: &Expr,
377 op: Operator,
378 right: &Expr,
379) -> Option<TimestampRange> {
380 let (ts, reverse) = match_ts_column_literal(ts_col_name, left, right)?;
381 let op = if reverse {
383 match op {
384 Operator::Lt => Operator::Gt,
385 Operator::LtEq => Operator::GtEq,
386 Operator::Gt => Operator::Lt,
387 Operator::GtEq => Operator::LtEq,
388 Operator::Eq => Operator::Eq,
389 _ => return None,
390 }
391 } else {
392 op
393 };
394
395 match op {
396 Operator::GtEq => {
397 let b = ts.convert_to_ceil(ts_col_unit)?;
399 Some(TimestampRange::from_start(b))
400 }
401 Operator::Gt => {
402 let v = ts.convert_to(ts_col_unit)?.value().checked_add(1)?;
404 Some(TimestampRange::from_start(Timestamp::new(v, ts_col_unit)))
405 }
406 Operator::LtEq => {
407 let b = ts.convert_to(ts_col_unit)?;
409 Some(TimestampRange::until_end(b, true))
410 }
411 Operator::Lt => {
412 let b = ts.convert_to_ceil(ts_col_unit)?;
415 Some(TimestampRange::until_end(b, false))
416 }
417 Operator::Eq => {
418 let f = ts.convert_to(ts_col_unit)?;
420 let c = ts.convert_to_ceil(ts_col_unit)?;
421 if f.value() != c.value() {
422 return None;
423 }
424 Some(TimestampRange::single(f))
425 }
426 _ => None,
427 }
428}
429
430fn implied_from_between(
431 ts_col_name: &str,
432 ts_col_unit: TimeUnit,
433 expr: &Expr,
434 low: &Expr,
435 high: &Expr,
436) -> Option<TimestampRange> {
437 let Expr::Column(c) = expr else {
438 return None;
439 };
440 if c.name != ts_col_name {
441 return None;
442 }
443 let (low_s, high_s) = match (low, high) {
444 (Expr::Literal(l, _), Expr::Literal(h, _)) => (l, h),
445 _ => return None,
446 };
447 if is_string_timestamp_literal(low_s) || is_string_timestamp_literal(high_s) {
448 return None;
449 }
450 let low_ts = scalar_value_to_timestamp(low_s, None)?;
451 let high_ts = scalar_value_to_timestamp(high_s, None)?;
452 let lo = low_ts.convert_to_ceil(ts_col_unit)?;
454 let hi = high_ts.convert_to(ts_col_unit)?;
455 Some(TimestampRange::new_inclusive(Some(lo), Some(hi)))
456}
457
458pub(crate) fn build_range_cache_key(
460 stream_ctx: &StreamContext,
461 part_range: &PartitionRange,
462 partition_pruner: Option<&PartitionPruner>,
463) -> Option<RangeScanCacheKey> {
464 build_range_cache_key_inner(stream_ctx, part_range, None, partition_pruner)
465}
466
467pub(crate) fn build_candidate_range_cache_key(
469 stream_ctx: &StreamContext,
470 part_range: &PartitionRange,
471 partition_pruner: &PartitionPruner,
472) -> Option<RangeScanCacheKey> {
473 build_range_cache_key_inner(
474 stream_ctx,
475 part_range,
476 Some(RangeScanStage::CandidateSeries),
477 Some(partition_pruner),
478 )
479}
480
481pub(crate) fn build_series_range_cache_key(
483 stream_ctx: &StreamContext,
484 part_range: &PartitionRange,
485 range: SeriesRange,
486 partition_pruner: &PartitionPruner,
487) -> Option<RangeScanCacheKey> {
488 build_range_cache_key_inner(
489 stream_ctx,
490 part_range,
491 Some(RangeScanStage::SeriesData(range)),
492 Some(partition_pruner),
493 )
494}
495
496fn build_range_cache_key_inner(
497 stream_ctx: &StreamContext,
498 part_range: &PartitionRange,
499 stage: Option<RangeScanStage>,
500 partition_pruner: Option<&PartitionPruner>,
501) -> Option<RangeScanCacheKey> {
502 if !stream_ctx.input.cache_strategy.has_range_result_cache() {
503 return None;
504 }
505
506 let fingerprint = stream_ctx.input.scan_fingerprint()?;
507
508 let has_dyn_filters = stream_ctx
510 .input
511 .predicate_group()
512 .predicate_without_region()
513 .is_some_and(|p| !p.dyn_filters().is_empty());
514 if has_dyn_filters {
515 return None;
516 }
517
518 let range_meta = &stream_ctx.ranges[part_range.identifier];
519 let mut files = Vec::with_capacity(range_meta.row_group_indices.len());
520 for index in &range_meta.row_group_indices {
521 if !stream_ctx.is_file_range_index(*index) {
522 return None;
523 }
524 let handle = stream_ctx.input.file_from_index(*index);
525 let file = handle.meta_ref();
526 files.push((
527 (handle.file_id().file_id(), index.row_group_index),
528 FileReadFingerprint {
529 target_schema_version: stream_ctx.input.region_metadata().schema_version,
530 source_region: handle.region_id(),
531 file_sequence: file.sequence.map(|sequence| sequence.get()),
532 preserve_row_sequence: file.preserve_row_sequence,
533 snapshot_sequence: stream_ctx.input.snapshot_sequence,
534 pre_filter_mode: partition_pruner.map_or_else(
535 || stream_ctx.range_pre_filter_mode(part_range),
536 |pruner| pruner.pre_filter_mode(index.index - stream_ctx.input.num_memtables()),
537 ),
538 },
539 ));
540 }
541 if files.is_empty() {
542 return None;
543 }
544 files.sort_unstable_by(|a, b| {
545 a.0.0
546 .as_bytes()
547 .cmp(b.0.0.as_bytes())
548 .then(a.0.1.cmp(&b.0.1))
549 });
550 let (row_groups, file_reads): (Vec<_>, Vec<_>) = files.into_iter().unzip();
551
552 let (file_min, file_max) = range_meta.time_range;
559 let covers = match stream_ctx.input.implied_time_range() {
560 Some(implied) if !implied.is_empty() => {
563 implied.contains(&file_min) && implied.contains(&file_max)
569 }
570 _ => false,
571 };
572 let scan = if covers {
573 fingerprint.without_time_filters()
574 } else {
575 fingerprint.clone()
576 };
577 let mut scan = match stage {
578 Some(RangeScanStage::CandidateSeries) => scan.for_candidate_series(),
579 Some(RangeScanStage::SeriesData(assignment)) => scan.for_series_data(assignment),
580 Some(RangeScanStage::DataForCrossRangeMerge) => {
581 let mut scan = scan;
582 scan.stage = RangeScanStage::DataForCrossRangeMerge;
583 scan
584 }
585 Some(RangeScanStage::Data) | None => scan,
586 };
587
588 scan.file_reads = Some(Arc::new(file_reads));
589 Some(RangeScanCacheKey {
590 region_id: stream_ctx.input.region_metadata().region_id,
591 row_groups,
592 scan,
593 })
594}
595
596pub(crate) struct CachedRangeReader<'a> {
598 pub(crate) stream_ctx: &'a StreamContext,
599 pub(crate) part_range: &'a PartitionRange,
600 pub(crate) key: Option<RangeScanCacheKey>,
601 pub(crate) metrics: &'a PartitionMetrics,
602}
603
604impl CachedRangeReader<'_> {
605 pub(crate) async fn build<F, Fut>(self, build: F) -> Result<(BoxedRecordBatchStream, usize)>
606 where
607 F: FnOnce() -> Fut,
608 Fut: Future<Output = Result<(BoxedRecordBatchStream, usize)>>,
609 {
610 self.build_in_scope(RangeCacheScope::default(), build).await
611 }
612
613 pub(crate) async fn build_with_memory_pool<F, Fut>(
615 self,
616 memory_pool: &Arc<dyn MemoryPool>,
617 build: F,
618 ) -> Result<(BoxedRecordBatchStream, usize)>
619 where
620 F: FnOnce() -> Fut,
621 Fut: Future<Output = Result<(BoxedRecordBatchStream, usize)>>,
622 {
623 self.build_in_scope(RangeCacheScope::for_memory_pool(memory_pool), build)
624 .await
625 }
626
627 async fn build_in_scope<F, Fut>(
628 self,
629 scope: RangeCacheScope,
630 build: F,
631 ) -> Result<(BoxedRecordBatchStream, usize)>
632 where
633 F: FnOnce() -> Fut,
634 Fut: Future<Output = Result<(BoxedRecordBatchStream, usize)>>,
635 {
636 let Some(key) = self.key else {
637 return build().await;
638 };
639 let files = self.stream_ctx.ranges[self.part_range.identifier]
640 .row_group_indices
641 .iter()
642 .map(|index| self.stream_ctx.input.file_from_index(*index).clone())
643 .collect();
644 load_shared_range(
645 &self.stream_ctx.input.cache_strategy,
646 key,
647 self.metrics,
648 files,
649 scope,
650 build,
651 )
652 .await
653 }
654}
655
656#[cfg(any(test, feature = "test"))]
658pub(crate) async fn get_or_load_range_result<F, Fut>(
659 strategy: &CacheStrategy,
660 key: RangeScanCacheKey,
661 part_metrics: &PartitionMetrics,
662 build: F,
663) -> Result<(BoxedRecordBatchStream, usize)>
664where
665 F: FnOnce() -> Fut,
666 Fut: Future<Output = Result<(BoxedRecordBatchStream, usize)>>,
667{
668 get_or_load_range_result_in_scope(
669 strategy,
670 key,
671 part_metrics,
672 RangeCacheScope::default(),
673 build,
674 )
675 .await
676}
677
678#[cfg(any(test, feature = "test"))]
681pub(crate) async fn get_or_load_range_result_in_scope<F, Fut>(
682 strategy: &CacheStrategy,
683 key: RangeScanCacheKey,
684 part_metrics: &PartitionMetrics,
685 scope: RangeCacheScope,
686 build: F,
687) -> Result<(BoxedRecordBatchStream, usize)>
688where
689 F: FnOnce() -> Fut,
690 Fut: Future<Output = Result<(BoxedRecordBatchStream, usize)>>,
691{
692 load_shared_range(strategy, key, part_metrics, Vec::new(), scope, build).await
693}
694
695async fn load_shared_range<F, Fut>(
696 strategy: &CacheStrategy,
697 key: RangeScanCacheKey,
698 part_metrics: &PartitionMetrics,
699 files: Vec<crate::sst::file::FileHandle>,
700 scope: RangeCacheScope,
701 build: F,
702) -> Result<(BoxedRecordBatchStream, usize)>
703where
704 F: FnOnce() -> Fut,
705 Fut: Future<Output = Result<(BoxedRecordBatchStream, usize)>>,
706{
707 let Some(cache) = strategy.range_result_cache() else {
708 return build().await;
709 };
710 cache
711 .read(
712 key,
713 part_metrics.range_cache_metrics_reporter(),
714 files,
715 scope,
716 build,
717 )
718 .await
719}
720
721#[cfg(feature = "test")]
729pub fn bench_cache_flat_range_stream(
730 stream: BoxedRecordBatchStream,
731 cache_size_bytes: u64,
732 region_id: RegionId,
733) -> BoxedRecordBatchStream {
734 use std::time::Instant;
735
736 use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
737
738 use crate::region::options::MergeMode;
739
740 let cache_manager = Arc::new(
741 crate::cache::CacheManager::builder()
742 .range_result_cache_size(cache_size_bytes)
743 .build(),
744 );
745 let cache_strategy = CacheStrategy::EnableAll(cache_manager);
746
747 let fingerprint = ScanRequestFingerprintBuilder {
748 read_columns: ReadColumns::new(std::iter::empty()),
749 read_column_types: vec![],
750 filters: vec![],
751 time_filters: vec![],
752 series_row_selector: None,
753 append_mode: false,
754 filter_deleted: false,
755 merge_mode: MergeMode::LastRow,
756 sequence_range: None,
757 partition_expr_version: 0,
758 }
759 .build();
760
761 let key = RangeScanCacheKey {
762 region_id,
763 row_groups: vec![],
764 scan: fingerprint,
765 };
766
767 let metrics_set = ExecutionPlanMetricsSet::new();
768 let part_metrics =
769 PartitionMetrics::new(region_id, 0, "bench", Instant::now(), false, &metrics_set);
770
771 Box::pin(try_stream! {
772 let (mut stream, _) = get_or_load_range_result(
773 &cache_strategy,
774 key,
775 &part_metrics,
776 || async { Ok((stream, DEFAULT_READ_BATCH_SIZE)) },
777 ).await?;
778 while let Some(batch) = stream.try_next().await? {
779 yield batch;
780 }
781 })
782}
783
784#[cfg(test)]
785mod tests {
786 use std::sync::Arc;
787 use std::time::Instant;
788
789 use common_time::Timestamp;
790 use common_time::range::TimestampRange;
791 use common_time::timestamp::TimeUnit;
792 use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
793 use datafusion_common::ScalarValue;
794 use datafusion_expr::{Expr, col, lit};
795 use smallvec::smallvec;
796 use store_api::storage::{FileId, RegionId};
797
798 use super::*;
799 use crate::cache::CacheManager;
800 use crate::read::flat_projection::FlatProjectionMapper;
801 use crate::read::pruner::Pruner;
802 use crate::read::range::{RangeMeta, RowGroupIndex, SourceIndex};
803 use crate::read::scan_region::{PredicateGroup, ScanInput, StreamContext};
804 use crate::sst::file::FileTimeRange;
805 use crate::test_util::memtable_util::metadata_with_primary_key;
806 use crate::test_util::scheduler_util::SchedulerEnv;
807 use crate::test_util::sst_util::sst_file_handle_with_file_id;
808
809 fn test_cache_strategy() -> CacheStrategy {
810 CacheStrategy::EnableAll(Arc::new(
811 CacheManager::builder()
812 .range_result_cache_size(1024 * 1024)
813 .build(),
814 ))
815 }
816
817 fn test_scan_fingerprint(
818 filters: Vec<String>,
819 time_filters: Vec<String>,
820 series_row_selector: Option<TimeSeriesRowSelector>,
821 filter_deleted: bool,
822 partition_expr_version: u64,
823 ) -> ScanRequestFingerprint {
824 let read_columns = ReadColumns::new([1, 2]);
825 ScanRequestFingerprintBuilder {
826 read_columns,
827 read_column_types: vec![None, None],
828 filters,
829 time_filters,
830 series_row_selector,
831 append_mode: false,
832 filter_deleted,
833 merge_mode: MergeMode::LastRow,
834 sequence_range: None,
835 partition_expr_version,
836 }
837 .build()
838 }
839
840 fn test_cache_context(strategy: &CacheStrategy) -> (RangeScanCacheKey, PartitionMetrics) {
841 let region_id = RegionId::new(1, 1);
842 let key = RangeScanCacheKey {
843 region_id,
844 row_groups: vec![],
845 scan: test_scan_fingerprint(vec![], vec![], None, false, 0),
846 };
847
848 let metrics_set = ExecutionPlanMetricsSet::new();
849 let part_metrics =
850 PartitionMetrics::new(region_id, 0, "test", Instant::now(), false, &metrics_set);
851
852 assert!(strategy.get_range_result(&key).is_none());
853 (key, part_metrics)
854 }
855
856 async fn new_stream_context(
857 filters: Vec<Expr>,
858 query_time_range: Option<TimestampRange>,
859 partition_time_range: FileTimeRange,
860 ) -> (StreamContext, PartitionRange) {
861 let env = SchedulerEnv::new().await;
862 let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
863 let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
864 let predicate = PredicateGroup::new(metadata.as_ref(), &filters).unwrap();
865 let file_id = FileId::random();
866 let file = sst_file_handle_with_file_id(
867 file_id,
868 partition_time_range.0.value(),
869 partition_time_range.1.value(),
870 );
871 let input = ScanInput::builder(env.access_layer.clone(), mapper)
872 .with_predicate(predicate)
873 .with_time_range(query_time_range)
874 .with_files(vec![file])
875 .with_cache(test_cache_strategy())
876 .build();
877 let range_meta = RangeMeta {
878 time_range: partition_time_range,
879 indices: smallvec![SourceIndex {
880 index: 0,
881 num_row_groups: 1,
882 }],
883 row_group_indices: smallvec![RowGroupIndex {
884 index: 0,
885 row_group_index: 0,
886 }],
887 num_rows: 10,
888 };
889 let partition_range = range_meta.new_partition_range(0);
890 let stream_ctx = StreamContext {
891 input,
892 ranges: vec![range_meta],
893 query_start: Instant::now(),
894 };
895
896 (stream_ctx, partition_range)
897 }
898
899 #[rstest::rstest]
900 #[case(1024 * 1024, true)]
901 #[case(1, false)]
902 #[tokio::test]
903 async fn test_shared_range_read_preserves_batches_when_admission_budget_is_exhausted(
904 #[case] capacity: u64,
905 #[case] cached: bool,
906 ) {
907 use std::sync::atomic::{AtomicUsize, Ordering};
908
909 let (mut ctx, range) =
910 new_stream_context(vec![col("k0").eq(lit("a"))], None, FileTimeRange::default()).await;
911 ctx.input.cache_strategy = CacheStrategy::EnableAll(Arc::new(
912 CacheManager::builder()
913 .range_result_cache_size(capacity)
914 .build(),
915 ));
916 let key = build_range_cache_key(&ctx, &range, None).unwrap();
917 let (_, metrics) = test_cache_context(&ctx.input.cache_strategy);
918 let calls = Arc::new(AtomicUsize::new(0));
919 let expected = vec![make_batch(&[1, 2]), make_batch(&[3, 4, 5])];
920 let batches = expected.clone();
921 let count = calls.clone();
922 let (stream, _) = CachedRangeReader {
923 stream_ctx: &ctx,
924 part_range: &range,
925 key: Some(key.clone()),
926 metrics: &metrics,
927 }
928 .build(move || async move {
929 count.fetch_add(1, Ordering::Relaxed);
930 Ok((
931 Box::pin(futures::stream::iter(batches.into_iter().map(Ok)))
932 as BoxedRecordBatchStream,
933 DEFAULT_READ_BATCH_SIZE,
934 ))
935 })
936 .await
937 .unwrap();
938 let actual: Vec<_> = stream.try_collect().await.unwrap();
939 assert_eq!(actual, expected);
940 assert_eq!(calls.load(Ordering::Relaxed), 1);
941 if cached {
942 wait_cached(&ctx.input.cache_strategy, &key).await;
943 }
944 assert_eq!(
945 ctx.input.cache_strategy.get_range_result(&key).is_some(),
946 cached
947 );
948 let memory = &ctx
949 .input
950 .cache_strategy
951 .range_result_cache()
952 .unwrap()
953 .retention_memory();
954 tokio::time::timeout(std::time::Duration::from_secs(5), async {
955 while memory.available_permits() != (capacity as usize).div_ceil(1024) {
956 tokio::task::yield_now().await;
957 }
958 })
959 .await
960 .expect("finalization did not release the source reservation");
961 }
962
963 #[tokio::test]
964 async fn test_cancelling_a_partial_load_releases_its_reservation() {
965 let strategy = test_cache_strategy();
966 let worker = strategy.range_result_cache().unwrap().clone();
967 let capacity = worker.retention_memory().available_permits();
968 let (key, metrics) = test_cache_context(&strategy);
969 let (mut stream, _) =
970 get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
971 Ok((
972 Box::pin(try_stream! {
973 yield make_batch(&[1, 2, 3]);
974 std::future::pending::<()>().await;
975 }) as BoxedRecordBatchStream,
976 DEFAULT_READ_BATCH_SIZE,
977 ))
978 })
979 .await
980 .unwrap();
981 assert!(stream.try_next().await.unwrap().is_some());
982 assert!(worker.retention_memory().available_permits() < capacity);
983 drop(stream);
984 assert_eq!(worker.retention_memory().available_permits(), capacity);
985 assert!(worker.get(&key).is_none());
986 }
987
988 #[tokio::test]
989 async fn test_follower_can_finish_without_polling_the_first_range_stream() {
990 use std::sync::atomic::{AtomicUsize, Ordering};
991
992 let (ctx, range) =
993 new_stream_context(vec![col("k0").eq(lit("a"))], None, FileTimeRange::default()).await;
994 let key = build_range_cache_key(&ctx, &range, None).unwrap();
995 let (_, metrics) = test_cache_context(&ctx.input.cache_strategy);
996 let gate = Arc::new(tokio::sync::Semaphore::new(0));
997 let calls = Arc::new(AtomicUsize::new(0));
998 let count = calls.clone();
999 let loader_gate = gate.clone();
1000 let build = move || async move {
1001 count.fetch_add(1, Ordering::Relaxed);
1002 Ok((
1003 Box::pin(try_stream! {
1004 let _permit = loader_gate.acquire().await.unwrap();
1005 yield make_batch(&[1, 2, 3]);
1006 }) as BoxedRecordBatchStream,
1007 DEFAULT_READ_BATCH_SIZE,
1008 ))
1009 };
1010 let (first, _) = CachedRangeReader {
1011 stream_ctx: &ctx,
1012 part_range: &range,
1013 key: Some(key.clone()),
1014 metrics: &metrics,
1015 }
1016 .build(build.clone())
1017 .await
1018 .unwrap();
1019 let (second, _) = CachedRangeReader {
1020 stream_ctx: &ctx,
1021 part_range: &range,
1022 key: Some(key),
1023 metrics: &metrics,
1024 }
1025 .build(build)
1026 .await
1027 .unwrap();
1028 gate.add_permits(1);
1029 let second_batches: Vec<_> =
1030 tokio::time::timeout(std::time::Duration::from_secs(5), second.try_collect())
1031 .await
1032 .expect("a follower must not depend on polling the first query")
1033 .unwrap();
1034 let first_batches: Vec<_> = first.try_collect().await.unwrap();
1035 assert_eq!(first_batches, second_batches);
1036 assert_eq!(second_batches, vec![make_batch(&[1, 2, 3])]);
1037 assert_eq!(calls.load(Ordering::Relaxed), 1);
1038 }
1039
1040 fn ts_lit(val: i64) -> Expr {
1042 lit(ScalarValue::TimestampMillisecond(Some(val), None))
1043 }
1044
1045 fn normalized_exprs(exprs: impl IntoIterator<Item = Expr>) -> Vec<String> {
1046 let mut exprs = exprs
1047 .into_iter()
1048 .map(|expr| expr.to_string())
1049 .collect::<Vec<_>>();
1050 exprs.sort_unstable();
1051 exprs
1052 }
1053
1054 async fn assert_range_cache_filters(
1055 filters: Vec<Expr>,
1056 query_time_range: Option<TimestampRange>,
1057 partition_time_range: FileTimeRange,
1058 expected_filters: Vec<Expr>,
1059 expected_time_filters: Vec<Expr>,
1060 ) {
1061 let (stream_ctx, part_range) =
1062 new_stream_context(filters, query_time_range, partition_time_range).await;
1063
1064 let key = build_range_cache_key(&stream_ctx, &part_range, None).unwrap();
1065
1066 assert_eq!(
1067 key.scan.filters(),
1068 normalized_exprs(expected_filters).as_slice()
1069 );
1070 assert_eq!(
1071 key.scan.time_filters(),
1072 normalized_exprs(expected_time_filters).as_slice()
1073 );
1074 }
1075
1076 #[tokio::test]
1077 async fn test_range_cache_time_filter_key_cases() {
1078 let partition = (
1079 Timestamp::new_millisecond(1000),
1080 Timestamp::new_millisecond(2000),
1081 );
1082
1083 struct Case {
1084 filters: Vec<Expr>,
1085 query_time_range: Option<TimestampRange>,
1086 expected_filters: Vec<Expr>,
1087 expected_time_filters: Vec<Expr>,
1088 }
1089
1090 for case in [
1094 Case {
1095 filters: vec![
1096 col("ts").gt_eq(ts_lit(1000)),
1097 col("ts").lt(ts_lit(2001)),
1098 col("ts").is_not_null(),
1099 col("k0").eq(lit("foo")),
1100 ],
1101 query_time_range: TimestampRange::with_unit(1000, 2002, TimeUnit::Millisecond),
1102 expected_filters: vec![col("k0").eq(lit("foo")), col("ts").is_not_null()],
1103 expected_time_filters: vec![],
1104 },
1105 Case {
1106 filters: vec![
1107 col("ts").gt_eq(ts_lit(500)),
1108 col("ts").lt(ts_lit(3000)),
1109 col("k0").eq(lit("foo")),
1110 ],
1111 query_time_range: TimestampRange::with_unit(500, 3000, TimeUnit::Millisecond),
1112 expected_filters: vec![col("k0").eq(lit("foo"))],
1113 expected_time_filters: vec![],
1114 },
1115 Case {
1116 filters: vec![
1117 col("ts").gt_eq(ts_lit(1000)),
1118 col("ts").lt_eq(ts_lit(2000)),
1119 col("k0").eq(lit("foo")),
1120 ],
1121 query_time_range: TimestampRange::with_unit(1000, 2001, TimeUnit::Millisecond),
1122 expected_filters: vec![col("k0").eq(lit("foo"))],
1123 expected_time_filters: vec![],
1124 },
1125 Case {
1126 filters: vec![
1127 col("ts").between(ts_lit(1000), ts_lit(2000)),
1128 col("k0").eq(lit("foo")),
1129 ],
1130 query_time_range: TimestampRange::with_unit(1000, 2001, TimeUnit::Millisecond),
1131 expected_filters: vec![col("k0").eq(lit("foo"))],
1132 expected_time_filters: vec![],
1133 },
1134 Case {
1135 filters: vec![col("ts").gt_eq(ts_lit(1200)), col("k0").eq(lit("foo"))],
1136 query_time_range: TimestampRange::with_unit(1200, 2001, TimeUnit::Millisecond),
1137 expected_filters: vec![col("k0").eq(lit("foo"))],
1138 expected_time_filters: vec![col("ts").gt_eq(ts_lit(1200))],
1139 },
1140 Case {
1141 filters: vec![
1142 col("ts").gt_eq(ts_lit(1500)),
1143 col("ts").is_not_null(),
1144 col("k0").eq(lit("foo")),
1145 ],
1146 query_time_range: None,
1147 expected_filters: vec![col("k0").eq(lit("foo")), col("ts").is_not_null()],
1148 expected_time_filters: vec![col("ts").gt_eq(ts_lit(1500))],
1149 },
1150 ] {
1151 assert_range_cache_filters(
1152 case.filters,
1153 case.query_time_range,
1154 partition,
1155 case.expected_filters,
1156 case.expected_time_filters,
1157 )
1158 .await;
1159 }
1160 }
1161
1162 #[tokio::test]
1163 async fn test_two_distinct_queries_share_cache_key_when_both_cover() {
1164 let partition_range = (
1165 Timestamp::new_millisecond(1000),
1166 Timestamp::new_millisecond(2000),
1167 );
1168
1169 let (ctx_a, part_a) = new_stream_context(
1170 vec![
1171 col("ts").gt_eq(ts_lit(500)),
1172 col("ts").lt(ts_lit(3000)),
1173 col("k0").eq(lit("foo")),
1174 ],
1175 TimestampRange::with_unit(500, 3000, TimeUnit::Millisecond),
1176 partition_range,
1177 )
1178 .await;
1179 let (ctx_b, part_b) = new_stream_context(
1180 vec![
1181 col("ts").gt_eq(ts_lit(100)),
1182 col("ts").lt(ts_lit(5000)),
1183 col("k0").eq(lit("foo")),
1184 ],
1185 TimestampRange::with_unit(100, 5000, TimeUnit::Millisecond),
1186 partition_range,
1187 )
1188 .await;
1189
1190 let key_a = build_range_cache_key(&ctx_a, &part_a, None).unwrap();
1191 let key_b = build_range_cache_key(&ctx_b, &part_b, None).unwrap();
1192 assert_eq!(key_a.scan, key_b.scan);
1193 assert!(key_a.scan.time_filters().is_empty());
1194 }
1195
1196 #[tokio::test]
1197 async fn test_scan_stages_have_separate_cache_keys() {
1198 let partition_range = (
1199 Timestamp::new_millisecond(1000),
1200 Timestamp::new_millisecond(2000),
1201 );
1202 let (ctx, part_range) =
1203 new_stream_context(vec![col("k0").eq(lit("foo"))], None, partition_range).await;
1204
1205 let ctx = Arc::new(ctx);
1206 let pruner = Arc::new(crate::read::pruner::Pruner::new(ctx.clone(), 0));
1207 let partition = PartitionPruner::new(pruner, std::slice::from_ref(&part_range));
1208 let data_key = build_range_cache_key(&ctx, &part_range, None).unwrap();
1209 let candidate_key = build_candidate_range_cache_key(&ctx, &part_range, &partition).unwrap();
1210 let range_0 = SeriesRange::new(0, 2).unwrap();
1211 let range_1 = SeriesRange::new(1, 2).unwrap();
1212 let series_key_0 =
1213 build_series_range_cache_key(&ctx, &part_range, range_0, &partition).unwrap();
1214 let series_key_1 =
1215 build_series_range_cache_key(&ctx, &part_range, range_1, &partition).unwrap();
1216
1217 let merge_key = data_key.clone().for_cross_range_merge();
1218 assert_ne!(merge_key, data_key);
1219 assert_ne!(merge_key.scan, candidate_key.scan);
1220 assert_ne!(merge_key.scan, series_key_0.scan);
1221 assert_eq!(merge_key.row_groups, data_key.row_groups);
1222 assert_eq!(merge_key, data_key.clone().for_cross_range_merge());
1223 assert_ne!(data_key.scan, candidate_key.scan);
1224 assert_ne!(candidate_key.scan, series_key_0.scan);
1225 assert_eq!(data_key.row_groups, candidate_key.row_groups);
1226 assert_ne!(data_key.scan, series_key_0.scan);
1227 assert_ne!(series_key_0.scan, series_key_1.scan);
1228 assert_eq!(data_key.row_groups, series_key_0.row_groups);
1229 }
1230
1231 #[tokio::test]
1232 async fn test_disables_optimization_on_or_clause() {
1233 let partition_range = (
1234 Timestamp::new_millisecond(1000),
1235 Timestamp::new_millisecond(2000),
1236 );
1237
1238 let or_a = col("ts").gt_eq(ts_lit(1000)).or(col("ts").lt(ts_lit(500)));
1239 let or_b = col("ts").gt_eq(ts_lit(900)).or(col("ts").lt(ts_lit(400)));
1240
1241 let (ctx_a, part_a) = new_stream_context(
1242 vec![or_a.clone(), col("k0").eq(lit("foo"))],
1243 None,
1244 partition_range,
1245 )
1246 .await;
1247 let (ctx_b, part_b) = new_stream_context(
1248 vec![or_b.clone(), col("k0").eq(lit("foo"))],
1249 None,
1250 partition_range,
1251 )
1252 .await;
1253
1254 assert!(ctx_a.input.implied_time_range().is_none());
1255 let key_a = build_range_cache_key(&ctx_a, &part_a, None).unwrap();
1256 let key_b = build_range_cache_key(&ctx_b, &part_b, None).unwrap();
1257 assert_ne!(key_a.scan, key_b.scan);
1258 assert_eq!(
1259 key_a.scan.time_filters(),
1260 normalized_exprs([or_a]).as_slice()
1261 );
1262 }
1263
1264 #[tokio::test]
1265 async fn test_empty_implied_range_does_not_panic_on_non_ms_file_range() {
1266 let partition = (
1271 Timestamp::new_millisecond(1000),
1272 Timestamp::new_millisecond(2000),
1273 );
1274
1275 let (mut ctx, part_range) = new_stream_context(
1276 vec![
1277 col("ts").gt_eq(ts_lit(1500)),
1278 col("ts").lt(ts_lit(1500)),
1279 col("k0").eq(lit("foo")),
1280 ],
1281 TimestampRange::with_unit(1500, 3000, TimeUnit::Millisecond),
1282 partition,
1283 )
1284 .await;
1285
1286 assert!(ctx.input.implied_time_range().unwrap().is_empty());
1287 ctx.ranges[0].time_range = (
1288 Timestamp::new(1_000_000_000, TimeUnit::Nanosecond),
1289 Timestamp::new(2_000_000_000, TimeUnit::Nanosecond),
1290 );
1291
1292 let key = build_range_cache_key(&ctx, &part_range, None).unwrap();
1293 assert!(!key.scan.time_filters().is_empty());
1295 }
1296
1297 #[tokio::test]
1298 async fn test_mixed_unit_file_range_is_compared_by_instant() {
1299 let partition = (
1304 Timestamp::new_millisecond(1000),
1305 Timestamp::new_millisecond(2000),
1306 );
1307
1308 let (mut ctx, part_range) = new_stream_context(
1312 vec![
1313 col("ts").gt_eq(ts_lit(0)),
1314 col("ts").lt(ts_lit(3000)),
1315 col("k0").eq(lit("foo")),
1316 ],
1317 TimestampRange::with_unit(0, 3000, TimeUnit::Millisecond),
1318 partition,
1319 )
1320 .await;
1321 ctx.ranges[0].time_range = (
1322 Timestamp::new_microsecond(1_000_000),
1323 Timestamp::new_microsecond(2_000_000),
1324 );
1325 let key = build_range_cache_key(&ctx, &part_range, None).unwrap();
1326 assert!(key.scan.time_filters().is_empty());
1327
1328 let (mut ctx, part_range) = new_stream_context(
1331 vec![
1332 col("ts").gt_eq(ts_lit(1500)),
1333 col("ts").lt(ts_lit(3000)),
1334 col("k0").eq(lit("foo")),
1335 ],
1336 TimestampRange::with_unit(1500, 3000, TimeUnit::Millisecond),
1337 partition,
1338 )
1339 .await;
1340 ctx.ranges[0].time_range = (
1341 Timestamp::new_microsecond(1_000_000),
1342 Timestamp::new_microsecond(2_000_000),
1343 );
1344 let key = build_range_cache_key(&ctx, &part_range, None).unwrap();
1345 assert!(!key.scan.time_filters().is_empty());
1346 }
1347
1348 fn ms_ts(v: i64) -> Timestamp {
1349 Timestamp::new_millisecond(v)
1350 }
1351
1352 fn implied_ms(expr: Expr) -> Option<TimestampRange> {
1353 implied_time_range_from_exprs("ts", TimeUnit::Millisecond, &[&expr])
1354 }
1355
1356 #[test]
1357 fn test_implied_time_range_supported_exprs() {
1358 for (expr, expected) in [
1359 (
1360 col("ts").gt_eq(ts_lit(1000)),
1361 Some(TimestampRange::from_start(ms_ts(1000))),
1362 ),
1363 (
1364 col("ts").gt(ts_lit(1000)),
1365 Some(TimestampRange::from_start(ms_ts(1001))),
1366 ),
1367 (
1368 col("ts").lt_eq(ts_lit(2000)),
1369 Some(TimestampRange::until_end(ms_ts(2000), true)),
1370 ),
1371 (
1372 col("ts").lt(ts_lit(2000)),
1373 Some(TimestampRange::until_end(ms_ts(2000), false)),
1374 ),
1375 (
1376 col("ts").eq(ts_lit(1500)),
1377 Some(TimestampRange::single(ms_ts(1500))),
1378 ),
1379 (
1380 ts_lit(1000).lt_eq(col("ts")),
1381 Some(TimestampRange::from_start(ms_ts(1000))),
1382 ),
1383 (
1384 col("ts").between(ts_lit(1000), ts_lit(2000)),
1385 Some(TimestampRange::new_inclusive(
1386 Some(ms_ts(1000)),
1387 Some(ms_ts(2000)),
1388 )),
1389 ),
1390 (
1391 col("ts")
1392 .gt_eq(ts_lit(1000))
1393 .and(col("ts").lt(ts_lit(2000))),
1394 TimestampRange::with_unit(1000, 2000, TimeUnit::Millisecond),
1395 ),
1396 (
1397 col("ts")
1398 .gt_eq(ts_lit(1000))
1399 .and(col("ts").lt(ts_lit(5000)))
1400 .and(col("ts").lt_eq(ts_lit(3000))),
1401 TimestampRange::with_unit(1000, 3001, TimeUnit::Millisecond),
1402 ),
1403 ] {
1404 assert_eq!(implied_ms(expr), expected);
1405 }
1406
1407 assert_eq!(
1408 implied_time_range_from_exprs("ts", TimeUnit::Millisecond, &[]),
1409 None
1410 );
1411 }
1412
1413 #[test]
1414 fn test_implied_time_range_unsupported_exprs() {
1415 let not_between = Expr::Between(Between {
1416 expr: Box::new(col("ts")),
1417 negated: true,
1418 low: Box::new(ts_lit(1000)),
1419 high: Box::new(ts_lit(2000)),
1420 });
1421
1422 for expr in [
1423 not_between,
1424 col("ts").gt_eq(ts_lit(1000)).or(col("ts").lt(ts_lit(500))),
1425 Expr::Not(Box::new(col("ts").gt_eq(ts_lit(1000)))),
1426 col("ts").in_list(vec![ts_lit(1000), ts_lit(2000)], false),
1427 col("ts").gt_eq(col("other")),
1428 col("other_ts").gt_eq(ts_lit(1000)),
1429 ] {
1430 assert!(implied_ms(expr).is_none());
1431 }
1432 }
1433
1434 #[test]
1435 fn test_implied_time_range_unit_conversion() {
1436 let second_1 = lit(ScalarValue::TimestampSecond(Some(1), None));
1437 let ns_1500 = lit(ScalarValue::TimestampNanosecond(Some(1_500_000_000), None));
1438 let ns_1500_5 = lit(ScalarValue::TimestampNanosecond(Some(1_500_500_000), None));
1439
1440 for (expr, expected) in [
1441 (
1442 col("ts").gt_eq(second_1.clone()),
1443 Some(TimestampRange::from_start(ms_ts(1000))),
1444 ),
1445 (
1446 col("ts").lt_eq(second_1),
1447 Some(TimestampRange::until_end(ms_ts(1000), true)),
1448 ),
1449 (
1450 col("ts").eq(ns_1500),
1451 Some(TimestampRange::single(ms_ts(1500))),
1452 ),
1453 (col("ts").eq(ns_1500_5.clone()), None),
1454 (
1455 col("ts").gt_eq(ns_1500_5.clone()),
1456 Some(TimestampRange::from_start(ms_ts(1501))),
1457 ),
1458 (
1459 col("ts").lt_eq(ns_1500_5.clone()),
1460 Some(TimestampRange::until_end(ms_ts(1500), true)),
1461 ),
1462 (
1463 col("ts").gt(ns_1500_5.clone()),
1464 Some(TimestampRange::from_start(ms_ts(1501))),
1465 ),
1466 (
1467 col("ts").lt(ns_1500_5),
1468 Some(TimestampRange::until_end(ms_ts(1501), false)),
1469 ),
1470 ] {
1471 assert_eq!(implied_ms(expr), expected);
1472 }
1473 }
1474
1475 #[test]
1476 fn test_selector_after_merge_changes_fingerprint() {
1477 let ordinary = test_scan_fingerprint(
1478 vec!["k0 = 'foo'".to_string()],
1479 vec![],
1480 Some(TimeSeriesRowSelector::LastRow { after_merge: false }),
1481 true,
1482 0,
1483 );
1484 let after_merge = test_scan_fingerprint(
1485 vec!["k0 = 'foo'".to_string()],
1486 vec![],
1487 Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
1488 true,
1489 0,
1490 );
1491
1492 assert_ne!(ordinary, after_merge);
1493 }
1494
1495 #[test]
1496 fn test_true_selector_after_merge_is_preserved_by_fingerprint_transforms() {
1497 let normalized =
1498 test_scan_fingerprint(vec!["k0 = 'foo'".to_string()], vec![], None, true, 0);
1499
1500 assert!(normalized.time_filters().is_empty());
1501
1502 let fingerprint = test_scan_fingerprint(
1503 vec!["k0 = 'foo'".to_string()],
1504 vec!["ts >= 1000".to_string()],
1505 Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
1506 true,
1507 7,
1508 );
1509
1510 let reset = fingerprint.without_time_filters();
1511 let candidate = fingerprint.for_candidate_series();
1512 let series_data = fingerprint.for_series_data(SeriesRange::new(0, 1).unwrap());
1513
1514 assert_eq!(reset.read_columns(), fingerprint.read_columns());
1515 assert_eq!(reset.read_column_types(), fingerprint.read_column_types());
1516 assert_eq!(reset.filters(), fingerprint.filters());
1517 assert!(reset.time_filters().is_empty());
1518 assert_eq!(reset.series_row_selector, fingerprint.series_row_selector);
1519 assert_eq!(
1520 fingerprint.series_row_selector,
1521 Some(TimeSeriesRowSelector::LastRow { after_merge: true })
1522 );
1523 assert_eq!(
1524 candidate.series_row_selector,
1525 fingerprint.series_row_selector
1526 );
1527 assert_eq!(
1528 series_data.series_row_selector,
1529 fingerprint.series_row_selector
1530 );
1531 assert_eq!(reset.append_mode, fingerprint.append_mode);
1532 assert_eq!(reset.filter_deleted, fingerprint.filter_deleted);
1533 assert_eq!(reset.merge_mode, fingerprint.merge_mode);
1534 assert_eq!(
1535 reset.partition_expr_version,
1536 fingerprint.partition_expr_version
1537 );
1538 }
1539
1540 fn test_schema() -> Arc<datatypes::arrow::datatypes::Schema> {
1541 use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field, Schema};
1542
1543 Arc::new(Schema::new(vec![Field::new(
1544 "value",
1545 ArrowDataType::Int64,
1546 false,
1547 )]))
1548 }
1549
1550 fn make_batch(values: &[i64]) -> RecordBatch {
1551 use datatypes::arrow::array::Int64Array;
1552
1553 RecordBatch::try_new(
1554 test_schema(),
1555 vec![Arc::new(Int64Array::from(values.to_vec()))],
1556 )
1557 .unwrap()
1558 }
1559
1560 fn make_large_binary_batch(rows: usize, bytes_per_row: usize) -> RecordBatch {
1561 use datatypes::arrow::array::BinaryArray;
1562 use datatypes::arrow::datatypes::{DataType as ArrowDataType, Field, Schema};
1563
1564 let schema = Arc::new(Schema::new(vec![Field::new(
1565 "value",
1566 ArrowDataType::Binary,
1567 false,
1568 )]));
1569 let payloads: Vec<_> = (0..rows)
1570 .map(|i| vec![(i % 256) as u8; bytes_per_row])
1571 .collect();
1572 let values = payloads.iter().map(Vec::as_slice).collect::<Vec<_>>();
1573
1574 RecordBatch::try_new(schema, vec![Arc::new(BinaryArray::from_vec(values))]).unwrap()
1575 }
1576
1577 fn source_stream(batches: Vec<RecordBatch>) -> BoxedRecordBatchStream {
1578 Box::pin(futures::stream::iter(batches.into_iter().map(Ok)))
1579 }
1580
1581 async fn wait_cached(
1582 strategy: &CacheStrategy,
1583 key: &RangeScanCacheKey,
1584 ) -> Arc<RangeScanCacheValue> {
1585 tokio::time::timeout(std::time::Duration::from_secs(5), async {
1586 loop {
1587 if let Some(value) = strategy.get_range_result(key) {
1588 return value;
1589 }
1590 tokio::task::yield_now().await;
1591 }
1592 })
1593 .await
1594 .expect("range result was not published")
1595 }
1596
1597 #[tokio::test]
1598 async fn test_merged_key_covers_files_and_snapshot_independently_of_order() {
1599 let (context, range) = new_stream_context(
1600 vec![col("k0").eq(lit("a"))],
1601 None,
1602 (
1603 Timestamp::new_millisecond(0),
1604 Timestamp::new_millisecond(10),
1605 ),
1606 )
1607 .await;
1608 let mut context = Arc::new(context);
1609 let first = build_range_cache_key(&context, &range, None).unwrap();
1610 let file = sst_file_handle_with_file_id(FileId::random(), 0, 10);
1611 Arc::get_mut(&mut context).unwrap().input.files.push(file);
1612 Arc::get_mut(&mut context).unwrap().ranges[0]
1613 .indices
1614 .push(SourceIndex {
1615 index: 1,
1616 num_row_groups: 1,
1617 });
1618 Arc::get_mut(&mut context).unwrap().ranges[0]
1619 .row_group_indices
1620 .push(RowGroupIndex {
1621 index: 1,
1622 row_group_index: -1,
1623 });
1624 let whole = build_range_cache_key(&context, &range, None).unwrap();
1625 assert_eq!(whole.row_groups.len(), 2);
1626 assert!(
1627 whole
1628 .row_groups
1629 .iter()
1630 .any(|(_, row_group)| *row_group == -1)
1631 );
1632 assert_ne!(first, whole);
1633 Arc::get_mut(&mut context).unwrap().ranges[0]
1634 .row_group_indices
1635 .swap(0, 1);
1636 let reordered = build_range_cache_key(&context, &range, None).unwrap();
1637 assert_eq!(whole, reordered);
1638 Arc::get_mut(&mut context).unwrap().input.snapshot_sequence = Some(42);
1639 let snapshot = build_range_cache_key(&context, &range, None).unwrap();
1640 assert_ne!(snapshot, reordered);
1641 assert_eq!(snapshot.scan.stage, RangeScanStage::Data);
1642 let sources = snapshot.scan.file_reads.as_ref().unwrap();
1643 assert_eq!(sources.len(), 2);
1644 assert!(
1645 sources
1646 .iter()
1647 .all(|source| source.snapshot_sequence == Some(42))
1648 );
1649 }
1650
1651 fn new_partition_pruner(
1652 context: &Arc<StreamContext>,
1653 range: &PartitionRange,
1654 ) -> Arc<PartitionPruner> {
1655 let pruner = Arc::new(Pruner::new(context.clone(), 1));
1656 pruner.add_partition_ranges(std::slice::from_ref(range));
1657 Arc::new(PartitionPruner::new(pruner, std::slice::from_ref(range)))
1658 }
1659
1660 async fn sst_range_stream(
1661 context: &Arc<StreamContext>,
1662 range: &PartitionRange,
1663 metrics: &PartitionMetrics,
1664 partition: Arc<PartitionPruner>,
1665 semaphore: Option<Arc<tokio::sync::Semaphore>>,
1666 ) -> BoxedRecordBatchStream {
1667 crate::read::seq_scan::SeqScan::build_flat_partition_range_read(
1668 context,
1669 range,
1670 crate::read::seq_scan::FlatReadMode::Sequential,
1671 metrics,
1672 partition,
1673 semaphore.clone(),
1674 semaphore,
1675 )
1676 .await
1677 .unwrap()
1678 .0
1679 }
1680
1681 fn u64_values(batches: &[RecordBatch], name: &str) -> Vec<Option<u64>> {
1682 batches
1683 .iter()
1684 .flat_map(|batch| {
1685 batch
1686 .column_by_name(name)
1687 .unwrap()
1688 .as_any()
1689 .downcast_ref::<datatypes::arrow::array::UInt64Array>()
1690 .unwrap()
1691 .iter()
1692 })
1693 .collect()
1694 }
1695
1696 async fn write_overlapping_sst_files(
1697 env: &SchedulerEnv,
1698 ) -> (
1699 store_api::metadata::RegionMetadataRef,
1700 Vec<crate::sst::file::FileHandle>,
1701 ) {
1702 use datatypes::arrow::array::UInt64Array;
1703 use parquet::arrow::ArrowWriter;
1704 use parquet::file::metadata::KeyValue;
1705 use parquet::file::properties::WriterProperties;
1706 use store_api::region_request::PathType;
1707
1708 use crate::sst::file::{FileHandle, FileMeta};
1709 use crate::sst::parquet::PARQUET_METADATA_KEY;
1710 use crate::test_util::sst_util::{
1711 new_record_batch_with_custom_sequence, sst_region_metadata,
1712 };
1713
1714 let metadata = Arc::new(sst_region_metadata());
1715 let mut files = Vec::new();
1716 for sequence in [1, 2] {
1717 let props = WriterProperties::builder()
1718 .set_key_value_metadata(Some(vec![KeyValue::new(
1719 PARQUET_METADATA_KEY.to_string(),
1720 metadata.to_json().unwrap(),
1721 )]))
1722 .build();
1723 let first = new_record_batch_with_custom_sequence(&["a", "x"], 0, 2, sequence);
1724 let mut bytes = Vec::new();
1725 let mut writer = ArrowWriter::try_new(&mut bytes, first.schema(), Some(props)).unwrap();
1726 for start in [0, 2] {
1727 let batch =
1728 new_record_batch_with_custom_sequence(&["a", "x"], start, start + 2, sequence);
1729 let mut columns = batch.columns().to_vec();
1730 columns[2] = Arc::new(UInt64Array::from(vec![
1731 sequence * 100 + start as u64,
1732 sequence * 100 + start as u64 + 1,
1733 ]));
1734 writer
1735 .write(&RecordBatch::try_new(batch.schema(), columns).unwrap())
1736 .unwrap();
1737 writer.flush().unwrap();
1738 }
1739 writer.close().unwrap();
1740 let file = FileHandle::new(
1741 FileMeta {
1742 region_id: metadata.region_id,
1743 file_id: FileId::random(),
1744 time_range: (Timestamp::new_millisecond(0), Timestamp::new_millisecond(3)),
1745 file_size: bytes.len() as u64,
1746 num_rows: 4,
1747 num_row_groups: 2,
1748 preserve_row_sequence: true,
1749 ..Default::default()
1750 },
1751 crate::test_util::new_noop_file_purger(),
1752 );
1753 env.access_layer
1754 .object_store()
1755 .write(&file.file_path("", PathType::Bare), bytes)
1756 .await
1757 .unwrap();
1758 files.push(file);
1759 }
1760 (metadata, files)
1761 }
1762
1763 #[tokio::test]
1764 async fn test_range_cache_keeps_cross_sst_dedup_and_task_membership() {
1765 use store_api::storage::consts::SEQUENCE_COLUMN_NAME;
1766
1767 for capacity in [0, 1024, 1024 * 1024] {
1768 let env = SchedulerEnv::new().await;
1769 let (metadata, files) = write_overlapping_sst_files(&env).await;
1770 let strategy = CacheStrategy::EnableAll(Arc::new(
1771 CacheManager::builder()
1772 .range_result_cache_size(capacity)
1773 .build(),
1774 ));
1775 for (case, (max_sequence, last_row)) in [
1776 (1, false),
1777 (2, false),
1778 (1, false),
1779 (2, true),
1780 (2, false),
1781 (1, true),
1782 ]
1783 .into_iter()
1784 .enumerate()
1785 {
1786 let mapper =
1787 FlatProjectionMapper::new(&metadata, 0..metadata.column_metadatas.len())
1788 .unwrap();
1789 let input = ScanInput::builder(env.access_layer.clone(), mapper)
1790 .with_files(files.clone())
1791 .with_cache(strategy.clone())
1792 .with_append_mode(false)
1793 .with_predicate(
1794 PredicateGroup::new(&metadata, &[col("tag_0").eq(lit("a"))]).unwrap(),
1795 )
1796 .with_batch_size(1)
1797 .with_snapshot_sequence(Some(max_sequence))
1798 .with_series_row_selector(
1799 last_row.then_some(TimeSeriesRowSelector::LastRow { after_merge: true }),
1800 )
1801 .with_sequence_range(Some(SequenceRange::GtLtEq {
1802 min: 0,
1803 max: max_sequence,
1804 }))
1805 .build();
1806 let context = Arc::new(StreamContext::seq_scan_ctx(input));
1807 assert_eq!(context.ranges.len(), 1);
1808 assert_eq!(context.ranges[0].row_group_indices.len(), 2);
1809 let range = context.ranges[0].new_partition_range(0);
1810 let (_, metrics) = test_cache_context(&strategy);
1811 for run in 0..2 {
1812 let partition = new_partition_pruner(&context, &range);
1813 let semaphore = Arc::new(tokio::sync::Semaphore::new(1));
1814 let batches = tokio::time::timeout(std::time::Duration::from_secs(5), async {
1815 let mut stream = sst_range_stream(
1816 &context,
1817 &range,
1818 &metrics,
1819 partition,
1820 Some(semaphore.clone()),
1821 )
1822 .await;
1823 if capacity > 0 && case == 1 && run == 0 {
1825 assert!(build_range_cache_key(&context, &range, None).is_some());
1826 let (_, follower_metrics) = test_cache_context(&strategy);
1827 let follower = sst_range_stream(
1828 &context,
1829 &range,
1830 &follower_metrics,
1831 new_partition_pruner(&context, &range),
1832 Some(semaphore.clone()),
1833 )
1834 .await;
1835 let first = stream.try_next().await.unwrap().unwrap();
1836 assert!(first.num_rows() < 4);
1837 drop(stream);
1838 let batches = follower.try_collect::<Vec<_>>().await.unwrap();
1839 assert!(batches.len() > 1);
1840 assert_eq!(batches.first().unwrap(), &first);
1841 assert_eq!(semaphore.available_permits(), 1);
1842 let owner: serde_json::Value =
1843 serde_json::from_str(&format!("{metrics:?}")).unwrap();
1844 let follower: serde_json::Value =
1845 serde_json::from_str(&format!("{follower_metrics:?}")).unwrap();
1846 assert!(owner["metrics"]["num_file_ranges"].as_u64().unwrap() > 0);
1847 assert_eq!(follower["metrics"]["num_file_ranges"], 0);
1848 batches
1849 } else {
1850 stream.try_collect::<Vec<_>>().await.unwrap()
1851 }
1852 })
1853 .await
1854 .unwrap();
1855 assert!(
1856 u64_values(&batches, SEQUENCE_COLUMN_NAME)
1857 .iter()
1858 .all(|sequence| *sequence == Some(max_sequence))
1859 );
1860 let values = u64_values(&batches, "field_0")
1861 .into_iter()
1862 .map(Option::unwrap)
1863 .collect::<Vec<_>>();
1864 let expected = if last_row {
1865 vec![max_sequence * 100 + 3]
1866 } else {
1867 (0..4).map(|offset| max_sequence * 100 + offset).collect()
1868 };
1869 assert_eq!(values, expected);
1870 }
1871 if capacity > 1024 {
1872 let key = build_range_cache_key(&context, &range, None).unwrap();
1873 let value = wait_cached(&strategy, &key).await;
1874 assert!(value.cached_batches().next().is_some());
1875 }
1876 }
1877 }
1878 }
1879
1880 fn range_cache_strategy(capacity: u64) -> CacheStrategy {
1881 CacheStrategy::EnableAll(Arc::new(
1882 CacheManager::builder()
1883 .range_result_cache_size(capacity)
1884 .build(),
1885 ))
1886 }
1887
1888 #[tokio::test]
1889 async fn test_shared_slices_compact_before_budget_fallback() {
1890 for (capacity, count) in [(64 * 1024, 16), (16 * 1024 * 1024, 128)] {
1891 let strategy = range_cache_strategy(capacity);
1892 let (key, metrics) = test_cache_context(&strategy);
1893 let backing = make_large_binary_batch(1024, 256);
1894 let mut batches: Vec<_> = (0..count).map(|i| backing.slice(i, 1)).collect();
1895 batches.insert(count / 2, backing.slice(count / 2, 0));
1896 let input = batches.clone();
1897 let (mut owner, _) =
1898 get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
1899 Ok((source_stream(input), 1))
1900 })
1901 .await
1902 .unwrap();
1903 let (slow, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
1904 panic!("reader must share sliced source")
1905 })
1906 .await
1907 .unwrap();
1908 for expected in &batches[..count * 3 / 4] {
1909 assert_eq!(&owner.try_next().await.unwrap().unwrap(), expected);
1910 }
1911 let (late, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
1912 panic!("compacted source must still admit readers")
1913 })
1914 .await
1915 .unwrap();
1916 drop(owner);
1917 assert_eq!(slow.try_collect::<Vec<_>>().await.unwrap(), batches);
1918 assert_eq!(late.try_collect::<Vec<_>>().await.unwrap(), batches);
1919 let value = wait_cached(&strategy, &key).await;
1920 assert!(value.estimated_size() < 64 * 1024);
1921 assert_eq!(
1922 get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
1923 panic!("cached result")
1924 })
1925 .await
1926 .unwrap()
1927 .0
1928 .try_collect::<Vec<_>>()
1929 .await
1930 .unwrap(),
1931 batches
1932 );
1933 }
1934 }
1935
1936 #[tokio::test]
1937 async fn test_failed_shared_stream_does_not_poison_a_later_request() {
1938 let pool_a: Arc<dyn MemoryPool> =
1939 Arc::new(datafusion::execution::memory_pool::UnboundedMemoryPool::default());
1940 let pool_b: Arc<dyn MemoryPool> =
1941 Arc::new(datafusion::execution::memory_pool::UnboundedMemoryPool::default());
1942 let scope_a = RangeCacheScope::for_memory_pool(&pool_a);
1943 let scope_b = RangeCacheScope::for_memory_pool(&pool_b);
1944 let strategy = test_cache_strategy();
1945 let (key, metrics) = test_cache_context(&strategy);
1946 let (mut failed, _) = get_or_load_range_result_in_scope(
1947 &strategy,
1948 key.clone(),
1949 &metrics,
1950 scope_a,
1951 || async {
1952 let source = futures::stream::iter(vec![
1953 Ok(make_batch(&[7])),
1954 Err(crate::error::UnexpectedSnafu {
1955 reason: "transient source error",
1956 }
1957 .build()),
1958 ]);
1959 Ok((Box::pin(source) as BoxedRecordBatchStream, 3))
1960 },
1961 )
1962 .await
1963 .unwrap();
1964 let (mut existing, _) = get_or_load_range_result_in_scope(
1966 &strategy,
1967 key.clone(),
1968 &metrics,
1969 scope_a,
1970 || async { panic!("existing reader must share the source") },
1971 )
1972 .await
1973 .unwrap();
1974 let (other_pool, _) = get_or_load_range_result_in_scope(
1975 &strategy,
1976 key.clone(),
1977 &metrics,
1978 scope_b,
1979 || async { Ok((source_stream(vec![make_batch(&[8])]), 1)) },
1980 )
1981 .await
1982 .unwrap();
1983 assert_eq!(failed.try_next().await.unwrap().unwrap(), make_batch(&[7]));
1984 let error = failed.try_next().await.unwrap_err();
1985 let crate::error::Error::LoadRangeResult { source, .. } = error else {
1986 panic!("expected shared source error, got {error:?}");
1987 };
1988 assert!(
1989 matches!(source.as_ref(), crate::error::Error::Unexpected { reason, .. }
1990 if reason == "transient source error")
1991 );
1992 let expected = make_batch(&[1, 2, 3]);
1993 let (mut retry, _) = get_or_load_range_result_in_scope(
1994 &strategy,
1995 key.clone(),
1996 &metrics,
1997 scope_a,
1998 || async {
1999 Ok((
2000 Box::pin(futures::stream::iter([Ok(expected.clone())]))
2001 as BoxedRecordBatchStream,
2002 3,
2003 ))
2004 },
2005 )
2006 .await
2007 .unwrap();
2008 assert_eq!(retry.try_next().await.unwrap().unwrap(), expected);
2009 assert_eq!(
2010 existing.try_next().await.unwrap().unwrap(),
2011 make_batch(&[7])
2012 );
2013 let error = existing.try_next().await.unwrap_err();
2014 let crate::error::Error::LoadRangeResult { source, .. } = error else {
2015 panic!("expected shared source error, got {error:?}");
2016 };
2017 assert!(
2018 matches!(source.as_ref(), crate::error::Error::Unexpected { reason, .. }
2019 if reason == "transient source error")
2020 );
2021 assert_eq!(
2022 other_pool.try_collect::<Vec<_>>().await.unwrap(),
2023 vec![make_batch(&[8])]
2024 );
2025 wait_cached(&strategy, &key).await;
2026 let (cached, _) =
2027 get_or_load_range_result_in_scope(&strategy, key, &metrics, scope_a, || async {
2028 panic!("completed result must be reusable across pools")
2029 })
2030 .await
2031 .unwrap();
2032 assert_eq!(
2033 cached.try_collect::<Vec<_>>().await.unwrap(),
2034 vec![make_batch(&[8])]
2035 );
2036 }
2037
2038 #[tokio::test]
2039 async fn test_shared_ranges_build_once_before_consumers_poll() {
2040 use std::sync::atomic::{AtomicUsize, Ordering};
2041 use std::time::Duration;
2042
2043 for enabled in [false, true] {
2044 let strategy = range_cache_strategy(if enabled { 1024 * 1024 } else { 0 });
2045 let (key, metrics) = test_cache_context(&strategy);
2046 let builds = AtomicUsize::new(0);
2047 let gate = Arc::new(tokio::sync::Semaphore::new(0));
2048 let produced = Arc::new(AtomicUsize::new(0));
2049 let expected: Vec<_> = (0..32).map(|n| make_batch(&[n])).collect();
2050 let mut requests = (0..8)
2051 .map(|_| {
2052 Box::pin(get_or_load_range_result(
2053 &strategy,
2054 key.clone(),
2055 &metrics,
2056 || async {
2057 builds.fetch_add(1, Ordering::Relaxed);
2058 let produced = produced.clone();
2059 let gate = gate.clone();
2060 let stream = Box::pin(try_stream! {
2061 gate.acquire().await.unwrap().forget();
2062 for n in 0..32 {
2063 tokio::task::yield_now().await;
2064 produced.fetch_add(1, Ordering::Relaxed);
2065 yield make_batch(&[n]);
2066 }
2067 });
2068 Ok((stream as BoxedRecordBatchStream, 3))
2069 },
2070 ))
2071 })
2072 .collect::<Vec<_>>();
2073 let streams = tokio::time::timeout(
2076 Duration::from_secs(5),
2077 futures::future::join_all(&mut requests),
2078 )
2079 .await
2080 .unwrap();
2081 assert_eq!(builds.load(Ordering::Relaxed), if enabled { 1 } else { 8 });
2082 gate.add_permits(8);
2083 let results = futures::future::join_all(
2084 streams
2085 .into_iter()
2086 .map(|stream| stream.unwrap().0.try_collect::<Vec<_>>()),
2087 )
2088 .await;
2089 for result in results {
2090 assert_eq!(result.unwrap(), expected);
2091 }
2092 assert_eq!(
2093 produced.load(Ordering::Relaxed),
2094 if enabled { 32 } else { 256 }
2095 );
2096 }
2097 }
2098
2099 #[tokio::test]
2100 async fn test_oversized_shared_range_keeps_existing_readers_and_declines_new_readers() {
2101 use std::sync::atomic::{AtomicUsize, Ordering};
2102
2103 let reads = Arc::new(AtomicUsize::new(0));
2104 let count = reads.clone();
2105 let strategy = range_cache_strategy(1024);
2106 let (key, metrics) = test_cache_context(&strategy);
2107 let expected = vec![
2108 make_batch(&[1]),
2109 make_batch(&[2, 3]),
2110 make_batch(&vec![5; 256]),
2111 make_batch(&[4]),
2112 ];
2113 let input = expected.clone();
2114 let (mut owner, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2115 let source = futures::stream::iter(input.into_iter().map(move |batch| {
2116 count.fetch_add(1, Ordering::Relaxed);
2117 Ok(batch)
2118 }));
2119 Ok((Box::pin(source) as BoxedRecordBatchStream, 1))
2120 })
2121 .await
2122 .unwrap();
2123 assert_eq!(reads.load(Ordering::Relaxed), 0);
2124 let limiter = strategy.range_result_cache().unwrap().retention_memory();
2125 assert_eq!(limiter.available_permits(), 1);
2126 assert!(strategy.get_range_result(&key).is_none());
2127 let (mut follower, _) =
2128 get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2129 panic!("existing follower must share")
2130 })
2131 .await
2132 .unwrap();
2133 assert_eq!(owner.try_next().await.unwrap().unwrap(), expected[0]);
2134 assert_eq!(owner.try_next().await.unwrap().unwrap(), expected[1]);
2135 assert_eq!(owner.try_next().await.unwrap().unwrap(), expected[2]);
2136 let (late, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2137 Ok((source_stream(vec![make_batch(&[9])]), 1))
2138 })
2139 .await
2140 .unwrap();
2141 assert_eq!(
2142 late.try_collect::<Vec<_>>().await.unwrap(),
2143 vec![make_batch(&[9])]
2144 );
2145 assert_eq!(limiter.available_permits(), 0);
2147 assert_eq!(follower.try_next().await.unwrap().unwrap(), expected[0]);
2148 assert_eq!(limiter.available_permits(), 0);
2149 assert_eq!(follower.try_next().await.unwrap().unwrap(), expected[1]);
2150 assert_eq!(limiter.available_permits(), 1);
2151 assert_eq!(
2152 follower.try_collect::<Vec<_>>().await.unwrap(),
2153 expected[2..]
2154 );
2155 assert_eq!(
2156 owner.try_collect::<Vec<_>>().await.unwrap(),
2157 vec![make_batch(&[4])]
2158 );
2159 assert!(strategy.get_range_result(&key).is_none());
2160 assert_eq!(reads.load(Ordering::Relaxed), 4);
2161 assert_eq!(limiter.available_permits(), 1);
2162 let (retry, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2163 Ok((source_stream(vec![]), 1))
2164 })
2165 .await
2166 .unwrap();
2167 assert!(retry.try_collect::<Vec<_>>().await.unwrap().is_empty());
2168 wait_cached(&strategy, &key).await;
2169 }
2170
2171 #[tokio::test]
2172 async fn test_dropping_reader_releases_unpolled_or_pending_source() {
2173 for polled in [false, true] {
2174 let strategy = test_cache_strategy();
2175 let (key, metrics) = test_cache_context(&strategy);
2176 let source_gate = Arc::new(tokio::sync::Semaphore::new(1));
2177 let permit = source_gate.clone().acquire_owned().await.unwrap();
2178 let (mut stream, _) =
2179 get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2180 let source = Box::pin(try_stream! {
2181 let _permit = permit;
2182 std::future::pending::<()>().await;
2183 yield make_batch(&[1]);
2184 });
2185 Ok((source as BoxedRecordBatchStream, 1))
2186 })
2187 .await
2188 .unwrap();
2189 assert_eq!(source_gate.available_permits(), 0);
2190 if polled {
2191 let next = stream.try_next();
2192 tokio::pin!(next);
2193 assert!(futures::poll!(&mut next).is_pending());
2194 }
2195 assert_eq!(source_gate.available_permits(), 0);
2196 drop(stream);
2197 assert_eq!(source_gate.available_permits(), 1);
2198 assert!(strategy.get_range_result(&key).is_none());
2199 let (retry, _) = get_or_load_range_result(&strategy, key, &metrics, || async {
2200 Ok((source_stream(vec![make_batch(&[2])]), 1))
2201 })
2202 .await
2203 .unwrap();
2204 assert_eq!(
2205 retry.try_collect::<Vec<_>>().await.unwrap(),
2206 vec![make_batch(&[2])]
2207 );
2208 }
2209 }
2210
2211 #[tokio::test]
2212 async fn test_cancelling_initial_reader_preserves_followers_and_releases_memory() {
2213 let strategy = test_cache_strategy();
2214 let (key, metrics) = test_cache_context(&strategy);
2215 let limiter = strategy.range_result_cache().unwrap().retention_memory();
2216 let initial = limiter.available_permits();
2217 let gate = Arc::new(tokio::sync::Notify::new());
2218 let producer_gate = gate.clone();
2219 let (mut owner, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2220 let source = Box::pin(try_stream! {
2221 yield make_batch(&[1]);
2222 producer_gate.notified().await;
2223 yield make_batch(&[2]);
2224 });
2225 Ok((source as BoxedRecordBatchStream, 1))
2226 })
2227 .await
2228 .unwrap();
2229 let (mut follower, _) =
2230 get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2231 panic!("follower must share")
2232 })
2233 .await
2234 .unwrap();
2235 assert_eq!(limiter.available_permits(), initial);
2236 assert_eq!(owner.try_next().await.unwrap().unwrap(), make_batch(&[1]));
2237 assert!(limiter.available_permits() < initial);
2238 drop(owner);
2239 assert_eq!(
2240 follower.try_next().await.unwrap().unwrap(),
2241 make_batch(&[1])
2242 );
2243 gate.notify_one();
2244 assert_eq!(
2245 follower.try_next().await.unwrap().unwrap(),
2246 make_batch(&[2])
2247 );
2248 drop(follower);
2250 assert_eq!(limiter.available_permits(), initial);
2251 assert!(strategy.get_range_result(&key).is_none());
2252 }
2253
2254 #[tokio::test]
2255 async fn test_completed_stream_does_not_wait_for_cache_compaction() {
2256 let strategy = test_cache_strategy();
2257 let (key, metrics) = test_cache_context(&strategy);
2258 let limiter = strategy.range_concat_memory().unwrap();
2259 let permit = limiter
2260 .acquire(limiter.available_permits() * limiter.permit_bytes())
2261 .await
2262 .unwrap();
2263 let expected = vec![make_batch(&[1]), make_batch(&[2])];
2264 let input = expected.clone();
2265 let (stream, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2266 Ok((source_stream(input), 1))
2267 })
2268 .await
2269 .unwrap();
2270 assert_eq!(stream.try_collect::<Vec<_>>().await.unwrap(), expected);
2271 tokio::time::timeout(std::time::Duration::from_secs(5), async {
2272 while limiter.waited_acquires() == 0 {
2273 tokio::task::yield_now().await;
2274 }
2275 })
2276 .await
2277 .expect("background compaction did not reach its memory wait");
2278 assert!(strategy.get_range_result(&key).is_none());
2279 let (joined, _) = get_or_load_range_result(&strategy, key.clone(), &metrics, || async {
2280 panic!("finalizing scan must remain discoverable")
2281 })
2282 .await
2283 .unwrap();
2284 assert_eq!(joined.try_collect::<Vec<_>>().await.unwrap(), expected);
2285 drop(permit);
2286 wait_cached(&strategy, &key).await;
2287 }
2288
2289 #[tokio::test]
2290 async fn test_range_load_compacts_at_thresholds_and_keeps_tail() {
2291 let row_batch = make_batch(&vec![1; DEFAULT_READ_BATCH_SIZE / 2 + 1]);
2292 let byte_batch = make_large_binary_batch(1024, 4097);
2294 assert!(byte_batch.num_rows() * 2 <= DEFAULT_READ_BATCH_SIZE);
2295 assert!(byte_batch.get_array_memory_size() * 2 > RANGE_CACHE_COMPACT_THRESHOLD_BYTES);
2296 assert!(row_batch.get_array_memory_size() * 2 < RANGE_CACHE_COMPACT_THRESHOLD_BYTES);
2297 for batches in [
2298 vec![],
2299 vec![make_batch(&[1, 2]), make_batch(&[3]), make_batch(&[4, 5])],
2300 vec![make_batch(&[1, 2, 3])],
2301 vec![row_batch.clone(), row_batch.clone(), row_batch],
2302 vec![byte_batch.clone(), byte_batch.clone(), byte_batch],
2303 ] {
2304 let input_size = batches
2305 .iter()
2306 .map(RecordBatch::get_array_memory_size)
2307 .sum::<usize>();
2308 let strategy = range_cache_strategy((input_size * 2).max(1024 * 1024) as u64);
2309 let (key, metrics) = test_cache_context(&strategy);
2310 let input = batches.clone();
2311 let expected_batch_size = 7;
2312 let (stream, batch_size) =
2313 get_or_load_range_result(&strategy, key.clone(), &metrics, || async move {
2314 Ok((source_stream(input), expected_batch_size))
2315 })
2316 .await
2317 .unwrap();
2318 assert_eq!(batch_size, expected_batch_size);
2319 assert_eq!(stream.try_collect::<Vec<_>>().await.unwrap(), batches);
2320 let value = wait_cached(&strategy, &key).await;
2321 let expected_lengths = if batches.is_empty() {
2322 vec![]
2323 } else if batches.iter().map(RecordBatch::num_rows).sum::<usize>() <= 5 {
2324 vec![batches.iter().map(RecordBatch::num_rows).collect()]
2325 } else {
2326 vec![
2327 vec![batches[0].num_rows(), batches[1].num_rows()],
2328 vec![batches[2].num_rows()],
2329 ]
2330 };
2331 assert_eq!(
2332 value
2333 .cached_batches()
2334 .map(|(_, lengths)| lengths.to_vec())
2335 .collect::<Vec<_>>(),
2336 expected_lengths
2337 );
2338 assert_eq!(
2339 value.estimated_batches_size(),
2340 value
2341 .cached_batches()
2342 .map(|(batch, _)| batch.get_array_memory_size())
2343 .sum::<usize>()
2344 );
2345 let (hit, batch_size) = get_or_load_range_result(&strategy, key, &metrics, || async {
2346 panic!("completed range must be reused without rebuilding");
2347 })
2348 .await
2349 .unwrap();
2350 assert_eq!(batch_size, expected_batch_size);
2351 assert_eq!(hit.try_collect::<Vec<_>>().await.unwrap(), batches);
2352 }
2353 }
2354}