1use std::collections::{BinaryHeap, HashMap, VecDeque};
18use std::fmt;
19use std::pin::Pin;
20use std::sync::{Arc, Mutex};
21use std::task::{Context, Poll};
22use std::time::{Duration, Instant};
23
24use async_stream::try_stream;
25use common_telemetry::tracing;
26use datafusion::physical_plan::metrics::{ExecutionPlanMetricsSet, MetricBuilder, Time};
27use datatypes::arrow::record_batch::RecordBatch;
28use datatypes::timestamp::timestamp_array_to_primitive;
29use futures::Stream;
30use prometheus::IntGauge;
31use smallvec::SmallVec;
32use snafu::ResultExt;
33use store_api::storage::{RegionId, SequenceRange, TimeSeriesRowSelector};
34
35use crate::error::{ComputeArrowSnafu, Result};
36use crate::memtable::MemScanMetrics;
37use crate::metrics::{
38 IN_PROGRESS_SCAN, PRECISE_FILTER_ROWS_TOTAL, READ_BATCHES_RETURN, READ_ROW_GROUPS_TOTAL,
39 READ_ROWS_IN_ROW_GROUP_TOTAL, READ_ROWS_RETURN, READ_STAGE_ELAPSED,
40};
41use crate::read::dedup::{DedupMetrics, DedupMetricsReport};
42use crate::read::flat_merge::{MergeMetrics, MergeMetricsReport};
43use crate::read::pruner::PartitionPruner;
44use crate::read::range::{RangeMeta, RowGroupIndex};
45use crate::read::scan_region::StreamContext;
46use crate::read::{BoxedRecordBatchStream, ScannerMetrics};
47use crate::sst::file::{FileTimeRange, RegionFileId};
48use crate::sst::index::bloom_filter::applier::BloomFilterIndexApplyMetrics;
49use crate::sst::index::fulltext_index::applier::FulltextIndexApplyMetrics;
50use crate::sst::index::inverted_index::applier::InvertedIndexApplyMetrics;
51use crate::sst::parquet::file_range::{FileRange, PreFilterMode};
52use crate::sst::parquet::flat_format::{sequence_column_index, time_index_column_index};
53use crate::sst::parquet::reader::{MetadataCacheMetrics, ReaderFilterMetrics, ReaderMetrics};
54use crate::sst::parquet::row_group::ParquetFetchMetrics;
55use crate::sst::parquet::{DEFAULT_READ_BATCH_SIZE, DEFAULT_ROW_GROUP_SIZE};
56
57#[derive(Default, Clone)]
59pub struct FileScanMetrics {
60 pub num_ranges: usize,
62 pub num_rows: usize,
64 pub build_part_cost: Duration,
66 pub build_reader_cost: Duration,
68 pub scan_cost: Duration,
70}
71
72impl fmt::Debug for FileScanMetrics {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 write!(f, "{{\"build_part_cost\":\"{:?}\"", self.build_part_cost)?;
75
76 if self.num_ranges > 0 {
77 write!(f, ", \"num_ranges\":{}", self.num_ranges)?;
78 }
79 if self.num_rows > 0 {
80 write!(f, ", \"num_rows\":{}", self.num_rows)?;
81 }
82 if !self.build_reader_cost.is_zero() {
83 write!(
84 f,
85 ", \"build_reader_cost\":\"{:?}\"",
86 self.build_reader_cost
87 )?;
88 }
89 if !self.scan_cost.is_zero() {
90 write!(f, ", \"scan_cost\":\"{:?}\"", self.scan_cost)?;
91 }
92
93 write!(f, "}}")
94 }
95}
96
97impl FileScanMetrics {
98 pub(crate) fn merge_from(&mut self, other: &FileScanMetrics) {
100 self.num_ranges += other.num_ranges;
101 self.num_rows += other.num_rows;
102 self.build_part_cost += other.build_part_cost;
103 self.build_reader_cost += other.build_reader_cost;
104 self.scan_cost += other.scan_cost;
105 }
106}
107
108#[derive(Default)]
110pub(crate) struct ScanMetricsSet {
111 prepare_scan_cost: Duration,
113 build_reader_cost: Duration,
115 scan_cost: Duration,
117 yield_cost: Duration,
119 convert_cost: Option<Time>,
121 total_cost: Duration,
123 num_rows: usize,
125 num_batches: usize,
127 num_mem_ranges: usize,
129 num_file_ranges: usize,
131
132 mem_scan_cost: Duration,
135 mem_rows: usize,
137 mem_batches: usize,
139 mem_series: usize,
141 mem_prefilter_cost: Duration,
143 mem_prefilter_rows_filtered: usize,
145
146 build_parts_cost: Duration,
149 sst_scan_cost: Duration,
151 rg_total: usize,
153 rg_fulltext_filtered: usize,
155 rg_inverted_filtered: usize,
157 rg_minmax_filtered: usize,
159 rg_bloom_filtered: usize,
161 rows_before_filter: usize,
163 rows_fulltext_filtered: usize,
165 rows_inverted_filtered: usize,
167 rows_bloom_filtered: usize,
169 rows_precise_filtered: usize,
171 fulltext_index_cache_hit: usize,
173 fulltext_index_cache_miss: usize,
175 inverted_index_cache_hit: usize,
177 inverted_index_cache_miss: usize,
179 bloom_filter_cache_hit: usize,
181 bloom_filter_cache_miss: usize,
183 minmax_cache_hit: usize,
185 minmax_cache_miss: usize,
187 pruner_cache_hit: usize,
189 pruner_cache_miss: usize,
191 pruner_prune_cost: Duration,
193 files_time_range_pruned: usize,
195 num_sst_record_batches: usize,
197 num_sst_batches: usize,
199 num_sst_rows: usize,
201
202 first_poll: Duration,
204
205 num_series_send_timeout: usize,
207 num_series_send_full: usize,
209 num_distributor_rows: usize,
211 num_distributor_batches: usize,
213 distributor_scan_cost: Duration,
215 distributor_yield_cost: Duration,
217 distributor_divider_cost: Duration,
219
220 merge_metrics: MergeMetrics,
222 dedup_metrics: DedupMetrics,
224
225 stream_eof: bool,
227
228 inverted_index_apply_metrics: Option<InvertedIndexApplyMetrics>,
231 bloom_filter_apply_metrics: Option<BloomFilterIndexApplyMetrics>,
233 fulltext_index_apply_metrics: Option<FulltextIndexApplyMetrics>,
235 fetch_metrics: Option<ParquetFetchMetrics>,
237 metadata_cache_metrics: Option<MetadataCacheMetrics>,
239 per_file_metrics: Option<HashMap<RegionFileId, FileScanMetrics>>,
241
242 build_ranges_mem_size: isize,
244 build_ranges_peak_mem_size: isize,
246 num_range_builders: isize,
248 num_peak_range_builders: isize,
250 range_cache_size: usize,
252 range_cache_hit: usize,
254 range_cache_miss: usize,
256}
257
258struct CompareCostReverse<'a> {
261 total_cost: Duration,
262 file_id: RegionFileId,
263 metrics: &'a FileScanMetrics,
264}
265
266impl Ord for CompareCostReverse<'_> {
267 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
268 other.total_cost.cmp(&self.total_cost)
270 }
271}
272
273impl PartialOrd for CompareCostReverse<'_> {
274 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
275 Some(self.cmp(other))
276 }
277}
278
279impl Eq for CompareCostReverse<'_> {}
280
281impl PartialEq for CompareCostReverse<'_> {
282 fn eq(&self, other: &Self) -> bool {
283 self.total_cost == other.total_cost
284 }
285}
286
287impl fmt::Debug for ScanMetricsSet {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 let ScanMetricsSet {
290 prepare_scan_cost,
291 build_reader_cost,
292 scan_cost,
293 yield_cost,
294 convert_cost,
295 total_cost,
296 num_rows,
297 num_batches,
298 num_mem_ranges,
299 num_file_ranges,
300 build_parts_cost,
301 sst_scan_cost,
302 rg_total,
303 rg_fulltext_filtered,
304 rg_inverted_filtered,
305 rg_minmax_filtered,
306 rg_bloom_filtered,
307 rows_before_filter,
308 rows_fulltext_filtered,
309 rows_inverted_filtered,
310 rows_bloom_filtered,
311 rows_precise_filtered,
312 fulltext_index_cache_hit,
313 fulltext_index_cache_miss,
314 inverted_index_cache_hit,
315 inverted_index_cache_miss,
316 bloom_filter_cache_hit,
317 bloom_filter_cache_miss,
318 minmax_cache_hit,
319 minmax_cache_miss,
320 pruner_cache_hit,
321 pruner_cache_miss,
322 pruner_prune_cost,
323 files_time_range_pruned,
324 num_sst_record_batches,
325 num_sst_batches,
326 num_sst_rows,
327 first_poll,
328 num_series_send_timeout,
329 num_series_send_full,
330 num_distributor_rows,
331 num_distributor_batches,
332 distributor_scan_cost,
333 distributor_yield_cost,
334 distributor_divider_cost,
335 merge_metrics,
336 dedup_metrics,
337 stream_eof,
338 mem_scan_cost,
339 mem_rows,
340 mem_batches,
341 mem_series,
342 mem_prefilter_cost,
343 mem_prefilter_rows_filtered,
344 inverted_index_apply_metrics,
345 bloom_filter_apply_metrics,
346 fulltext_index_apply_metrics,
347 fetch_metrics,
348 metadata_cache_metrics,
349 per_file_metrics,
350 build_ranges_mem_size: _,
351 build_ranges_peak_mem_size,
352 num_range_builders: _,
353 num_peak_range_builders,
354 range_cache_size,
355 range_cache_hit,
356 range_cache_miss,
357 } = self;
358
359 write!(
361 f,
362 "{{\"prepare_scan_cost\":\"{prepare_scan_cost:?}\", \
363 \"build_reader_cost\":\"{build_reader_cost:?}\", \
364 \"scan_cost\":\"{scan_cost:?}\", \
365 \"yield_cost\":\"{yield_cost:?}\", \
366 \"total_cost\":\"{total_cost:?}\", \
367 \"num_rows\":{num_rows}, \
368 \"num_batches\":{num_batches}, \
369 \"num_mem_ranges\":{num_mem_ranges}, \
370 \"num_file_ranges\":{num_file_ranges}, \
371 \"build_parts_cost\":\"{build_parts_cost:?}\", \
372 \"sst_scan_cost\":\"{sst_scan_cost:?}\", \
373 \"rg_total\":{rg_total}, \
374 \"rows_before_filter\":{rows_before_filter}, \
375 \"num_sst_record_batches\":{num_sst_record_batches}, \
376 \"num_sst_batches\":{num_sst_batches}, \
377 \"num_sst_rows\":{num_sst_rows}, \
378 \"first_poll\":\"{first_poll:?}\""
379 )?;
380
381 if let Some(time) = convert_cost {
383 let duration = Duration::from_nanos(time.value() as u64);
384 write!(f, ", \"convert_cost\":\"{duration:?}\"")?;
385 }
386
387 if *files_time_range_pruned > 0 {
389 write!(f, ", \"files_time_range_pruned\":{files_time_range_pruned}")?;
390 }
391 if *rg_fulltext_filtered > 0 {
392 write!(f, ", \"rg_fulltext_filtered\":{rg_fulltext_filtered}")?;
393 }
394 if *rg_inverted_filtered > 0 {
395 write!(f, ", \"rg_inverted_filtered\":{rg_inverted_filtered}")?;
396 }
397 if *rg_minmax_filtered > 0 {
398 write!(f, ", \"rg_minmax_filtered\":{rg_minmax_filtered}")?;
399 }
400 if *rg_bloom_filtered > 0 {
401 write!(f, ", \"rg_bloom_filtered\":{rg_bloom_filtered}")?;
402 }
403 if *rows_fulltext_filtered > 0 {
404 write!(f, ", \"rows_fulltext_filtered\":{rows_fulltext_filtered}")?;
405 }
406 if *rows_inverted_filtered > 0 {
407 write!(f, ", \"rows_inverted_filtered\":{rows_inverted_filtered}")?;
408 }
409 if *rows_bloom_filtered > 0 {
410 write!(f, ", \"rows_bloom_filtered\":{rows_bloom_filtered}")?;
411 }
412 if *rows_precise_filtered > 0 {
413 write!(f, ", \"rows_precise_filtered\":{rows_precise_filtered}")?;
414 }
415 if *fulltext_index_cache_hit > 0 {
416 write!(
417 f,
418 ", \"fulltext_index_cache_hit\":{fulltext_index_cache_hit}"
419 )?;
420 }
421 if *fulltext_index_cache_miss > 0 {
422 write!(
423 f,
424 ", \"fulltext_index_cache_miss\":{fulltext_index_cache_miss}"
425 )?;
426 }
427 if *inverted_index_cache_hit > 0 {
428 write!(
429 f,
430 ", \"inverted_index_cache_hit\":{inverted_index_cache_hit}"
431 )?;
432 }
433 if *inverted_index_cache_miss > 0 {
434 write!(
435 f,
436 ", \"inverted_index_cache_miss\":{inverted_index_cache_miss}"
437 )?;
438 }
439 if *bloom_filter_cache_hit > 0 {
440 write!(f, ", \"bloom_filter_cache_hit\":{bloom_filter_cache_hit}")?;
441 }
442 if *bloom_filter_cache_miss > 0 {
443 write!(f, ", \"bloom_filter_cache_miss\":{bloom_filter_cache_miss}")?;
444 }
445 if *minmax_cache_hit > 0 {
446 write!(f, ", \"minmax_cache_hit\":{minmax_cache_hit}")?;
447 }
448 if *minmax_cache_miss > 0 {
449 write!(f, ", \"minmax_cache_miss\":{minmax_cache_miss}")?;
450 }
451 if *pruner_cache_hit > 0 {
452 write!(f, ", \"pruner_cache_hit\":{pruner_cache_hit}")?;
453 }
454 if *pruner_cache_miss > 0 {
455 write!(f, ", \"pruner_cache_miss\":{pruner_cache_miss}")?;
456 }
457 if !pruner_prune_cost.is_zero() {
458 write!(f, ", \"pruner_prune_cost\":\"{pruner_prune_cost:?}\"")?;
459 }
460
461 if *num_series_send_timeout > 0 {
463 write!(f, ", \"num_series_send_timeout\":{num_series_send_timeout}")?;
464 }
465 if *num_series_send_full > 0 {
466 write!(f, ", \"num_series_send_full\":{num_series_send_full}")?;
467 }
468 if *num_distributor_rows > 0 {
469 write!(f, ", \"num_distributor_rows\":{num_distributor_rows}")?;
470 }
471 if *num_distributor_batches > 0 {
472 write!(f, ", \"num_distributor_batches\":{num_distributor_batches}")?;
473 }
474 if !distributor_scan_cost.is_zero() {
475 write!(
476 f,
477 ", \"distributor_scan_cost\":\"{distributor_scan_cost:?}\""
478 )?;
479 }
480 if !distributor_yield_cost.is_zero() {
481 write!(
482 f,
483 ", \"distributor_yield_cost\":\"{distributor_yield_cost:?}\""
484 )?;
485 }
486 if !distributor_divider_cost.is_zero() {
487 write!(
488 f,
489 ", \"distributor_divider_cost\":\"{distributor_divider_cost:?}\""
490 )?;
491 }
492
493 if *mem_rows > 0 {
495 write!(f, ", \"mem_rows\":{mem_rows}")?;
496 }
497 if *mem_batches > 0 {
498 write!(f, ", \"mem_batches\":{mem_batches}")?;
499 }
500 if *mem_series > 0 {
501 write!(f, ", \"mem_series\":{mem_series}")?;
502 }
503 if !mem_scan_cost.is_zero() {
504 write!(f, ", \"mem_scan_cost\":\"{mem_scan_cost:?}\"")?;
505 }
506 if !mem_prefilter_cost.is_zero() {
507 write!(f, ", \"mem_prefilter_cost\":\"{mem_prefilter_cost:?}\"")?;
508 }
509 if *mem_prefilter_rows_filtered > 0 {
510 write!(
511 f,
512 ", \"mem_prefilter_rows_filtered\":{mem_prefilter_rows_filtered}"
513 )?;
514 }
515
516 if let Some(metrics) = inverted_index_apply_metrics
518 && !metrics.is_empty()
519 {
520 write!(f, ", \"inverted_index_apply_metrics\":{:?}", metrics)?;
521 }
522 if let Some(metrics) = bloom_filter_apply_metrics
523 && !metrics.is_empty()
524 {
525 write!(f, ", \"bloom_filter_apply_metrics\":{:?}", metrics)?;
526 }
527 if let Some(metrics) = fulltext_index_apply_metrics
528 && !metrics.is_empty()
529 {
530 write!(f, ", \"fulltext_index_apply_metrics\":{:?}", metrics)?;
531 }
532 if let Some(metrics) = fetch_metrics
533 && !metrics.is_empty()
534 {
535 write!(f, ", \"fetch_metrics\":{:?}", metrics)?;
536 }
537 if let Some(metrics) = metadata_cache_metrics
538 && !metrics.is_empty()
539 {
540 write!(f, ", \"metadata_cache_metrics\":{:?}", metrics)?;
541 }
542
543 if !merge_metrics.scan_cost.is_zero() {
545 write!(f, ", \"merge_metrics\":{:?}", merge_metrics)?;
546 }
547
548 if !dedup_metrics.dedup_cost.is_zero() {
550 write!(f, ", \"dedup_metrics\":{:?}", dedup_metrics)?;
551 }
552
553 if let Some(file_metrics) = per_file_metrics
555 && !file_metrics.is_empty()
556 {
557 let mut heap = BinaryHeap::new();
559 for (file_id, metrics) in file_metrics.iter() {
560 let total_cost =
561 metrics.build_part_cost + metrics.build_reader_cost + metrics.scan_cost;
562
563 if total_cost.is_zero() && metrics.num_ranges == 0 {
566 continue;
567 }
568
569 if heap.len() < 10 {
570 heap.push(CompareCostReverse {
572 total_cost,
573 file_id: *file_id,
574 metrics,
575 });
576 } else if let Some(min_entry) = heap.peek() {
577 if total_cost > min_entry.total_cost {
579 heap.pop();
580 heap.push(CompareCostReverse {
581 total_cost,
582 file_id: *file_id,
583 metrics,
584 });
585 }
586 }
587 }
588
589 let top_files = heap.into_sorted_vec();
590 write!(f, ", \"top_file_metrics\": {{")?;
591 for (i, item) in top_files.iter().enumerate() {
592 let CompareCostReverse {
593 total_cost: _,
594 file_id,
595 metrics,
596 } = item;
597 if i > 0 {
598 write!(f, ", ")?;
599 }
600 write!(f, "\"{}\": {:?}", file_id, metrics)?;
601 }
602 write!(f, "}}")?;
603 }
604
605 if *range_cache_size > 0 {
606 write!(f, ", \"range_cache_size\":{range_cache_size}")?;
607 }
608 if *range_cache_hit > 0 {
609 write!(f, ", \"range_cache_hit\":{range_cache_hit}")?;
610 }
611 if *range_cache_miss > 0 {
612 write!(f, ", \"range_cache_miss\":{range_cache_miss}")?;
613 }
614
615 write!(
616 f,
617 ", \"build_ranges_peak_mem_size\":{build_ranges_peak_mem_size}, \
618 \"num_peak_range_builders\":{num_peak_range_builders}, \
619 \"stream_eof\":{stream_eof}}}"
620 )
621 }
622}
623impl ScanMetricsSet {
624 fn with_prepare_scan_cost(mut self, cost: Duration) -> Self {
626 self.prepare_scan_cost += cost;
627 self
628 }
629
630 fn with_convert_cost(mut self, time: Time) -> Self {
632 self.convert_cost = Some(time);
633 self
634 }
635
636 fn merge_scanner_metrics(&mut self, other: &ScannerMetrics) {
638 let ScannerMetrics {
639 scan_cost,
640 yield_cost,
641 num_batches,
642 num_rows,
643 } = other;
644
645 self.scan_cost += *scan_cost;
646 self.yield_cost += *yield_cost;
647 self.num_rows += *num_rows;
648 self.num_batches += *num_batches;
649 }
650
651 fn merge_reader_metrics(&mut self, other: &ReaderMetrics) {
653 let ReaderMetrics {
654 build_cost,
655 filter_metrics:
656 ReaderFilterMetrics {
657 rg_total,
658 rg_fulltext_filtered,
659 rg_inverted_filtered,
660 rg_minmax_filtered,
661 rg_bloom_filtered,
662 rows_total,
663 rows_fulltext_filtered,
664 rows_inverted_filtered,
665 rows_bloom_filtered,
666 rows_precise_filtered,
667 fulltext_index_cache_hit,
668 fulltext_index_cache_miss,
669 inverted_index_cache_hit,
670 inverted_index_cache_miss,
671 bloom_filter_cache_hit,
672 bloom_filter_cache_miss,
673 minmax_cache_hit,
674 minmax_cache_miss,
675 pruner_cache_hit,
676 pruner_cache_miss,
677 pruner_prune_cost,
678 files_time_range_pruned,
679 inverted_index_apply_metrics,
680 bloom_filter_apply_metrics,
681 fulltext_index_apply_metrics,
682 },
683 num_record_batches,
684 num_batches,
685 num_rows,
686 scan_cost,
687 metadata_cache_metrics,
688 fetch_metrics,
689 metadata_mem_size,
690 num_range_builders,
691 } = other;
692
693 self.build_parts_cost += *build_cost;
694 self.sst_scan_cost += *scan_cost;
695
696 self.files_time_range_pruned += *files_time_range_pruned;
697
698 self.rg_total += *rg_total;
699 self.rg_fulltext_filtered += *rg_fulltext_filtered;
700 self.rg_inverted_filtered += *rg_inverted_filtered;
701 self.rg_minmax_filtered += *rg_minmax_filtered;
702 self.rg_bloom_filtered += *rg_bloom_filtered;
703
704 self.rows_before_filter += *rows_total;
705 self.rows_fulltext_filtered += *rows_fulltext_filtered;
706 self.rows_inverted_filtered += *rows_inverted_filtered;
707 self.rows_bloom_filtered += *rows_bloom_filtered;
708 self.rows_precise_filtered += *rows_precise_filtered;
709
710 self.fulltext_index_cache_hit += *fulltext_index_cache_hit;
711 self.fulltext_index_cache_miss += *fulltext_index_cache_miss;
712 self.inverted_index_cache_hit += *inverted_index_cache_hit;
713 self.inverted_index_cache_miss += *inverted_index_cache_miss;
714 self.bloom_filter_cache_hit += *bloom_filter_cache_hit;
715 self.bloom_filter_cache_miss += *bloom_filter_cache_miss;
716 self.minmax_cache_hit += *minmax_cache_hit;
717 self.minmax_cache_miss += *minmax_cache_miss;
718 self.pruner_cache_hit += *pruner_cache_hit;
719 self.pruner_cache_miss += *pruner_cache_miss;
720 self.pruner_prune_cost += *pruner_prune_cost;
721
722 self.num_sst_record_batches += *num_record_batches;
723 self.num_sst_batches += *num_batches;
724 self.num_sst_rows += *num_rows;
725
726 if let Some(metrics) = inverted_index_apply_metrics {
728 self.inverted_index_apply_metrics
729 .get_or_insert_with(InvertedIndexApplyMetrics::default)
730 .merge_from(metrics);
731 }
732 if let Some(metrics) = bloom_filter_apply_metrics {
733 self.bloom_filter_apply_metrics
734 .get_or_insert_with(BloomFilterIndexApplyMetrics::default)
735 .merge_from(metrics);
736 }
737 if let Some(metrics) = fulltext_index_apply_metrics {
738 self.fulltext_index_apply_metrics
739 .get_or_insert_with(FulltextIndexApplyMetrics::default)
740 .merge_from(metrics);
741 }
742 if let Some(metrics) = fetch_metrics {
743 self.fetch_metrics
744 .get_or_insert_with(ParquetFetchMetrics::default)
745 .merge_from(metrics);
746 }
747 self.metadata_cache_metrics
748 .get_or_insert_with(MetadataCacheMetrics::default)
749 .merge_from(metadata_cache_metrics);
750
751 self.build_ranges_mem_size += *metadata_mem_size;
753 if self.build_ranges_mem_size > self.build_ranges_peak_mem_size {
754 self.build_ranges_peak_mem_size = self.build_ranges_mem_size;
755 }
756
757 self.num_range_builders += *num_range_builders;
759 if self.num_range_builders > self.num_peak_range_builders {
760 self.num_peak_range_builders = self.num_range_builders;
761 }
762 }
763
764 fn merge_per_file_metrics(&mut self, other: &HashMap<RegionFileId, FileScanMetrics>) {
766 let self_file_metrics = self.per_file_metrics.get_or_insert_with(HashMap::new);
767 for (file_id, metrics) in other {
768 self_file_metrics
769 .entry(*file_id)
770 .or_default()
771 .merge_from(metrics);
772 }
773 }
774
775 fn set_distributor_metrics(&mut self, distributor_metrics: &SeriesDistributorMetrics) {
777 let SeriesDistributorMetrics {
778 num_series_send_timeout,
779 num_series_send_full,
780 num_rows,
781 num_batches,
782 scan_cost,
783 yield_cost,
784 divider_cost,
785 } = distributor_metrics;
786
787 self.num_series_send_timeout += *num_series_send_timeout;
788 self.num_series_send_full += *num_series_send_full;
789 self.num_distributor_rows += *num_rows;
790 self.num_distributor_batches += *num_batches;
791 self.distributor_scan_cost += *scan_cost;
792 self.distributor_yield_cost += *yield_cost;
793 self.distributor_divider_cost += *divider_cost;
794 }
795
796 fn observe_metrics(&self) {
798 READ_STAGE_ELAPSED
799 .with_label_values(&["prepare_scan"])
800 .observe(self.prepare_scan_cost.as_secs_f64());
801 READ_STAGE_ELAPSED
802 .with_label_values(&["build_reader"])
803 .observe(self.build_reader_cost.as_secs_f64());
804 READ_STAGE_ELAPSED
805 .with_label_values(&["scan"])
806 .observe(self.scan_cost.as_secs_f64());
807 READ_STAGE_ELAPSED
808 .with_label_values(&["yield"])
809 .observe(self.yield_cost.as_secs_f64());
810 if let Some(time) = &self.convert_cost {
811 READ_STAGE_ELAPSED
812 .with_label_values(&["convert"])
813 .observe(Duration::from_nanos(time.value() as u64).as_secs_f64());
814 }
815 READ_STAGE_ELAPSED
816 .with_label_values(&["total"])
817 .observe(self.total_cost.as_secs_f64());
818 READ_ROWS_RETURN.observe(self.num_rows as f64);
819 READ_BATCHES_RETURN.observe(self.num_batches as f64);
820
821 READ_STAGE_ELAPSED
822 .with_label_values(&["build_parts"])
823 .observe(self.build_parts_cost.as_secs_f64());
824
825 READ_ROW_GROUPS_TOTAL
826 .with_label_values(&["before_filtering"])
827 .inc_by(self.rg_total as u64);
828 READ_ROW_GROUPS_TOTAL
829 .with_label_values(&["fulltext_index_filtered"])
830 .inc_by(self.rg_fulltext_filtered as u64);
831 READ_ROW_GROUPS_TOTAL
832 .with_label_values(&["inverted_index_filtered"])
833 .inc_by(self.rg_inverted_filtered as u64);
834 READ_ROW_GROUPS_TOTAL
835 .with_label_values(&["minmax_index_filtered"])
836 .inc_by(self.rg_minmax_filtered as u64);
837 READ_ROW_GROUPS_TOTAL
838 .with_label_values(&["bloom_filter_index_filtered"])
839 .inc_by(self.rg_bloom_filtered as u64);
840
841 PRECISE_FILTER_ROWS_TOTAL
842 .with_label_values(&["parquet"])
843 .inc_by(self.rows_precise_filtered as u64);
844 READ_ROWS_IN_ROW_GROUP_TOTAL
845 .with_label_values(&["before_filtering"])
846 .inc_by(self.rows_before_filter as u64);
847 READ_ROWS_IN_ROW_GROUP_TOTAL
848 .with_label_values(&["fulltext_index_filtered"])
849 .inc_by(self.rows_fulltext_filtered as u64);
850 READ_ROWS_IN_ROW_GROUP_TOTAL
851 .with_label_values(&["inverted_index_filtered"])
852 .inc_by(self.rows_inverted_filtered as u64);
853 READ_ROWS_IN_ROW_GROUP_TOTAL
854 .with_label_values(&["bloom_filter_index_filtered"])
855 .inc_by(self.rows_bloom_filtered as u64);
856 }
857}
858
859struct PartitionMetricsInner {
860 region_id: RegionId,
861 partition: usize,
863 scanner_type: &'static str,
865 query_start: Instant,
867 explain_verbose: bool,
869 metrics: Mutex<ScanMetricsSet>,
871 in_progress_scan: IntGauge,
872
873 build_parts_cost: Time,
876 build_reader_cost: Time,
878 scan_cost: Time,
880 yield_cost: Time,
882 convert_cost: Time,
884 elapsed_compute: Time,
886}
887
888impl PartitionMetricsInner {
889 fn on_finish(&self, stream_eof: bool) {
890 let mut metrics = self.metrics.lock().unwrap();
891 if metrics.total_cost.is_zero() {
892 metrics.total_cost = self.query_start.elapsed();
893 }
894 if !metrics.stream_eof {
895 metrics.stream_eof = stream_eof;
896 }
897 }
898}
899
900impl MergeMetricsReport for PartitionMetricsInner {
901 fn report(&self, metrics: &mut MergeMetrics) {
902 let mut scan_metrics = self.metrics.lock().unwrap();
903 scan_metrics.merge_metrics.merge(metrics);
905
906 *metrics = MergeMetrics::default();
908 }
909}
910
911impl DedupMetricsReport for PartitionMetricsInner {
912 fn report(&self, metrics: &mut DedupMetrics) {
913 let mut scan_metrics = self.metrics.lock().unwrap();
914 scan_metrics.dedup_metrics.merge(metrics);
916
917 *metrics = DedupMetrics::default();
919 }
920}
921
922impl Drop for PartitionMetricsInner {
923 fn drop(&mut self) {
924 self.on_finish(false);
925 let metrics = self.metrics.lock().unwrap();
926 metrics.observe_metrics();
927 self.in_progress_scan.dec();
928
929 if self.explain_verbose {
930 common_telemetry::info!(
931 "{} finished, region_id: {}, partition: {}, scan_metrics: {:?}",
932 self.scanner_type,
933 self.region_id,
934 self.partition,
935 metrics,
936 );
937 } else {
938 common_telemetry::debug!(
939 "{} finished, region_id: {}, partition: {}, scan_metrics: {:?}",
940 self.scanner_type,
941 self.region_id,
942 self.partition,
943 metrics,
944 );
945 }
946 }
947}
948
949#[derive(Default)]
951pub(crate) struct PartitionMetricsList(Mutex<Vec<Option<PartitionMetrics>>>);
952
953impl PartitionMetricsList {
954 pub(crate) fn set(&self, partition: usize, metrics: PartitionMetrics) {
956 let mut list = self.0.lock().unwrap();
957 if list.len() <= partition {
958 list.resize(partition + 1, None);
959 }
960 list[partition] = Some(metrics);
961 }
962
963 pub(crate) fn format_verbose_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
965 let list = self.0.lock().unwrap();
966 write!(f, ", \"metrics_per_partition\": ")?;
967 f.debug_list()
968 .entries(list.iter().filter_map(|p| p.as_ref()))
969 .finish()?;
970 write!(f, "}}")
971 }
972}
973
974#[derive(Clone)]
976pub struct PartitionMetrics(Arc<PartitionMetricsInner>);
977
978impl PartitionMetrics {
979 pub(crate) fn new(
980 region_id: RegionId,
981 partition: usize,
982 scanner_type: &'static str,
983 query_start: Instant,
984 explain_verbose: bool,
985 metrics_set: &ExecutionPlanMetricsSet,
986 ) -> Self {
987 let partition_str = partition.to_string();
988 let in_progress_scan = IN_PROGRESS_SCAN.with_label_values(&[scanner_type, &partition_str]);
989 in_progress_scan.inc();
990 let convert_cost = MetricBuilder::new(metrics_set).subset_time("convert_cost", partition);
991 let metrics = ScanMetricsSet::default()
992 .with_prepare_scan_cost(query_start.elapsed())
993 .with_convert_cost(convert_cost.clone());
994 let inner = PartitionMetricsInner {
995 region_id,
996 partition,
997 scanner_type,
998 query_start,
999 explain_verbose,
1000 metrics: Mutex::new(metrics),
1001 in_progress_scan,
1002 build_parts_cost: MetricBuilder::new(metrics_set)
1003 .subset_time("build_parts_cost", partition),
1004 build_reader_cost: MetricBuilder::new(metrics_set)
1005 .subset_time("build_reader_cost", partition),
1006 scan_cost: MetricBuilder::new(metrics_set).subset_time("scan_cost", partition),
1007 yield_cost: MetricBuilder::new(metrics_set).subset_time("yield_cost", partition),
1008 convert_cost,
1009 elapsed_compute: MetricBuilder::new(metrics_set).elapsed_compute(partition),
1010 };
1011 Self(Arc::new(inner))
1012 }
1013
1014 pub(crate) fn on_first_poll(&self) {
1015 let mut metrics = self.0.metrics.lock().unwrap();
1016 metrics.first_poll = self.0.query_start.elapsed();
1017 }
1018
1019 pub(crate) fn inc_num_mem_ranges(&self, num: usize) {
1020 let mut metrics = self.0.metrics.lock().unwrap();
1021 metrics.num_mem_ranges += num;
1022 }
1023
1024 pub fn inc_num_file_ranges(&self, num: usize) {
1025 let mut metrics = self.0.metrics.lock().unwrap();
1026 metrics.num_file_ranges += num;
1027 }
1028
1029 fn record_elapsed_compute(&self, duration: Duration) {
1030 if duration.is_zero() {
1031 return;
1032 }
1033 self.0.elapsed_compute.add_duration(duration);
1034 }
1035
1036 pub(crate) fn inc_build_reader_cost(&self, cost: Duration) {
1038 self.0.build_reader_cost.add_duration(cost);
1039
1040 let mut metrics = self.0.metrics.lock().unwrap();
1041 metrics.build_reader_cost += cost;
1042 }
1043
1044 pub(crate) fn inc_convert_batch_cost(&self, cost: Duration) {
1045 self.0.convert_cost.add_duration(cost);
1046 self.record_elapsed_compute(cost);
1047 }
1048
1049 pub(crate) fn report_mem_scan_metrics(&self, data: &crate::memtable::MemScanMetricsData) {
1051 let mut metrics = self.0.metrics.lock().unwrap();
1052 metrics.mem_scan_cost += data.scan_cost;
1053 metrics.mem_rows += data.num_rows;
1054 metrics.mem_batches += data.num_batches;
1055 metrics.mem_series += data.total_series;
1056 metrics.mem_prefilter_cost += data.prefilter_cost;
1057 metrics.mem_prefilter_rows_filtered += data.prefilter_rows_filtered;
1058 }
1059
1060 pub(crate) fn merge_metrics(&self, metrics: &ScannerMetrics) {
1062 self.0.scan_cost.add_duration(metrics.scan_cost);
1063 self.record_elapsed_compute(metrics.scan_cost);
1064 self.0.yield_cost.add_duration(metrics.yield_cost);
1065 self.record_elapsed_compute(metrics.yield_cost);
1066
1067 let mut metrics_set = self.0.metrics.lock().unwrap();
1068 metrics_set.merge_scanner_metrics(metrics);
1069 }
1070
1071 pub fn merge_reader_metrics(
1073 &self,
1074 metrics: &ReaderMetrics,
1075 per_file_metrics: Option<&HashMap<RegionFileId, FileScanMetrics>>,
1076 ) {
1077 self.0.build_parts_cost.add_duration(metrics.build_cost);
1078
1079 let mut metrics_set = self.0.metrics.lock().unwrap();
1080 metrics_set.merge_reader_metrics(metrics);
1081
1082 if let Some(file_metrics) = per_file_metrics {
1084 metrics_set.merge_per_file_metrics(file_metrics);
1085 }
1086 }
1087
1088 pub(crate) fn on_finish(&self) {
1090 self.0.on_finish(true);
1091 }
1092
1093 pub(crate) fn set_distributor_metrics(&self, metrics: &SeriesDistributorMetrics) {
1095 let mut metrics_set = self.0.metrics.lock().unwrap();
1096 metrics_set.set_distributor_metrics(metrics);
1097 }
1098
1099 pub(crate) fn explain_verbose(&self) -> bool {
1101 self.0.explain_verbose
1102 }
1103
1104 pub(crate) fn merge_metrics_reporter(&self) -> Arc<dyn MergeMetricsReport> {
1106 self.0.clone()
1107 }
1108
1109 pub(crate) fn dedup_metrics_reporter(&self) -> Arc<dyn DedupMetricsReport> {
1111 self.0.clone()
1112 }
1113
1114 pub(crate) fn inc_range_cache_size(&self, size: usize) {
1116 let mut metrics = self.0.metrics.lock().unwrap();
1117 metrics.range_cache_size += size;
1118 }
1119
1120 pub(crate) fn inc_range_cache_hit(&self) {
1122 let mut metrics = self.0.metrics.lock().unwrap();
1123 metrics.range_cache_hit += 1;
1124 }
1125
1126 pub(crate) fn inc_range_cache_miss(&self) {
1128 let mut metrics = self.0.metrics.lock().unwrap();
1129 metrics.range_cache_miss += 1;
1130 }
1131}
1132
1133impl fmt::Debug for PartitionMetrics {
1134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1135 let metrics = self.0.metrics.lock().unwrap();
1136 write!(
1137 f,
1138 r#"{{"partition":{}, "metrics":{:?}}}"#,
1139 self.0.partition, metrics
1140 )
1141 }
1142}
1143
1144#[derive(Default)]
1146pub(crate) struct SeriesDistributorMetrics {
1147 pub(crate) num_series_send_timeout: usize,
1149 pub(crate) num_series_send_full: usize,
1151 pub(crate) num_rows: usize,
1153 pub(crate) num_batches: usize,
1155 pub(crate) scan_cost: Duration,
1157 pub(crate) yield_cost: Duration,
1159 pub(crate) divider_cost: Duration,
1161}
1162
1163#[tracing::instrument(
1165 skip_all,
1166 fields(
1167 region_id = %stream_ctx.input.region_metadata().region_id,
1168 row_group_index = %index.index,
1169 source = "mem_flat"
1170 )
1171)]
1172pub(crate) fn scan_flat_mem_ranges(
1173 stream_ctx: Arc<StreamContext>,
1174 part_metrics: PartitionMetrics,
1175 index: RowGroupIndex,
1176 time_range: FileTimeRange,
1177) -> impl Stream<Item = Result<RecordBatch>> {
1178 try_stream! {
1179 let ranges = stream_ctx.input.build_mem_ranges(index);
1180 part_metrics.inc_num_mem_ranges(ranges.len());
1181 for range in ranges {
1182 let build_reader_start = Instant::now();
1183 let mem_scan_metrics = Some(MemScanMetrics::default());
1184 let mut iter = range.build_record_batch_iter(Some(time_range), mem_scan_metrics.clone())?;
1185 part_metrics.inc_build_reader_cost(build_reader_start.elapsed());
1186
1187 while let Some(record_batch) = iter.next().transpose()? {
1188 yield record_batch;
1189 }
1190
1191 if let Some(ref metrics) = mem_scan_metrics {
1193 let data = metrics.data();
1194 part_metrics.report_mem_scan_metrics(&data);
1195 }
1196 }
1197 }
1198}
1199
1200const SPLIT_ROW_THRESHOLD: u64 = DEFAULT_ROW_GROUP_SIZE as u64;
1202const NUM_SERIES_THRESHOLD: u64 = 10240;
1204const BATCH_SIZE_THRESHOLD: u64 = 50;
1207
1208pub(crate) fn should_split_flat_batches_for_merge(
1211 stream_ctx: &Arc<StreamContext>,
1212 range_meta: &RangeMeta,
1213) -> Option<usize> {
1214 let mut num_files_to_split = 0;
1216 let mut num_mem_rows = 0;
1217 let mut num_mem_series = 0;
1218 let mut total_rows: u64 = 0;
1220 let mut total_series: u64 = 0;
1221 for index in &range_meta.row_group_indices {
1225 if stream_ctx.is_mem_range_index(*index) {
1226 let memtable = &stream_ctx.input.memtables[index.index];
1227 let stats = memtable.stats();
1229 num_mem_rows += stats.num_rows();
1230 num_mem_series += stats.series_count();
1231 } else if stream_ctx.is_file_range_index(*index) {
1232 let file_index = index.index - stream_ctx.input.num_memtables();
1234 let file = &stream_ctx.input.files[file_index];
1235 let file_meta = file.meta_ref();
1236 if file_meta.level == 0 {
1237 if file_meta.num_series > 0 {
1239 if !can_split_series(file_meta.num_rows, file_meta.num_series) {
1240 return None;
1241 }
1242 total_rows += file_meta.num_rows;
1243 total_series += file_meta.num_series;
1244 }
1245 num_files_to_split += 1;
1247 continue;
1248 } else if file_meta.num_rows < SPLIT_ROW_THRESHOLD || file_meta.num_series == 0 {
1249 continue;
1251 }
1252 debug_assert!(file_meta.num_rows > 0);
1253 if !can_split_series(file_meta.num_rows, file_meta.num_series) {
1254 common_telemetry::trace!(
1256 "Can't split series for file {}, level: {}, num_rows: {}, num_series: {}",
1257 file_meta.file_id,
1258 file_meta.level,
1259 file_meta.num_rows,
1260 file_meta.num_series,
1261 );
1262 return None;
1263 } else {
1264 num_files_to_split += 1;
1265 total_rows += file.meta_ref().num_rows;
1266 total_series += file.meta_ref().num_series;
1267 }
1268 }
1269 }
1271
1272 let should_split = if num_files_to_split > 0 {
1273 true
1275 } else if num_mem_series > 0
1276 && num_mem_rows > 0
1277 && can_split_series(num_mem_rows as u64, num_mem_series as u64)
1278 {
1279 total_rows += num_mem_rows as u64;
1280 total_series += num_mem_series as u64;
1281 true
1282 } else {
1283 false
1284 };
1285
1286 if !should_split {
1287 return None;
1288 }
1289
1290 let estimated_batch_size = if total_series > 0 && total_rows > 0 {
1292 ((total_rows / total_series) as usize).clamp(1, DEFAULT_READ_BATCH_SIZE)
1293 } else {
1294 DEFAULT_READ_BATCH_SIZE / 4
1296 };
1297 Some(estimated_batch_size)
1298}
1299
1300pub(crate) fn compute_parallel_channel_size(estimated_rows_per_batch: usize) -> usize {
1303 let size = 2 * DEFAULT_READ_BATCH_SIZE / estimated_rows_per_batch.max(1);
1304 size.clamp(2, 64)
1305}
1306
1307pub(crate) fn compute_average_batch_size(
1309 estimated_rows_per_batch: impl IntoIterator<Item = usize>,
1310) -> usize {
1311 let mut total = 0usize;
1312 let mut count = 0usize;
1313 for size in estimated_rows_per_batch {
1314 total += size;
1315 count += 1;
1316 }
1317
1318 if count == 0 {
1319 return DEFAULT_READ_BATCH_SIZE;
1320 }
1321
1322 (total / count).clamp(1, DEFAULT_READ_BATCH_SIZE)
1323}
1324
1325fn can_split_series(num_rows: u64, num_series: u64) -> bool {
1326 if num_rows == 0 || num_series == 0 {
1327 return false;
1328 }
1329
1330 num_series < NUM_SERIES_THRESHOLD || num_rows / num_series >= BATCH_SIZE_THRESHOLD
1332}
1333
1334#[cfg(test)]
1335mod split_tests {
1336 use std::sync::Arc;
1337
1338 use common_time::Timestamp;
1339 use smallvec::smallvec;
1340 use store_api::storage::FileId;
1341
1342 use super::*;
1343 use crate::read::flat_projection::FlatProjectionMapper;
1344 use crate::read::range::{RangeMeta, RowGroupIndex, SourceIndex};
1345 use crate::read::scan_region::{ScanInput, StreamContext};
1346 use crate::sst::file::FileHandle;
1347 use crate::test_util::memtable_util::metadata_with_primary_key;
1348 use crate::test_util::scheduler_util::SchedulerEnv;
1349 use crate::test_util::sst_util::sst_file_handle_with_file_id;
1350
1351 async fn new_stream_context_with_files(files: Vec<FileHandle>) -> StreamContext {
1352 let env = SchedulerEnv::new().await;
1353 let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
1354 let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
1355 let input = ScanInput::builder(env.access_layer.clone(), mapper)
1356 .with_files(files)
1357 .build();
1358
1359 StreamContext {
1360 input,
1361 ranges: vec![],
1362 query_start: std::time::Instant::now(),
1363 }
1364 }
1365
1366 fn single_file_range_meta() -> RangeMeta {
1367 RangeMeta {
1368 time_range: (
1369 Timestamp::new_millisecond(0),
1370 Timestamp::new_millisecond(1000),
1371 ),
1372 indices: smallvec![SourceIndex {
1373 index: 0,
1374 num_row_groups: 1,
1375 }],
1376 row_group_indices: smallvec![RowGroupIndex {
1377 index: 0,
1378 row_group_index: 0,
1379 }],
1380 num_rows: 1024,
1381 }
1382 }
1383
1384 #[tokio::test]
1385 async fn should_split_level_zero_file_even_when_series_stats_are_missing() {
1386 let mut file = sst_file_handle_with_file_id(FileId::random(), 0, 1000)
1387 .meta_ref()
1388 .clone();
1389 file.level = 0;
1390 file.num_rows = DEFAULT_ROW_GROUP_SIZE as u64;
1391 file.num_row_groups = 1;
1392 file.num_series = 0;
1393
1394 let file = FileHandle::new(file, crate::test_util::new_noop_file_purger());
1395 let stream_ctx = Arc::new(new_stream_context_with_files(vec![file]).await);
1396
1397 assert!(
1398 should_split_flat_batches_for_merge(&stream_ctx, &single_file_range_meta()).is_some()
1399 );
1400 }
1401
1402 #[test]
1403 fn can_split_series_returns_false_for_zero_inputs() {
1404 assert!(!can_split_series(0, 1));
1405 assert!(!can_split_series(1, 0));
1406 assert!(!can_split_series(0, 0));
1407 }
1408}
1409
1410pub(crate) fn new_filter_metrics(explain_verbose: bool) -> ReaderFilterMetrics {
1413 if explain_verbose {
1414 ReaderFilterMetrics {
1415 inverted_index_apply_metrics: Some(InvertedIndexApplyMetrics::default()),
1416 bloom_filter_apply_metrics: Some(BloomFilterIndexApplyMetrics::default()),
1417 fulltext_index_apply_metrics: Some(FulltextIndexApplyMetrics::default()),
1418 ..Default::default()
1419 }
1420 } else {
1421 ReaderFilterMetrics::default()
1422 }
1423}
1424
1425#[tracing::instrument(
1427 skip_all,
1428 fields(
1429 region_id = %stream_ctx.input.region_metadata().region_id,
1430 row_group_index = %index.index,
1431 source = read_type
1432 )
1433)]
1434pub(crate) async fn scan_flat_file_ranges(
1435 stream_ctx: Arc<StreamContext>,
1436 part_metrics: PartitionMetrics,
1437 index: RowGroupIndex,
1438 read_type: &'static str,
1439 partition_pruner: Arc<PartitionPruner>,
1440) -> Result<impl Stream<Item = Result<RecordBatch>>> {
1441 let mut reader_metrics = ReaderMetrics {
1442 filter_metrics: new_filter_metrics(part_metrics.explain_verbose()),
1443 ..Default::default()
1444 };
1445 let ranges = partition_pruner
1446 .build_file_ranges(index, &part_metrics, &mut reader_metrics)
1447 .await?;
1448 part_metrics.inc_num_file_ranges(ranges.len());
1449 part_metrics.merge_reader_metrics(&reader_metrics, None);
1450
1451 let init_per_file_metrics = if part_metrics.explain_verbose() {
1453 let file = stream_ctx.input.file_from_index(index);
1454 let file_id = file.file_id();
1455
1456 let mut map = HashMap::new();
1457 map.insert(
1458 file_id,
1459 FileScanMetrics {
1460 build_part_cost: reader_metrics.build_cost,
1461 ..Default::default()
1462 },
1463 );
1464 Some(map)
1465 } else {
1466 None
1467 };
1468
1469 Ok(build_flat_file_range_scan_stream(
1470 stream_ctx,
1471 part_metrics,
1472 read_type,
1473 ranges,
1474 init_per_file_metrics,
1475 ))
1476}
1477
1478pub(crate) fn filter_flat_batch_by_sequence(
1490 record_batch: RecordBatch,
1491 sequence_range: Option<SequenceRange>,
1492 file_sequence_trusted: bool,
1493) -> Result<Option<RecordBatch>> {
1494 let Some(sequence) = sequence_range else {
1495 return Ok(Some(record_batch));
1496 };
1497 if !file_sequence_trusted {
1498 return Ok(Some(record_batch));
1499 }
1500
1501 let num_rows = record_batch.num_rows();
1502 if num_rows == 0 {
1503 return Ok(Some(record_batch));
1504 }
1505 let sequence_column = record_batch.column(sequence_column_index(record_batch.num_columns()));
1506 let predicate = sequence
1507 .filter(sequence_column)
1508 .context(ComputeArrowSnafu)?;
1509 let select_count = predicate.true_count();
1510 if select_count == 0 {
1511 return Ok(None);
1512 }
1513 if select_count == num_rows {
1514 return Ok(Some(record_batch));
1515 }
1516 let filtered_batch = datatypes::arrow::compute::filter_record_batch(&record_batch, &predicate)
1517 .context(ComputeArrowSnafu)?;
1518 Ok(Some(filtered_batch))
1519}
1520
1521#[tracing::instrument(
1523 skip_all,
1524 fields(read_type = read_type, range_count = ranges.len())
1525)]
1526pub fn build_flat_file_range_scan_stream(
1527 stream_ctx: Arc<StreamContext>,
1528 part_metrics: PartitionMetrics,
1529 read_type: &'static str,
1530 ranges: SmallVec<[FileRange; 2]>,
1531 mut per_file_metrics: Option<HashMap<RegionFileId, FileScanMetrics>>,
1532) -> impl Stream<Item = Result<RecordBatch>> {
1533 try_stream! {
1534 let fetch_metrics = if part_metrics.explain_verbose() {
1535 Some(Arc::new(ParquetFetchMetrics::default()))
1536 } else {
1537 None
1538 };
1539 let reader_metrics = &mut ReaderMetrics {
1540 fetch_metrics: fetch_metrics.clone(),
1541 ..Default::default()
1542 };
1543 for range in ranges {
1544 let build_reader_start = Instant::now();
1545 let Some(mut reader) = range
1546 .flat_reader(
1547 match stream_ctx.input.series_row_selector {
1548 Some(TimeSeriesRowSelector::LastRow { after_merge: false })
1549 if stream_ctx.input.sequence_range.is_none() =>
1550 {
1551 stream_ctx.input.series_row_selector
1552 }
1553 _ => None,
1554 },
1555 fetch_metrics.as_deref(),
1556 )
1557 .await?
1558 else {
1559 continue;
1560 };
1561 let build_cost = build_reader_start.elapsed();
1562 part_metrics.inc_build_reader_cost(build_cost);
1563
1564 let may_compat = range.compat_batch();
1565 let file_sequence_trusted = range
1566 .file_handle()
1567 .is_effective_target_sequence_trusted(stream_ctx.input.region_metadata().region_id);
1568
1569 let mapper = range.compaction_projection_mapper();
1570 while let Some(record_batch) = reader.next_batch().await? {
1571 let record_batch = if let Some(mapper) = mapper {
1572 let batch = mapper.project(record_batch)?;
1573 batch
1574 } else {
1575 record_batch
1576 };
1577
1578 let Some(record_batch) = filter_flat_batch_by_sequence(
1579 record_batch,
1580 stream_ctx.input.sequence_range,
1581 file_sequence_trusted,
1582 )? else {
1583 continue;
1584 };
1585
1586 if let Some(flat_compat) = may_compat {
1587 let batch = flat_compat.compat(record_batch)?;
1588 yield batch;
1589 } else {
1590 yield record_batch;
1591 }
1592 }
1593
1594 let prune_metrics = reader.metrics();
1595
1596 if let Some(file_metrics_map) = per_file_metrics.as_mut() {
1598 let file_id = range.file_handle().file_id();
1599 let file_metrics = file_metrics_map
1600 .entry(file_id)
1601 .or_insert_with(FileScanMetrics::default);
1602
1603 file_metrics.num_ranges += 1;
1604 file_metrics.num_rows += prune_metrics.num_rows;
1605 file_metrics.build_reader_cost += build_cost;
1606 file_metrics.scan_cost += prune_metrics.scan_cost;
1607 }
1608
1609 reader_metrics.merge_from(&prune_metrics);
1610 }
1611
1612 reader_metrics.observe_rows(read_type);
1614 reader_metrics.filter_metrics.observe();
1615 part_metrics.merge_reader_metrics(reader_metrics, per_file_metrics.as_ref());
1616 }
1617}
1618
1619#[cfg(feature = "enterprise")]
1621pub(crate) async fn scan_flat_extension_range(
1622 context: Arc<StreamContext>,
1623 index: RowGroupIndex,
1624 partition_metrics: PartitionMetrics,
1625 options: crate::extension::ExtensionRangeReadOptions,
1626) -> Result<BoxedRecordBatchStream> {
1627 use snafu::ResultExt;
1628
1629 let range = context.input.extension_range(index.index);
1630 let reader = range.flat_reader(context.as_ref(), options);
1631 let stream = reader
1632 .read(context, partition_metrics, index)
1633 .await
1634 .context(crate::error::ScanExternalRangeSnafu)?;
1635 Ok(stream)
1636}
1637
1638pub(crate) async fn maybe_scan_flat_other_ranges(
1639 context: &Arc<StreamContext>,
1640 index: RowGroupIndex,
1641 metrics: &PartitionMetrics,
1642 pre_filter_mode: PreFilterMode,
1643) -> Result<BoxedRecordBatchStream> {
1644 #[cfg(feature = "enterprise")]
1645 {
1646 let options = crate::extension::ExtensionRangeReadOptions { pre_filter_mode };
1647 scan_flat_extension_range(context.clone(), index, metrics.clone(), options).await
1648 }
1649
1650 #[cfg(not(feature = "enterprise"))]
1651 {
1652 let _ = context;
1653 let _ = index;
1654 let _ = metrics;
1655 let _ = pre_filter_mode;
1656
1657 crate::error::UnexpectedSnafu {
1658 reason: "no other ranges scannable in flat format",
1659 }
1660 .fail()
1661 }
1662}
1663
1664pub(crate) struct SplitRecordBatchStream<S> {
1666 inner: S,
1668 batches: VecDeque<RecordBatch>,
1670}
1671
1672impl<S> SplitRecordBatchStream<S> {
1673 pub(crate) fn new(inner: S) -> Self {
1675 Self {
1676 inner,
1677 batches: VecDeque::new(),
1678 }
1679 }
1680}
1681
1682impl<S> Stream for SplitRecordBatchStream<S>
1683where
1684 S: Stream<Item = Result<RecordBatch>> + Unpin,
1685{
1686 type Item = Result<RecordBatch>;
1687
1688 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1689 loop {
1690 if let Some(batch) = self.batches.pop_front() {
1692 return Poll::Ready(Some(Ok(batch)));
1693 }
1694
1695 let record_batch = match futures::ready!(Pin::new(&mut self.inner).poll_next(cx)) {
1697 Some(Ok(batch)) => batch,
1698 Some(Err(e)) => return Poll::Ready(Some(Err(e))),
1699 None => return Poll::Ready(None),
1700 };
1701
1702 split_record_batch(record_batch, &mut self.batches);
1704 }
1706 }
1707}
1708
1709pub(crate) fn split_record_batch(record_batch: RecordBatch, batches: &mut VecDeque<RecordBatch>) {
1714 let batch_rows = record_batch.num_rows();
1715 if batch_rows == 0 {
1716 return;
1717 }
1718 if batch_rows < 2 {
1719 batches.push_back(record_batch);
1720 return;
1721 }
1722
1723 let time_index_pos = time_index_column_index(record_batch.num_columns());
1724 let timestamps = record_batch.column(time_index_pos);
1725 let (ts_values, _unit) = timestamp_array_to_primitive(timestamps).unwrap();
1726 let mut offsets = Vec::with_capacity(16);
1727 offsets.push(0);
1728 let values = ts_values.values();
1729 for (i, &value) in values.iter().take(batch_rows - 1).enumerate() {
1730 if value >= values[i + 1] {
1731 offsets.push(i + 1);
1732 }
1733 }
1734 offsets.push(values.len());
1735
1736 for (i, &start) in offsets[..offsets.len() - 1].iter().enumerate() {
1738 let end = offsets[i + 1];
1739 let rows_in_batch = end - start;
1740 batches.push_back(record_batch.slice(start, rows_in_batch));
1741 }
1742}
1743
1744#[cfg(test)]
1745mod tests {
1746 use std::sync::Arc;
1747 use std::time::Instant;
1748
1749 use common_time::Timestamp;
1750 use smallvec::{SmallVec, smallvec};
1751 use store_api::storage::RegionId;
1752
1753 use super::*;
1754 use crate::cache::CacheStrategy;
1755 use crate::memtable::{
1756 BoxedBatchIterator, BoxedRecordBatchIterator, IterBuilder, MemtableRange,
1757 MemtableRangeContext, MemtableStats,
1758 };
1759 use crate::read::flat_projection::FlatProjectionMapper;
1760 use crate::read::range::{MemRangeBuilder, SourceIndex};
1761 use crate::read::scan_region::ScanInput;
1762 use crate::sst::file::{FileHandle, FileMeta};
1763 use crate::sst::file_purger::NoopFilePurger;
1764 use crate::test_util::memtable_util::metadata_for_test;
1765 use crate::test_util::scheduler_util::SchedulerEnv;
1766
1767 struct EmptyIterBuilder;
1768
1769 impl IterBuilder for EmptyIterBuilder {
1770 fn build(&self, _metrics: Option<MemScanMetrics>) -> Result<BoxedBatchIterator> {
1771 Ok(Box::new(std::iter::empty()))
1772 }
1773
1774 fn is_record_batch(&self) -> bool {
1775 true
1776 }
1777
1778 fn build_record_batch(
1779 &self,
1780 _time_range: Option<(Timestamp, Timestamp)>,
1781 _metrics: Option<MemScanMetrics>,
1782 ) -> Result<BoxedRecordBatchIterator> {
1783 Ok(Box::new(std::iter::empty()))
1784 }
1785 }
1786
1787 async fn new_test_stream_ctx(
1788 files: Vec<FileHandle>,
1789 memtables: Vec<MemRangeBuilder>,
1790 ) -> Arc<StreamContext> {
1791 let env = SchedulerEnv::new().await;
1792 let metadata = metadata_for_test();
1793 let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
1794 let input = ScanInput::builder(env.access_layer.clone(), mapper)
1795 .with_cache(CacheStrategy::Disabled)
1796 .with_memtables(memtables)
1797 .with_files(files)
1798 .build();
1799
1800 Arc::new(StreamContext {
1801 input,
1802 ranges: Vec::new(),
1803 query_start: Instant::now(),
1804 })
1805 }
1806
1807 fn new_test_file(num_rows: u64, num_series: u64) -> FileHandle {
1808 let meta = FileMeta {
1809 region_id: RegionId::new(123, 456),
1810 file_id: Default::default(),
1811 level: 1,
1812 time_range: (
1813 Timestamp::new_millisecond(0),
1814 Timestamp::new_millisecond(1000),
1815 ),
1816 num_rows,
1817 num_series,
1818 ..Default::default()
1819 };
1820 FileHandle::new(meta, Arc::new(NoopFilePurger))
1821 }
1822
1823 fn new_test_memtable(num_rows: usize, series_count: usize) -> MemRangeBuilder {
1824 let context = Arc::new(MemtableRangeContext::new(
1825 0,
1826 Box::new(EmptyIterBuilder),
1827 Default::default(),
1828 ));
1829 let stats = MemtableStats {
1830 time_range: Some((
1831 Timestamp::new_millisecond(0),
1832 Timestamp::new_millisecond(1000),
1833 )),
1834 num_rows,
1835 num_ranges: 1,
1836 series_count,
1837 ..Default::default()
1838 };
1839 let range = MemtableRange::new(context, stats.clone());
1840 MemRangeBuilder::new(range, stats)
1841 }
1842
1843 fn new_test_range_meta(row_group_indices: SmallVec<[RowGroupIndex; 2]>) -> RangeMeta {
1844 let indices = row_group_indices
1845 .iter()
1846 .map(|row_group_index| SourceIndex {
1847 index: row_group_index.index,
1848 num_row_groups: 1,
1849 })
1850 .collect();
1851
1852 RangeMeta {
1853 time_range: (
1854 Timestamp::new_millisecond(0),
1855 Timestamp::new_millisecond(1000),
1856 ),
1857 indices,
1858 row_group_indices,
1859 num_rows: 0,
1860 }
1861 }
1862
1863 #[tokio::test]
1864 async fn test_should_split_flat_batches_for_merge_uses_splittable_file_rows_per_series() {
1865 let num_rows = SPLIT_ROW_THRESHOLD * 2;
1866 let num_series = (num_rows / 100).max(1);
1867 let stream_ctx =
1868 new_test_stream_ctx(vec![new_test_file(num_rows, num_series)], vec![]).await;
1869 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
1870 index: 0,
1871 row_group_index: 0,
1872 }]);
1873
1874 assert_eq!(
1875 Some((num_rows / num_series) as usize),
1876 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
1877 );
1878 }
1879
1880 #[tokio::test]
1881 async fn test_should_split_flat_batches_for_merge_skips_small_or_unknown_series_files() {
1882 let stream_ctx = new_test_stream_ctx(
1883 vec![
1884 new_test_file(SPLIT_ROW_THRESHOLD.saturating_sub(1), 1),
1885 new_test_file(SPLIT_ROW_THRESHOLD * 2, 0),
1886 ],
1887 vec![],
1888 )
1889 .await;
1890 let range_meta = new_test_range_meta(smallvec![
1891 RowGroupIndex {
1892 index: 0,
1893 row_group_index: 0,
1894 },
1895 RowGroupIndex {
1896 index: 1,
1897 row_group_index: 0,
1898 }
1899 ]);
1900
1901 assert_eq!(
1902 None,
1903 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
1904 );
1905 }
1906
1907 #[tokio::test]
1908 async fn test_should_split_flat_batches_for_merge_returns_none_for_unsplittable_file() {
1909 let num_series =
1910 (SPLIT_ROW_THRESHOLD / (BATCH_SIZE_THRESHOLD - 1)).max(NUM_SERIES_THRESHOLD) + 1;
1911 let stream_ctx =
1912 new_test_stream_ctx(vec![new_test_file(SPLIT_ROW_THRESHOLD, num_series)], vec![]).await;
1913 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
1914 index: 0,
1915 row_group_index: 0,
1916 }]);
1917
1918 assert_eq!(
1919 None,
1920 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
1921 );
1922 }
1923
1924 #[tokio::test]
1925 async fn test_should_split_flat_batches_for_merge_uses_level_zero_series_stats() {
1926 for (num_rows, num_series, expected) in [
1927 (70_144, 35_083, None),
1928 (100_000, 1_000, Some(100)),
1929 (100_000, 0, Some(DEFAULT_READ_BATCH_SIZE / 4)),
1930 ] {
1931 let mut meta = new_test_file(num_rows, num_series).meta_ref().clone();
1932 meta.level = 0;
1933 let file = FileHandle::new(meta, Arc::new(NoopFilePurger));
1934 let stream_ctx = new_test_stream_ctx(vec![file], vec![]).await;
1935 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
1936 index: 0,
1937 row_group_index: 0,
1938 }]);
1939
1940 assert_eq!(
1941 expected,
1942 should_split_flat_batches_for_merge(&stream_ctx, &range_meta),
1943 "rows={num_rows}, series={num_series}",
1944 );
1945 }
1946 }
1947
1948 #[tokio::test]
1949 async fn test_should_split_flat_batches_for_merge_falls_back_to_memtables() {
1950 let stream_ctx = new_test_stream_ctx(vec![], vec![new_test_memtable(5_000, 100)]).await;
1951 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
1952 index: 0,
1953 row_group_index: 0,
1954 }]);
1955
1956 assert_eq!(
1957 Some(50),
1958 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
1959 );
1960 }
1961
1962 #[tokio::test]
1963 async fn test_should_split_flat_batches_for_merge_clamps_estimate() {
1964 let stream_ctx =
1965 new_test_stream_ctx(vec![new_test_file(SPLIT_ROW_THRESHOLD * 2, 1)], vec![]).await;
1966 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
1967 index: 0,
1968 row_group_index: 0,
1969 }]);
1970
1971 assert_eq!(
1972 Some(DEFAULT_READ_BATCH_SIZE),
1973 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
1974 );
1975 }
1976
1977 #[test]
1978 fn test_compute_parallel_channel_size_clamps_to_max_for_small_batches() {
1979 assert_eq!(64, compute_parallel_channel_size(0));
1980 assert_eq!(64, compute_parallel_channel_size(1));
1981 }
1982
1983 #[test]
1984 fn test_compute_parallel_channel_size_returns_expected_mid_range_size() {
1985 assert_eq!(
1986 4,
1987 compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE / 2)
1988 );
1989 }
1990
1991 #[test]
1992 fn test_compute_parallel_channel_size_clamps_to_min_for_large_batches() {
1993 assert_eq!(2, compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE));
1994 assert_eq!(
1995 2,
1996 compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE * 2)
1997 );
1998 }
1999
2000 #[test]
2001 fn test_compute_average_batch_size_uses_arithmetic_mean() {
2002 assert_eq!(24, compute_average_batch_size([16, 24, 32]));
2003 }
2004
2005 #[test]
2006 fn test_compute_average_batch_size_clamps_values() {
2007 assert_eq!(
2008 DEFAULT_READ_BATCH_SIZE,
2009 compute_average_batch_size([DEFAULT_READ_BATCH_SIZE, DEFAULT_READ_BATCH_SIZE * 2])
2010 );
2011 assert_eq!(1, compute_average_batch_size([0, 1]));
2012 }
2013
2014 #[test]
2015 fn test_compute_average_batch_size_falls_back_when_empty() {
2016 assert_eq!(
2017 DEFAULT_READ_BATCH_SIZE,
2018 compute_average_batch_size(std::iter::empty())
2019 );
2020 }
2021
2022 fn flat_ts_batch(timestamps: &[i64]) -> RecordBatch {
2024 use datatypes::arrow::array::{TimestampMillisecondArray, UInt8Array, UInt64Array};
2025 use datatypes::arrow::datatypes::{DataType, Field, Schema, TimeUnit};
2026
2027 let num_rows = timestamps.len();
2028 let schema = Arc::new(Schema::new(vec![
2029 Field::new(
2030 "ts",
2031 DataType::Timestamp(TimeUnit::Millisecond, None),
2032 false,
2033 ),
2034 Field::new("pk", DataType::UInt64, false),
2035 Field::new("seq", DataType::UInt64, false),
2036 Field::new("op", DataType::UInt8, false),
2037 ]));
2038 RecordBatch::try_new(
2039 schema,
2040 vec![
2041 Arc::new(TimestampMillisecondArray::from(timestamps.to_vec())),
2042 Arc::new(UInt64Array::from(vec![0u64; num_rows])),
2043 Arc::new(UInt64Array::from(vec![0u64; num_rows])),
2044 Arc::new(UInt8Array::from(vec![0u8; num_rows])),
2045 ],
2046 )
2047 .unwrap()
2048 }
2049
2050 fn split_ts(timestamps: &[i64]) -> Vec<Vec<i64>> {
2052 let mut batches = VecDeque::new();
2053 split_record_batch(flat_ts_batch(timestamps), &mut batches);
2054 batches
2055 .iter()
2056 .map(|batch| {
2057 let pos = time_index_column_index(batch.num_columns());
2058 let (values, _) = timestamp_array_to_primitive(batch.column(pos)).unwrap();
2059 values.values().to_vec()
2060 })
2061 .collect()
2062 }
2063
2064 #[test]
2065 fn test_split_record_batch_on_equal_timestamps() {
2066 assert_eq!(
2068 split_ts(&[1, 2, 2, 3, 1]),
2069 vec![vec![1, 2], vec![2, 3], vec![1]]
2070 );
2071 assert_eq!(split_ts(&[5, 5, 5]), vec![vec![5], vec![5], vec![5]]);
2073 assert_eq!(split_ts(&[5, 5, 1, 2]), vec![vec![5], vec![5], vec![1, 2]]);
2075 assert_eq!(split_ts(&[1, 2, 5, 5]), vec![vec![1, 2, 5], vec![5]]);
2077 }
2078
2079 #[test]
2080 fn test_split_record_batch_on_decreasing_timestamps() {
2081 assert_eq!(split_ts(&[1, 2, 3]), vec![vec![1, 2, 3]]);
2082 assert_eq!(split_ts(&[1, 3, 2, 4]), vec![vec![1, 3], vec![2, 4]]);
2083 }
2084
2085 #[test]
2086 fn test_split_record_batch_empty_and_single_row() {
2087 let mut batches = VecDeque::new();
2088 split_record_batch(flat_ts_batch(&[]), &mut batches);
2089 assert!(batches.is_empty());
2090
2091 assert_eq!(split_ts(&[42]), vec![vec![42]]);
2092 }
2093}
2094
2095#[cfg(test)]
2096mod sequence_filter_tests {
2097 use std::sync::Arc;
2098
2099 use datatypes::arrow::array::{Int64Array, StringArray, UInt8Array, UInt64Array};
2100 use datatypes::arrow::datatypes::{DataType, Field, Schema};
2101 use datatypes::arrow::record_batch::RecordBatch;
2102 use store_api::storage::SequenceRange;
2103
2104 use super::filter_flat_batch_by_sequence;
2105
2106 fn batch(sequences: &[u64]) -> RecordBatch {
2108 let schema = Arc::new(Schema::new(vec![
2109 Field::new("tag_0", DataType::Utf8, false),
2110 Field::new("field_0", DataType::Int64, false),
2111 Field::new(
2112 "ts",
2113 DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Millisecond, None),
2114 false,
2115 ),
2116 Field::new("__primary_key", DataType::UInt8, false),
2117 Field::new("__sequence", DataType::UInt64, false),
2118 Field::new("__op_type", DataType::UInt8, false),
2119 ]));
2120 let tags = StringArray::from_iter_values((0..sequences.len()).map(|i| i.to_string()));
2121 let fields = Int64Array::from_iter_values(0..sequences.len() as i64);
2122 let ts = datatypes::arrow::array::TimestampMillisecondArray::from_iter_values(
2123 (0..sequences.len()).map(|i| i as i64 * 1000),
2124 );
2125 let pk = UInt8Array::from(vec![0u8; sequences.len()]);
2126 let seq = UInt64Array::from_iter_values(sequences.iter().copied());
2127 let op = UInt8Array::from(vec![0u8; sequences.len()]);
2128 RecordBatch::try_new(
2129 schema,
2130 vec![
2131 Arc::new(tags),
2132 Arc::new(fields),
2133 Arc::new(ts),
2134 Arc::new(pk),
2135 Arc::new(seq),
2136 Arc::new(op),
2137 ],
2138 )
2139 .unwrap()
2140 }
2141
2142 fn remaining_tags(batch: &RecordBatch) -> Vec<String> {
2143 batch
2144 .column(0)
2145 .as_any()
2146 .downcast_ref::<StringArray>()
2147 .unwrap()
2148 .iter()
2149 .map(|v| v.unwrap().to_string())
2150 .collect()
2151 }
2152
2153 #[test]
2154 fn test_filter_flat_batch_by_sequence_no_range_or_legacy_file() {
2155 let b = batch(&[1, 2, 3, 4]);
2156
2157 let out = filter_flat_batch_by_sequence(b.clone(), None, true).unwrap();
2158 assert_eq!(remaining_tags(&out.unwrap()), vec!["0", "1", "2", "3"]);
2159
2160 let out = filter_flat_batch_by_sequence(
2161 b.clone(),
2162 Some(SequenceRange::GtLtEq { min: 2, max: 3 }),
2163 false,
2164 )
2165 .unwrap();
2166 assert_eq!(remaining_tags(&out.unwrap()), vec!["0", "1", "2", "3"]);
2167 }
2168
2169 #[test]
2170 fn test_filter_flat_batch_by_sequence_exact_range() {
2171 let b = batch(&[1, 2, 3, 4]);
2172 let out = filter_flat_batch_by_sequence(
2173 b.clone(),
2174 Some(SequenceRange::GtLtEq { min: 2, max: 3 }),
2175 true,
2176 )
2177 .unwrap();
2178 assert_eq!(remaining_tags(&out.unwrap()), vec!["2"]);
2179
2180 let out = filter_flat_batch_by_sequence(
2181 b.clone(),
2182 Some(SequenceRange::GtLtEq { min: 10, max: 20 }),
2183 true,
2184 )
2185 .unwrap();
2186 assert!(out.is_none());
2187
2188 let empty = batch(&[]);
2189 let out = filter_flat_batch_by_sequence(
2190 empty,
2191 Some(SequenceRange::GtLtEq { min: 0, max: 10 }),
2192 true,
2193 )
2194 .unwrap();
2195 assert_eq!(out.unwrap().num_rows(), 0);
2196
2197 let out = filter_flat_batch_by_sequence(
2200 batch(&[1, 5, 9]),
2201 Some(SequenceRange::GtLtEq { min: 2, max: 8 }),
2202 true,
2203 )
2204 .unwrap();
2205 assert_eq!(remaining_tags(&out.unwrap()), vec!["1"]);
2206 }
2207}