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;
29#[cfg(feature = "enterprise")]
30use futures::TryStreamExt;
31use futures::{Stream, StreamExt};
32use prometheus::IntGauge;
33use smallvec::SmallVec;
34use snafu::ResultExt;
35use store_api::storage::{RegionId, SequenceRange, TimeSeriesRowSelector};
36
37use crate::cache::range_cache::RangeCacheMetricsReport;
38use crate::error::{ComputeArrowSnafu, Result};
39use crate::memtable::MemScanMetrics;
40use crate::metrics::{
41 IN_PROGRESS_SCAN, PRECISE_FILTER_ROWS_TOTAL, READ_BATCHES_RETURN, READ_ROW_GROUPS_TOTAL,
42 READ_ROWS_IN_ROW_GROUP_TOTAL, READ_ROWS_RETURN, READ_STAGE_ELAPSED,
43};
44use crate::read::dedup::{DedupMetrics, DedupMetricsReport};
45use crate::read::flat_merge::{MergeMetrics, MergeMetricsReport};
46use crate::read::last_row::FlatLastTimestampSelector;
47use crate::read::pruner::PartitionPruner;
48use crate::read::range::{RangeMeta, RowGroupIndex};
49use crate::read::scan_region::StreamContext;
50use crate::read::{BoxedRecordBatchStream, ScannerMetrics};
51use crate::sst::file::{FileTimeRange, RegionFileId};
52use crate::sst::index::bloom_filter::applier::BloomFilterIndexApplyMetrics;
53use crate::sst::index::fulltext_index::applier::FulltextIndexApplyMetrics;
54use crate::sst::index::inverted_index::applier::InvertedIndexApplyMetrics;
55use crate::sst::parquet::file_range::{FileRange, PreFilterMode};
56use crate::sst::parquet::flat_format::{sequence_column_index, time_index_column_index};
57use crate::sst::parquet::json_align;
58use crate::sst::parquet::reader::{MetadataCacheMetrics, ReaderFilterMetrics, ReaderMetrics};
59use crate::sst::parquet::row_group::ParquetFetchMetrics;
60use crate::sst::parquet::{DEFAULT_READ_BATCH_SIZE, DEFAULT_ROW_GROUP_SIZE};
61
62#[derive(Default, Clone)]
64pub struct FileScanMetrics {
65 pub num_ranges: usize,
67 pub num_rows: usize,
69 pub build_part_cost: Duration,
71 pub build_reader_cost: Duration,
73 pub scan_cost: Duration,
75}
76
77impl fmt::Debug for FileScanMetrics {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{{\"build_part_cost\":\"{:?}\"", self.build_part_cost)?;
80
81 if self.num_ranges > 0 {
82 write!(f, ", \"num_ranges\":{}", self.num_ranges)?;
83 }
84 if self.num_rows > 0 {
85 write!(f, ", \"num_rows\":{}", self.num_rows)?;
86 }
87 if !self.build_reader_cost.is_zero() {
88 write!(
89 f,
90 ", \"build_reader_cost\":\"{:?}\"",
91 self.build_reader_cost
92 )?;
93 }
94 if !self.scan_cost.is_zero() {
95 write!(f, ", \"scan_cost\":\"{:?}\"", self.scan_cost)?;
96 }
97
98 write!(f, "}}")
99 }
100}
101
102impl FileScanMetrics {
103 pub(crate) fn merge_from(&mut self, other: &FileScanMetrics) {
105 self.num_ranges += other.num_ranges;
106 self.num_rows += other.num_rows;
107 self.build_part_cost += other.build_part_cost;
108 self.build_reader_cost += other.build_reader_cost;
109 self.scan_cost += other.scan_cost;
110 }
111}
112
113#[derive(Default)]
115pub(crate) struct ScanMetricsSet {
116 prepare_scan_cost: Duration,
118 build_reader_cost: Duration,
120 scan_cost: Duration,
122 yield_cost: Duration,
124 convert_cost: Option<Time>,
126 total_cost: Duration,
128 num_rows: usize,
130 num_batches: usize,
132 num_mem_ranges: usize,
134 num_file_ranges: usize,
136
137 mem_scan_cost: Duration,
140 mem_rows: usize,
142 mem_batches: usize,
144 mem_series: usize,
146 mem_prefilter_cost: Duration,
148 mem_prefilter_rows_filtered: usize,
150
151 build_parts_cost: Duration,
154 sst_scan_cost: Duration,
156 rg_total: usize,
158 rg_fulltext_filtered: usize,
160 rg_inverted_filtered: usize,
162 rg_minmax_filtered: usize,
164 rg_bloom_filtered: usize,
166 rows_before_filter: usize,
168 rows_fulltext_filtered: usize,
170 rows_inverted_filtered: usize,
172 rows_bloom_filtered: usize,
174 rows_precise_filtered: usize,
176 fulltext_index_cache_hit: usize,
178 fulltext_index_cache_miss: usize,
180 inverted_index_cache_hit: usize,
182 inverted_index_cache_miss: usize,
184 bloom_filter_cache_hit: usize,
186 bloom_filter_cache_miss: usize,
188 minmax_cache_hit: usize,
190 minmax_cache_miss: usize,
192 pruner_cache_hit: usize,
194 pruner_cache_miss: usize,
196 pruner_prune_cost: Duration,
198 files_time_range_pruned: usize,
200 num_sst_record_batches: usize,
202 num_sst_batches: usize,
204 num_sst_rows: usize,
206
207 first_poll: Duration,
209
210 num_series_send_full: usize,
212 num_distributor_rows: usize,
214 num_distributor_batches: usize,
216 distributor_scan_cost: Duration,
218 distributor_yield_cost: Duration,
220 distributor_divider_cost: Duration,
222
223 merge_metrics: MergeMetrics,
225 dedup_metrics: DedupMetrics,
227
228 stream_eof: bool,
230
231 inverted_index_apply_metrics: Option<InvertedIndexApplyMetrics>,
234 bloom_filter_apply_metrics: Option<BloomFilterIndexApplyMetrics>,
236 fulltext_index_apply_metrics: Option<FulltextIndexApplyMetrics>,
238 fetch_metrics: Option<ParquetFetchMetrics>,
240 metadata_cache_metrics: Option<MetadataCacheMetrics>,
242 per_file_metrics: Option<HashMap<RegionFileId, FileScanMetrics>>,
244
245 build_ranges_mem_size: isize,
247 build_ranges_peak_mem_size: isize,
249 num_range_builders: isize,
251 num_peak_range_builders: isize,
253 range_cache_size: usize,
255 range_cache_hit: usize,
257 range_cache_miss: usize,
259}
260
261struct CompareCostReverse<'a> {
264 total_cost: Duration,
265 file_id: RegionFileId,
266 metrics: &'a FileScanMetrics,
267}
268
269impl Ord for CompareCostReverse<'_> {
270 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
271 other.total_cost.cmp(&self.total_cost)
273 }
274}
275
276impl PartialOrd for CompareCostReverse<'_> {
277 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278 Some(self.cmp(other))
279 }
280}
281
282impl Eq for CompareCostReverse<'_> {}
283
284impl PartialEq for CompareCostReverse<'_> {
285 fn eq(&self, other: &Self) -> bool {
286 self.total_cost == other.total_cost
287 }
288}
289
290impl fmt::Debug for ScanMetricsSet {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 let ScanMetricsSet {
293 prepare_scan_cost,
294 build_reader_cost,
295 scan_cost,
296 yield_cost,
297 convert_cost,
298 total_cost,
299 num_rows,
300 num_batches,
301 num_mem_ranges,
302 num_file_ranges,
303 build_parts_cost,
304 sst_scan_cost,
305 rg_total,
306 rg_fulltext_filtered,
307 rg_inverted_filtered,
308 rg_minmax_filtered,
309 rg_bloom_filtered,
310 rows_before_filter,
311 rows_fulltext_filtered,
312 rows_inverted_filtered,
313 rows_bloom_filtered,
314 rows_precise_filtered,
315 fulltext_index_cache_hit,
316 fulltext_index_cache_miss,
317 inverted_index_cache_hit,
318 inverted_index_cache_miss,
319 bloom_filter_cache_hit,
320 bloom_filter_cache_miss,
321 minmax_cache_hit,
322 minmax_cache_miss,
323 pruner_cache_hit,
324 pruner_cache_miss,
325 pruner_prune_cost,
326 files_time_range_pruned,
327 num_sst_record_batches,
328 num_sst_batches,
329 num_sst_rows,
330 first_poll,
331 num_series_send_full,
332 num_distributor_rows,
333 num_distributor_batches,
334 distributor_scan_cost,
335 distributor_yield_cost,
336 distributor_divider_cost,
337 merge_metrics,
338 dedup_metrics,
339 stream_eof,
340 mem_scan_cost,
341 mem_rows,
342 mem_batches,
343 mem_series,
344 mem_prefilter_cost,
345 mem_prefilter_rows_filtered,
346 inverted_index_apply_metrics,
347 bloom_filter_apply_metrics,
348 fulltext_index_apply_metrics,
349 fetch_metrics,
350 metadata_cache_metrics,
351 per_file_metrics,
352 build_ranges_mem_size: _,
353 build_ranges_peak_mem_size,
354 num_range_builders: _,
355 num_peak_range_builders,
356 range_cache_size,
357 range_cache_hit,
358 range_cache_miss,
359 } = self;
360
361 write!(
363 f,
364 "{{\"prepare_scan_cost\":\"{prepare_scan_cost:?}\", \
365 \"build_reader_cost\":\"{build_reader_cost:?}\", \
366 \"scan_cost\":\"{scan_cost:?}\", \
367 \"yield_cost\":\"{yield_cost:?}\", \
368 \"total_cost\":\"{total_cost:?}\", \
369 \"num_rows\":{num_rows}, \
370 \"num_batches\":{num_batches}, \
371 \"num_mem_ranges\":{num_mem_ranges}, \
372 \"num_file_ranges\":{num_file_ranges}, \
373 \"build_parts_cost\":\"{build_parts_cost:?}\", \
374 \"sst_scan_cost\":\"{sst_scan_cost:?}\", \
375 \"rg_total\":{rg_total}, \
376 \"rows_before_filter\":{rows_before_filter}, \
377 \"num_sst_record_batches\":{num_sst_record_batches}, \
378 \"num_sst_batches\":{num_sst_batches}, \
379 \"num_sst_rows\":{num_sst_rows}, \
380 \"first_poll\":\"{first_poll:?}\""
381 )?;
382
383 if let Some(time) = convert_cost {
385 let duration = Duration::from_nanos(time.value() as u64);
386 write!(f, ", \"convert_cost\":\"{duration:?}\"")?;
387 }
388
389 if *files_time_range_pruned > 0 {
391 write!(f, ", \"files_time_range_pruned\":{files_time_range_pruned}")?;
392 }
393 if *rg_fulltext_filtered > 0 {
394 write!(f, ", \"rg_fulltext_filtered\":{rg_fulltext_filtered}")?;
395 }
396 if *rg_inverted_filtered > 0 {
397 write!(f, ", \"rg_inverted_filtered\":{rg_inverted_filtered}")?;
398 }
399 if *rg_minmax_filtered > 0 {
400 write!(f, ", \"rg_minmax_filtered\":{rg_minmax_filtered}")?;
401 }
402 if *rg_bloom_filtered > 0 {
403 write!(f, ", \"rg_bloom_filtered\":{rg_bloom_filtered}")?;
404 }
405 if *rows_fulltext_filtered > 0 {
406 write!(f, ", \"rows_fulltext_filtered\":{rows_fulltext_filtered}")?;
407 }
408 if *rows_inverted_filtered > 0 {
409 write!(f, ", \"rows_inverted_filtered\":{rows_inverted_filtered}")?;
410 }
411 if *rows_bloom_filtered > 0 {
412 write!(f, ", \"rows_bloom_filtered\":{rows_bloom_filtered}")?;
413 }
414 if *rows_precise_filtered > 0 {
415 write!(f, ", \"rows_precise_filtered\":{rows_precise_filtered}")?;
416 }
417 if *fulltext_index_cache_hit > 0 {
418 write!(
419 f,
420 ", \"fulltext_index_cache_hit\":{fulltext_index_cache_hit}"
421 )?;
422 }
423 if *fulltext_index_cache_miss > 0 {
424 write!(
425 f,
426 ", \"fulltext_index_cache_miss\":{fulltext_index_cache_miss}"
427 )?;
428 }
429 if *inverted_index_cache_hit > 0 {
430 write!(
431 f,
432 ", \"inverted_index_cache_hit\":{inverted_index_cache_hit}"
433 )?;
434 }
435 if *inverted_index_cache_miss > 0 {
436 write!(
437 f,
438 ", \"inverted_index_cache_miss\":{inverted_index_cache_miss}"
439 )?;
440 }
441 if *bloom_filter_cache_hit > 0 {
442 write!(f, ", \"bloom_filter_cache_hit\":{bloom_filter_cache_hit}")?;
443 }
444 if *bloom_filter_cache_miss > 0 {
445 write!(f, ", \"bloom_filter_cache_miss\":{bloom_filter_cache_miss}")?;
446 }
447 if *minmax_cache_hit > 0 {
448 write!(f, ", \"minmax_cache_hit\":{minmax_cache_hit}")?;
449 }
450 if *minmax_cache_miss > 0 {
451 write!(f, ", \"minmax_cache_miss\":{minmax_cache_miss}")?;
452 }
453 if *pruner_cache_hit > 0 {
454 write!(f, ", \"pruner_cache_hit\":{pruner_cache_hit}")?;
455 }
456 if *pruner_cache_miss > 0 {
457 write!(f, ", \"pruner_cache_miss\":{pruner_cache_miss}")?;
458 }
459 if !pruner_prune_cost.is_zero() {
460 write!(f, ", \"pruner_prune_cost\":\"{pruner_prune_cost:?}\"")?;
461 }
462
463 if *num_series_send_full > 0 {
465 write!(f, ", \"num_series_send_full\":{num_series_send_full}")?;
466 }
467 if *num_distributor_rows > 0 {
468 write!(f, ", \"num_distributor_rows\":{num_distributor_rows}")?;
469 }
470 if *num_distributor_batches > 0 {
471 write!(f, ", \"num_distributor_batches\":{num_distributor_batches}")?;
472 }
473 if !distributor_scan_cost.is_zero() {
474 write!(
475 f,
476 ", \"distributor_scan_cost\":\"{distributor_scan_cost:?}\""
477 )?;
478 }
479 if !distributor_yield_cost.is_zero() {
480 write!(
481 f,
482 ", \"distributor_yield_cost\":\"{distributor_yield_cost:?}\""
483 )?;
484 }
485 if !distributor_divider_cost.is_zero() {
486 write!(
487 f,
488 ", \"distributor_divider_cost\":\"{distributor_divider_cost:?}\""
489 )?;
490 }
491
492 if *mem_rows > 0 {
494 write!(f, ", \"mem_rows\":{mem_rows}")?;
495 }
496 if *mem_batches > 0 {
497 write!(f, ", \"mem_batches\":{mem_batches}")?;
498 }
499 if *mem_series > 0 {
500 write!(f, ", \"mem_series\":{mem_series}")?;
501 }
502 if !mem_scan_cost.is_zero() {
503 write!(f, ", \"mem_scan_cost\":\"{mem_scan_cost:?}\"")?;
504 }
505 if !mem_prefilter_cost.is_zero() {
506 write!(f, ", \"mem_prefilter_cost\":\"{mem_prefilter_cost:?}\"")?;
507 }
508 if *mem_prefilter_rows_filtered > 0 {
509 write!(
510 f,
511 ", \"mem_prefilter_rows_filtered\":{mem_prefilter_rows_filtered}"
512 )?;
513 }
514
515 if let Some(metrics) = inverted_index_apply_metrics
517 && !metrics.is_empty()
518 {
519 write!(f, ", \"inverted_index_apply_metrics\":{:?}", metrics)?;
520 }
521 if let Some(metrics) = bloom_filter_apply_metrics
522 && !metrics.is_empty()
523 {
524 write!(f, ", \"bloom_filter_apply_metrics\":{:?}", metrics)?;
525 }
526 if let Some(metrics) = fulltext_index_apply_metrics
527 && !metrics.is_empty()
528 {
529 write!(f, ", \"fulltext_index_apply_metrics\":{:?}", metrics)?;
530 }
531 if let Some(metrics) = fetch_metrics
532 && !metrics.is_empty()
533 {
534 write!(f, ", \"fetch_metrics\":{:?}", metrics)?;
535 }
536 if let Some(metrics) = metadata_cache_metrics
537 && !metrics.is_empty()
538 {
539 write!(f, ", \"metadata_cache_metrics\":{:?}", metrics)?;
540 }
541
542 if !merge_metrics.scan_cost.is_zero() {
544 write!(f, ", \"merge_metrics\":{:?}", merge_metrics)?;
545 }
546
547 if !dedup_metrics.dedup_cost.is_zero() {
549 write!(f, ", \"dedup_metrics\":{:?}", dedup_metrics)?;
550 }
551
552 if let Some(file_metrics) = per_file_metrics
554 && !file_metrics.is_empty()
555 {
556 let mut heap = BinaryHeap::new();
558 for (file_id, metrics) in file_metrics.iter() {
559 let total_cost =
560 metrics.build_part_cost + metrics.build_reader_cost + metrics.scan_cost;
561
562 if total_cost.is_zero() && metrics.num_ranges == 0 {
565 continue;
566 }
567
568 if heap.len() < 10 {
569 heap.push(CompareCostReverse {
571 total_cost,
572 file_id: *file_id,
573 metrics,
574 });
575 } else if let Some(min_entry) = heap.peek() {
576 if total_cost > min_entry.total_cost {
578 heap.pop();
579 heap.push(CompareCostReverse {
580 total_cost,
581 file_id: *file_id,
582 metrics,
583 });
584 }
585 }
586 }
587
588 let top_files = heap.into_sorted_vec();
589 write!(f, ", \"top_file_metrics\": {{")?;
590 for (i, item) in top_files.iter().enumerate() {
591 let CompareCostReverse {
592 total_cost: _,
593 file_id,
594 metrics,
595 } = item;
596 if i > 0 {
597 write!(f, ", ")?;
598 }
599 write!(f, "\"{}\": {:?}", file_id, metrics)?;
600 }
601 write!(f, "}}")?;
602 }
603
604 if *range_cache_size > 0 {
605 write!(f, ", \"range_cache_size\":{range_cache_size}")?;
606 }
607 if *range_cache_hit > 0 {
608 write!(f, ", \"range_cache_hit\":{range_cache_hit}")?;
609 }
610 if *range_cache_miss > 0 {
611 write!(f, ", \"range_cache_miss\":{range_cache_miss}")?;
612 }
613
614 write!(
615 f,
616 ", \"build_ranges_peak_mem_size\":{build_ranges_peak_mem_size}, \
617 \"num_peak_range_builders\":{num_peak_range_builders}, \
618 \"stream_eof\":{stream_eof}}}"
619 )
620 }
621}
622impl ScanMetricsSet {
623 fn with_prepare_scan_cost(mut self, cost: Duration) -> Self {
625 self.prepare_scan_cost += cost;
626 self
627 }
628
629 fn with_convert_cost(mut self, time: Time) -> Self {
631 self.convert_cost = Some(time);
632 self
633 }
634
635 fn merge_scanner_metrics(&mut self, other: &ScannerMetrics) {
637 let ScannerMetrics {
638 scan_cost,
639 yield_cost,
640 num_batches,
641 num_rows,
642 } = other;
643
644 self.scan_cost += *scan_cost;
645 self.yield_cost += *yield_cost;
646 self.num_rows += *num_rows;
647 self.num_batches += *num_batches;
648 }
649
650 fn merge_reader_metrics(&mut self, other: &ReaderMetrics) {
652 let ReaderMetrics {
653 build_cost,
654 filter_metrics:
655 ReaderFilterMetrics {
656 rg_total,
657 rg_fulltext_filtered,
658 rg_inverted_filtered,
659 rg_minmax_filtered,
660 rg_bloom_filtered,
661 rows_total,
662 rows_fulltext_filtered,
663 rows_inverted_filtered,
664 rows_bloom_filtered,
665 rows_precise_filtered,
666 fulltext_index_cache_hit,
667 fulltext_index_cache_miss,
668 inverted_index_cache_hit,
669 inverted_index_cache_miss,
670 bloom_filter_cache_hit,
671 bloom_filter_cache_miss,
672 minmax_cache_hit,
673 minmax_cache_miss,
674 pruner_cache_hit,
675 pruner_cache_miss,
676 pruner_prune_cost,
677 files_time_range_pruned,
678 inverted_index_apply_metrics,
679 bloom_filter_apply_metrics,
680 fulltext_index_apply_metrics,
681 },
682 num_record_batches,
683 num_batches,
684 num_rows,
685 scan_cost,
686 metadata_cache_metrics,
687 fetch_metrics,
688 metadata_mem_size,
689 num_range_builders,
690 } = other;
691
692 self.build_parts_cost += *build_cost;
693 self.sst_scan_cost += *scan_cost;
694
695 self.files_time_range_pruned += *files_time_range_pruned;
696
697 self.rg_total += *rg_total;
698 self.rg_fulltext_filtered += *rg_fulltext_filtered;
699 self.rg_inverted_filtered += *rg_inverted_filtered;
700 self.rg_minmax_filtered += *rg_minmax_filtered;
701 self.rg_bloom_filtered += *rg_bloom_filtered;
702
703 self.rows_before_filter += *rows_total;
704 self.rows_fulltext_filtered += *rows_fulltext_filtered;
705 self.rows_inverted_filtered += *rows_inverted_filtered;
706 self.rows_bloom_filtered += *rows_bloom_filtered;
707 self.rows_precise_filtered += *rows_precise_filtered;
708
709 self.fulltext_index_cache_hit += *fulltext_index_cache_hit;
710 self.fulltext_index_cache_miss += *fulltext_index_cache_miss;
711 self.inverted_index_cache_hit += *inverted_index_cache_hit;
712 self.inverted_index_cache_miss += *inverted_index_cache_miss;
713 self.bloom_filter_cache_hit += *bloom_filter_cache_hit;
714 self.bloom_filter_cache_miss += *bloom_filter_cache_miss;
715 self.minmax_cache_hit += *minmax_cache_hit;
716 self.minmax_cache_miss += *minmax_cache_miss;
717 self.pruner_cache_hit += *pruner_cache_hit;
718 self.pruner_cache_miss += *pruner_cache_miss;
719 self.pruner_prune_cost += *pruner_prune_cost;
720
721 self.num_sst_record_batches += *num_record_batches;
722 self.num_sst_batches += *num_batches;
723 self.num_sst_rows += *num_rows;
724
725 if let Some(metrics) = inverted_index_apply_metrics {
727 self.inverted_index_apply_metrics
728 .get_or_insert_with(InvertedIndexApplyMetrics::default)
729 .merge_from(metrics);
730 }
731 if let Some(metrics) = bloom_filter_apply_metrics {
732 self.bloom_filter_apply_metrics
733 .get_or_insert_with(BloomFilterIndexApplyMetrics::default)
734 .merge_from(metrics);
735 }
736 if let Some(metrics) = fulltext_index_apply_metrics {
737 self.fulltext_index_apply_metrics
738 .get_or_insert_with(FulltextIndexApplyMetrics::default)
739 .merge_from(metrics);
740 }
741 if let Some(metrics) = fetch_metrics {
742 self.fetch_metrics
743 .get_or_insert_with(ParquetFetchMetrics::default)
744 .merge_from(metrics);
745 }
746 self.metadata_cache_metrics
747 .get_or_insert_with(MetadataCacheMetrics::default)
748 .merge_from(metadata_cache_metrics);
749
750 self.build_ranges_mem_size += *metadata_mem_size;
752 if self.build_ranges_mem_size > self.build_ranges_peak_mem_size {
753 self.build_ranges_peak_mem_size = self.build_ranges_mem_size;
754 }
755
756 self.num_range_builders += *num_range_builders;
758 if self.num_range_builders > self.num_peak_range_builders {
759 self.num_peak_range_builders = self.num_range_builders;
760 }
761 }
762
763 fn merge_per_file_metrics(&mut self, other: &HashMap<RegionFileId, FileScanMetrics>) {
765 let self_file_metrics = self.per_file_metrics.get_or_insert_with(HashMap::new);
766 for (file_id, metrics) in other {
767 self_file_metrics
768 .entry(*file_id)
769 .or_default()
770 .merge_from(metrics);
771 }
772 }
773
774 fn set_distributor_metrics(&mut self, distributor_metrics: &SeriesDistributorMetrics) {
776 let SeriesDistributorMetrics {
777 num_series_send_full,
778 num_rows,
779 num_batches,
780 scan_cost,
781 yield_cost,
782 divider_cost,
783 } = distributor_metrics;
784
785 self.num_series_send_full += *num_series_send_full;
786 self.num_distributor_rows += *num_rows;
787 self.num_distributor_batches += *num_batches;
788 self.distributor_scan_cost += *scan_cost;
789 self.distributor_yield_cost += *yield_cost;
790 self.distributor_divider_cost += *divider_cost;
791 }
792
793 fn observe_metrics(&self) {
795 READ_STAGE_ELAPSED
796 .with_label_values(&["prepare_scan"])
797 .observe(self.prepare_scan_cost.as_secs_f64());
798 READ_STAGE_ELAPSED
799 .with_label_values(&["build_reader"])
800 .observe(self.build_reader_cost.as_secs_f64());
801 READ_STAGE_ELAPSED
802 .with_label_values(&["scan"])
803 .observe(self.scan_cost.as_secs_f64());
804 READ_STAGE_ELAPSED
805 .with_label_values(&["yield"])
806 .observe(self.yield_cost.as_secs_f64());
807 if let Some(time) = &self.convert_cost {
808 READ_STAGE_ELAPSED
809 .with_label_values(&["convert"])
810 .observe(Duration::from_nanos(time.value() as u64).as_secs_f64());
811 }
812 READ_STAGE_ELAPSED
813 .with_label_values(&["total"])
814 .observe(self.total_cost.as_secs_f64());
815 READ_ROWS_RETURN.observe(self.num_rows as f64);
816 READ_BATCHES_RETURN.observe(self.num_batches as f64);
817
818 READ_STAGE_ELAPSED
819 .with_label_values(&["build_parts"])
820 .observe(self.build_parts_cost.as_secs_f64());
821
822 READ_ROW_GROUPS_TOTAL
823 .with_label_values(&["before_filtering"])
824 .inc_by(self.rg_total as u64);
825 READ_ROW_GROUPS_TOTAL
826 .with_label_values(&["fulltext_index_filtered"])
827 .inc_by(self.rg_fulltext_filtered as u64);
828 READ_ROW_GROUPS_TOTAL
829 .with_label_values(&["inverted_index_filtered"])
830 .inc_by(self.rg_inverted_filtered as u64);
831 READ_ROW_GROUPS_TOTAL
832 .with_label_values(&["minmax_index_filtered"])
833 .inc_by(self.rg_minmax_filtered as u64);
834 READ_ROW_GROUPS_TOTAL
835 .with_label_values(&["bloom_filter_index_filtered"])
836 .inc_by(self.rg_bloom_filtered as u64);
837
838 PRECISE_FILTER_ROWS_TOTAL
839 .with_label_values(&["parquet"])
840 .inc_by(self.rows_precise_filtered as u64);
841 READ_ROWS_IN_ROW_GROUP_TOTAL
842 .with_label_values(&["before_filtering"])
843 .inc_by(self.rows_before_filter as u64);
844 READ_ROWS_IN_ROW_GROUP_TOTAL
845 .with_label_values(&["fulltext_index_filtered"])
846 .inc_by(self.rows_fulltext_filtered as u64);
847 READ_ROWS_IN_ROW_GROUP_TOTAL
848 .with_label_values(&["inverted_index_filtered"])
849 .inc_by(self.rows_inverted_filtered as u64);
850 READ_ROWS_IN_ROW_GROUP_TOTAL
851 .with_label_values(&["bloom_filter_index_filtered"])
852 .inc_by(self.rows_bloom_filtered as u64);
853 }
854}
855
856struct PartitionMetricsInner {
857 region_id: RegionId,
858 partition: usize,
860 scanner_type: &'static str,
862 query_start: Instant,
864 explain_verbose: bool,
866 metrics: Mutex<ScanMetricsSet>,
868 in_progress_scan: IntGauge,
869
870 build_parts_cost: Time,
873 build_reader_cost: Time,
875 scan_cost: Time,
877 yield_cost: Time,
879 convert_cost: Time,
881 elapsed_compute: Time,
883}
884
885impl PartitionMetricsInner {
886 fn on_finish(&self, stream_eof: bool) {
887 let mut metrics = self.metrics.lock().unwrap();
888 if metrics.total_cost.is_zero() {
889 metrics.total_cost = self.query_start.elapsed();
890 }
891 if !metrics.stream_eof {
892 metrics.stream_eof = stream_eof;
893 }
894 }
895}
896
897impl MergeMetricsReport for PartitionMetricsInner {
898 fn report(&self, metrics: &mut MergeMetrics) {
899 let mut scan_metrics = self.metrics.lock().unwrap();
900 scan_metrics.merge_metrics.merge(metrics);
902
903 *metrics = MergeMetrics::default();
905 }
906}
907
908impl DedupMetricsReport for PartitionMetricsInner {
909 fn report(&self, metrics: &mut DedupMetrics) {
910 let mut scan_metrics = self.metrics.lock().unwrap();
911 scan_metrics.dedup_metrics.merge(metrics);
913
914 *metrics = DedupMetrics::default();
916 }
917}
918
919impl RangeCacheMetricsReport for PartitionMetricsInner {
920 fn inc_range_cache_size(&self, size: usize) {
922 let mut metrics = self.metrics.lock().unwrap();
923 metrics.range_cache_size += size;
924 }
925
926 fn inc_range_cache_hit(&self) {
928 let mut metrics = self.metrics.lock().unwrap();
929 metrics.range_cache_hit += 1;
930 }
931
932 fn inc_range_cache_miss(&self) {
934 let mut metrics = self.metrics.lock().unwrap();
935 metrics.range_cache_miss += 1;
936 }
937}
938
939impl Drop for PartitionMetricsInner {
940 fn drop(&mut self) {
941 self.on_finish(false);
942 let metrics = self.metrics.lock().unwrap();
943 metrics.observe_metrics();
944 self.in_progress_scan.dec();
945
946 if self.explain_verbose {
947 common_telemetry::info!(
948 "{} finished, region_id: {}, partition: {}, scan_metrics: {:?}",
949 self.scanner_type,
950 self.region_id,
951 self.partition,
952 metrics,
953 );
954 } else {
955 common_telemetry::debug!(
956 "{} finished, region_id: {}, partition: {}, scan_metrics: {:?}",
957 self.scanner_type,
958 self.region_id,
959 self.partition,
960 metrics,
961 );
962 }
963 }
964}
965
966#[derive(Default)]
968pub(crate) struct PartitionMetricsList(Mutex<Vec<Option<PartitionMetrics>>>);
969
970impl PartitionMetricsList {
971 pub(crate) fn set(&self, partition: usize, metrics: PartitionMetrics) {
973 let mut list = self.0.lock().unwrap();
974 if list.len() <= partition {
975 list.resize(partition + 1, None);
976 }
977 list[partition] = Some(metrics);
978 }
979
980 pub(crate) fn format_verbose_metrics(&self, f: &mut fmt::Formatter) -> fmt::Result {
982 let list = self.0.lock().unwrap();
983 write!(f, ", \"metrics_per_partition\": ")?;
984 f.debug_list()
985 .entries(list.iter().filter_map(|p| p.as_ref()))
986 .finish()?;
987 write!(f, "}}")
988 }
989}
990
991#[derive(Clone)]
993pub struct PartitionMetrics(Arc<PartitionMetricsInner>);
994
995impl PartitionMetrics {
996 pub(crate) fn new(
997 region_id: RegionId,
998 partition: usize,
999 scanner_type: &'static str,
1000 query_start: Instant,
1001 explain_verbose: bool,
1002 metrics_set: &ExecutionPlanMetricsSet,
1003 ) -> Self {
1004 let partition_str = partition.to_string();
1005 let in_progress_scan = IN_PROGRESS_SCAN.with_label_values(&[scanner_type, &partition_str]);
1006 in_progress_scan.inc();
1007 let convert_cost = MetricBuilder::new(metrics_set).subset_time("convert_cost", partition);
1008 let metrics = ScanMetricsSet::default()
1009 .with_prepare_scan_cost(query_start.elapsed())
1010 .with_convert_cost(convert_cost.clone());
1011 let inner = PartitionMetricsInner {
1012 region_id,
1013 partition,
1014 scanner_type,
1015 query_start,
1016 explain_verbose,
1017 metrics: Mutex::new(metrics),
1018 in_progress_scan,
1019 build_parts_cost: MetricBuilder::new(metrics_set)
1020 .subset_time("build_parts_cost", partition),
1021 build_reader_cost: MetricBuilder::new(metrics_set)
1022 .subset_time("build_reader_cost", partition),
1023 scan_cost: MetricBuilder::new(metrics_set).subset_time("scan_cost", partition),
1024 yield_cost: MetricBuilder::new(metrics_set).subset_time("yield_cost", partition),
1025 convert_cost,
1026 elapsed_compute: MetricBuilder::new(metrics_set).elapsed_compute(partition),
1027 };
1028 Self(Arc::new(inner))
1029 }
1030
1031 pub(crate) fn on_first_poll(&self) {
1032 let mut metrics = self.0.metrics.lock().unwrap();
1033 metrics.first_poll = self.0.query_start.elapsed();
1034 }
1035
1036 pub(crate) fn inc_num_mem_ranges(&self, num: usize) {
1037 let mut metrics = self.0.metrics.lock().unwrap();
1038 metrics.num_mem_ranges += num;
1039 }
1040
1041 pub fn inc_num_file_ranges(&self, num: usize) {
1042 let mut metrics = self.0.metrics.lock().unwrap();
1043 metrics.num_file_ranges += num;
1044 }
1045
1046 fn record_elapsed_compute(&self, duration: Duration) {
1047 if duration.is_zero() {
1048 return;
1049 }
1050 self.0.elapsed_compute.add_duration(duration);
1051 }
1052
1053 pub(crate) fn inc_build_reader_cost(&self, cost: Duration) {
1055 self.0.build_reader_cost.add_duration(cost);
1056
1057 let mut metrics = self.0.metrics.lock().unwrap();
1058 metrics.build_reader_cost += cost;
1059 }
1060
1061 pub(crate) fn inc_convert_batch_cost(&self, cost: Duration) {
1062 self.0.convert_cost.add_duration(cost);
1063 self.record_elapsed_compute(cost);
1064 }
1065
1066 pub(crate) fn report_mem_scan_metrics(&self, data: &crate::memtable::MemScanMetricsData) {
1068 let mut metrics = self.0.metrics.lock().unwrap();
1069 metrics.mem_scan_cost += data.scan_cost;
1070 metrics.mem_rows += data.num_rows;
1071 metrics.mem_batches += data.num_batches;
1072 metrics.mem_series += data.total_series;
1073 metrics.mem_prefilter_cost += data.prefilter_cost;
1074 metrics.mem_prefilter_rows_filtered += data.prefilter_rows_filtered;
1075 }
1076
1077 pub(crate) fn merge_metrics(&self, metrics: &ScannerMetrics) {
1079 self.0.scan_cost.add_duration(metrics.scan_cost);
1080 self.record_elapsed_compute(metrics.scan_cost);
1081 self.0.yield_cost.add_duration(metrics.yield_cost);
1082 self.record_elapsed_compute(metrics.yield_cost);
1083
1084 let mut metrics_set = self.0.metrics.lock().unwrap();
1085 metrics_set.merge_scanner_metrics(metrics);
1086 }
1087
1088 pub fn merge_reader_metrics(
1090 &self,
1091 metrics: &ReaderMetrics,
1092 per_file_metrics: Option<&HashMap<RegionFileId, FileScanMetrics>>,
1093 ) {
1094 self.0.build_parts_cost.add_duration(metrics.build_cost);
1095
1096 let mut metrics_set = self.0.metrics.lock().unwrap();
1097 metrics_set.merge_reader_metrics(metrics);
1098
1099 if let Some(file_metrics) = per_file_metrics {
1101 metrics_set.merge_per_file_metrics(file_metrics);
1102 }
1103 }
1104
1105 pub(crate) fn on_finish(&self) {
1107 self.0.on_finish(true);
1108 }
1109
1110 pub(crate) fn set_distributor_metrics(&self, metrics: &SeriesDistributorMetrics) {
1112 let mut metrics_set = self.0.metrics.lock().unwrap();
1113 metrics_set.set_distributor_metrics(metrics);
1114 }
1115
1116 pub(crate) fn explain_verbose(&self) -> bool {
1118 self.0.explain_verbose
1119 }
1120
1121 pub(crate) fn merge_metrics_reporter(&self) -> Arc<dyn MergeMetricsReport> {
1123 self.0.clone()
1124 }
1125
1126 pub(crate) fn dedup_metrics_reporter(&self) -> Arc<dyn DedupMetricsReport> {
1128 self.0.clone()
1129 }
1130
1131 pub(crate) fn range_cache_metrics_reporter(&self) -> Arc<dyn RangeCacheMetricsReport> {
1133 self.0.clone()
1134 }
1135}
1136
1137impl fmt::Debug for PartitionMetrics {
1138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1139 let metrics = self.0.metrics.lock().unwrap();
1140 write!(
1141 f,
1142 r#"{{"partition":{}, "metrics":{:?}}}"#,
1143 self.0.partition, metrics
1144 )
1145 }
1146}
1147
1148#[derive(Default)]
1150pub(crate) struct SeriesDistributorMetrics {
1151 pub(crate) num_series_send_full: usize,
1153 pub(crate) num_rows: usize,
1155 pub(crate) num_batches: usize,
1157 pub(crate) scan_cost: Duration,
1159 pub(crate) yield_cost: Duration,
1161 pub(crate) divider_cost: Duration,
1163}
1164
1165pub(crate) fn align_query_source_batch(
1167 context: &StreamContext,
1168 batch: RecordBatch,
1169) -> Result<RecordBatch> {
1170 let query_schema = context.input.mapper.input_arrow_schema(false);
1171 json_align::align_query_batch(batch, &query_schema)
1172}
1173
1174#[tracing::instrument(
1176 skip_all,
1177 fields(
1178 region_id = %stream_ctx.input.region_metadata().region_id,
1179 row_group_index = %index.index,
1180 source = "mem_flat"
1181 )
1182)]
1183pub(crate) fn scan_flat_mem_ranges(
1184 stream_ctx: Arc<StreamContext>,
1185 part_metrics: PartitionMetrics,
1186 index: RowGroupIndex,
1187 time_range: FileTimeRange,
1188) -> impl Stream<Item = Result<RecordBatch>> {
1189 try_stream! {
1190 let ranges = stream_ctx.input.build_mem_ranges(index);
1191 part_metrics.inc_num_mem_ranges(ranges.len());
1192 for range in ranges {
1193 let build_reader_start = Instant::now();
1194 let mem_scan_metrics = Some(MemScanMetrics::default());
1195 let mut iter = range.build_record_batch_iter(Some(time_range), mem_scan_metrics.clone())?;
1196 part_metrics.inc_build_reader_cost(build_reader_start.elapsed());
1197
1198 while let Some(record_batch) = iter.next().transpose()? {
1199 yield record_batch;
1200 }
1201
1202 if let Some(ref metrics) = mem_scan_metrics {
1204 let data = metrics.data();
1205 part_metrics.report_mem_scan_metrics(&data);
1206 }
1207 }
1208 }
1209}
1210
1211pub(crate) fn prepare_flat_mem_stream(
1220 context: Arc<StreamContext>,
1221 stream: impl Stream<Item = Result<RecordBatch>> + Send + 'static,
1222) -> BoxedRecordBatchStream {
1223 if context.input.read_cols.json_target_types().is_empty() {
1224 return Box::pin(stream);
1225 }
1226
1227 let select_last = context.input.append_mode
1228 && matches!(
1229 context.input.series_row_selector,
1230 Some(TimeSeriesRowSelector::LastRow { after_merge: false })
1231 );
1232 Box::pin(stream.map(move |batch| {
1233 let batch = batch?;
1234 let batch = if select_last {
1236 FlatLastTimestampSelector::select_batch(batch)?
1237 } else {
1238 batch
1239 };
1240 align_query_source_batch(&context, batch)
1241 }))
1242}
1243
1244const SPLIT_ROW_THRESHOLD: u64 = DEFAULT_ROW_GROUP_SIZE as u64;
1246const NUM_SERIES_THRESHOLD: u64 = 10240;
1248pub(crate) const BATCH_SIZE_THRESHOLD: u64 = 50;
1251pub(crate) const MIN_COMPACTION_SPLIT_ROWS: usize = DEFAULT_READ_BATCH_SIZE / 2;
1254
1255pub(crate) fn should_split_flat_batches_for_merge(
1258 stream_ctx: &Arc<StreamContext>,
1259 range_meta: &RangeMeta,
1260) -> Option<usize> {
1261 let mut num_files_to_split = 0;
1263 let mut num_mem_rows = 0;
1264 let mut num_mem_series = 0;
1265 let mut total_rows: u64 = 0;
1267 let mut total_series: u64 = 0;
1268 for index in &range_meta.row_group_indices {
1272 if stream_ctx.is_mem_range_index(*index) {
1273 let memtable = &stream_ctx.input.memtables[index.index];
1274 let stats = memtable.stats();
1276 num_mem_rows += stats.num_rows();
1277 num_mem_series += stats.series_count();
1278 } else if stream_ctx.is_file_range_index(*index) {
1279 let file_index = index.index - stream_ctx.input.num_memtables();
1281 let file = &stream_ctx.input.files[file_index];
1282 let file_meta = file.meta_ref();
1283 if file_meta.level == 0 {
1284 if file_meta.num_series > 0 {
1286 if !can_split_series(file_meta.num_rows, file_meta.num_series) {
1287 return None;
1288 }
1289 total_rows += file_meta.num_rows;
1290 total_series += file_meta.num_series;
1291 }
1292 num_files_to_split += 1;
1294 continue;
1295 } else if file_meta.num_rows < SPLIT_ROW_THRESHOLD || file_meta.num_series == 0 {
1296 continue;
1298 }
1299 debug_assert!(file_meta.num_rows > 0);
1300 if !can_split_series(file_meta.num_rows, file_meta.num_series) {
1301 common_telemetry::trace!(
1303 "Can't split series for file {}, level: {}, num_rows: {}, num_series: {}",
1304 file_meta.file_id,
1305 file_meta.level,
1306 file_meta.num_rows,
1307 file_meta.num_series,
1308 );
1309 return None;
1310 } else {
1311 num_files_to_split += 1;
1312 total_rows += file.meta_ref().num_rows;
1313 total_series += file.meta_ref().num_series;
1314 }
1315 }
1316 }
1318
1319 let should_split = if num_files_to_split > 0 {
1320 true
1322 } else if num_mem_series > 0
1323 && num_mem_rows > 0
1324 && can_split_series(num_mem_rows as u64, num_mem_series as u64)
1325 {
1326 total_rows += num_mem_rows as u64;
1327 total_series += num_mem_series as u64;
1328 true
1329 } else {
1330 false
1331 };
1332
1333 if !should_split {
1334 return None;
1335 }
1336
1337 let estimated_batch_size = if total_series > 0 && total_rows > 0 {
1339 ((total_rows / total_series) as usize).clamp(1, DEFAULT_READ_BATCH_SIZE)
1340 } else {
1341 DEFAULT_READ_BATCH_SIZE / 4
1343 };
1344 Some(estimated_batch_size)
1345}
1346
1347pub(crate) fn compute_parallel_channel_size(estimated_rows_per_batch: usize) -> usize {
1350 let size = 2 * DEFAULT_READ_BATCH_SIZE / estimated_rows_per_batch.max(1);
1351 size.clamp(2, 64)
1352}
1353
1354pub(crate) fn compute_average_batch_size(
1356 estimated_rows_per_batch: impl IntoIterator<Item = usize>,
1357) -> usize {
1358 let mut total = 0usize;
1359 let mut count = 0usize;
1360 for size in estimated_rows_per_batch {
1361 total += size;
1362 count += 1;
1363 }
1364
1365 if count == 0 {
1366 return DEFAULT_READ_BATCH_SIZE;
1367 }
1368
1369 (total / count).clamp(1, DEFAULT_READ_BATCH_SIZE)
1370}
1371
1372fn can_split_series(num_rows: u64, num_series: u64) -> bool {
1373 if num_rows == 0 || num_series == 0 {
1374 return false;
1375 }
1376
1377 num_series < NUM_SERIES_THRESHOLD || num_rows / num_series >= BATCH_SIZE_THRESHOLD
1379}
1380
1381#[cfg(test)]
1382mod split_tests {
1383 use std::sync::Arc;
1384
1385 use common_time::Timestamp;
1386 use smallvec::smallvec;
1387 use store_api::storage::FileId;
1388
1389 use super::*;
1390 use crate::read::flat_projection::FlatProjectionMapper;
1391 use crate::read::range::{RangeMeta, RowGroupIndex, SourceIndex};
1392 use crate::read::scan_region::{ScanInput, StreamContext};
1393 use crate::sst::file::FileHandle;
1394 use crate::test_util::memtable_util::metadata_with_primary_key;
1395 use crate::test_util::scheduler_util::SchedulerEnv;
1396 use crate::test_util::sst_util::sst_file_handle_with_file_id;
1397
1398 async fn new_stream_context_with_files(files: Vec<FileHandle>) -> StreamContext {
1399 let env = SchedulerEnv::new().await;
1400 let metadata = Arc::new(metadata_with_primary_key(vec![0, 1], false));
1401 let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
1402 let input = ScanInput::builder(env.access_layer.clone(), mapper)
1403 .with_files(files)
1404 .build();
1405
1406 StreamContext {
1407 input,
1408 ranges: vec![],
1409 query_start: std::time::Instant::now(),
1410 }
1411 }
1412
1413 fn single_file_range_meta() -> RangeMeta {
1414 RangeMeta {
1415 time_range: (
1416 Timestamp::new_millisecond(0),
1417 Timestamp::new_millisecond(1000),
1418 ),
1419 indices: smallvec![SourceIndex {
1420 index: 0,
1421 num_row_groups: 1,
1422 }],
1423 row_group_indices: smallvec![RowGroupIndex {
1424 index: 0,
1425 row_group_index: 0,
1426 }],
1427 num_rows: 1024,
1428 }
1429 }
1430
1431 #[tokio::test]
1432 async fn should_split_level_zero_file_even_when_series_stats_are_missing() {
1433 let mut file = sst_file_handle_with_file_id(FileId::random(), 0, 1000)
1434 .meta_ref()
1435 .clone();
1436 file.level = 0;
1437 file.num_rows = DEFAULT_ROW_GROUP_SIZE as u64;
1438 file.num_row_groups = 1;
1439 file.num_series = 0;
1440
1441 let file = FileHandle::new(file, crate::test_util::new_noop_file_purger());
1442 let stream_ctx = Arc::new(new_stream_context_with_files(vec![file]).await);
1443
1444 assert!(
1445 should_split_flat_batches_for_merge(&stream_ctx, &single_file_range_meta()).is_some()
1446 );
1447 }
1448
1449 #[test]
1450 fn can_split_series_returns_false_for_zero_inputs() {
1451 assert!(!can_split_series(0, 1));
1452 assert!(!can_split_series(1, 0));
1453 assert!(!can_split_series(0, 0));
1454 }
1455}
1456
1457pub(crate) fn new_filter_metrics(explain_verbose: bool) -> ReaderFilterMetrics {
1460 if explain_verbose {
1461 ReaderFilterMetrics {
1462 inverted_index_apply_metrics: Some(InvertedIndexApplyMetrics::default()),
1463 bloom_filter_apply_metrics: Some(BloomFilterIndexApplyMetrics::default()),
1464 fulltext_index_apply_metrics: Some(FulltextIndexApplyMetrics::default()),
1465 ..Default::default()
1466 }
1467 } else {
1468 ReaderFilterMetrics::default()
1469 }
1470}
1471
1472#[tracing::instrument(
1474 skip_all,
1475 fields(
1476 region_id = %stream_ctx.input.region_metadata().region_id,
1477 row_group_index = %index.index,
1478 source = read_type
1479 )
1480)]
1481pub(crate) async fn scan_flat_file_ranges(
1482 stream_ctx: Arc<StreamContext>,
1483 part_metrics: PartitionMetrics,
1484 index: RowGroupIndex,
1485 read_type: &'static str,
1486 partition_pruner: Arc<PartitionPruner>,
1487) -> Result<impl Stream<Item = Result<RecordBatch>>> {
1488 let mut reader_metrics = ReaderMetrics {
1489 filter_metrics: new_filter_metrics(part_metrics.explain_verbose()),
1490 ..Default::default()
1491 };
1492 let ranges = partition_pruner
1493 .build_file_ranges(index, &part_metrics, &mut reader_metrics)
1494 .await?;
1495 part_metrics.inc_num_file_ranges(ranges.len());
1496 part_metrics.merge_reader_metrics(&reader_metrics, None);
1497
1498 let init_per_file_metrics = if part_metrics.explain_verbose() {
1500 let file = stream_ctx.input.file_from_index(index);
1501 let file_id = file.file_id();
1502
1503 let mut map = HashMap::new();
1504 map.insert(
1505 file_id,
1506 FileScanMetrics {
1507 build_part_cost: reader_metrics.build_cost,
1508 ..Default::default()
1509 },
1510 );
1511 Some(map)
1512 } else {
1513 None
1514 };
1515
1516 Ok(build_flat_file_range_scan_stream(
1517 stream_ctx,
1518 part_metrics,
1519 read_type,
1520 ranges,
1521 init_per_file_metrics,
1522 ))
1523}
1524
1525pub(crate) fn filter_flat_batch_by_sequence(
1537 record_batch: RecordBatch,
1538 sequence_range: Option<SequenceRange>,
1539 file_sequence_trusted: bool,
1540) -> Result<Option<RecordBatch>> {
1541 let Some(sequence) = sequence_range else {
1542 return Ok(Some(record_batch));
1543 };
1544 if !file_sequence_trusted {
1545 return Ok(Some(record_batch));
1546 }
1547
1548 let num_rows = record_batch.num_rows();
1549 if num_rows == 0 {
1550 return Ok(Some(record_batch));
1551 }
1552 let sequence_column = record_batch.column(sequence_column_index(record_batch.num_columns()));
1553 let predicate = sequence
1554 .filter(sequence_column)
1555 .context(ComputeArrowSnafu)?;
1556 let select_count = predicate.true_count();
1557 if select_count == 0 {
1558 return Ok(None);
1559 }
1560 if select_count == num_rows {
1561 return Ok(Some(record_batch));
1562 }
1563 let filtered_batch = datatypes::arrow::compute::filter_record_batch(&record_batch, &predicate)
1564 .context(ComputeArrowSnafu)?;
1565 Ok(Some(filtered_batch))
1566}
1567
1568#[tracing::instrument(
1570 skip_all,
1571 fields(read_type = read_type, range_count = ranges.len())
1572)]
1573pub fn build_flat_file_range_scan_stream(
1574 stream_ctx: Arc<StreamContext>,
1575 part_metrics: PartitionMetrics,
1576 read_type: &'static str,
1577 ranges: SmallVec<[FileRange; 2]>,
1578 mut per_file_metrics: Option<HashMap<RegionFileId, FileScanMetrics>>,
1579) -> impl Stream<Item = Result<RecordBatch>> {
1580 try_stream! {
1581 let fetch_metrics = if part_metrics.explain_verbose() {
1582 Some(Arc::new(ParquetFetchMetrics::default()))
1583 } else {
1584 None
1585 };
1586 let reader_metrics = &mut ReaderMetrics {
1587 fetch_metrics: fetch_metrics.clone(),
1588 ..Default::default()
1589 };
1590 for range in ranges {
1591 let build_reader_start = Instant::now();
1592 let Some(mut reader) = range
1593 .flat_reader(
1594 match stream_ctx.input.series_row_selector {
1595 Some(TimeSeriesRowSelector::LastRow { after_merge: false })
1596 if stream_ctx.input.sequence_range.is_none() =>
1597 {
1598 stream_ctx.input.series_row_selector
1599 }
1600 _ => None,
1601 },
1602 fetch_metrics.as_deref(),
1603 )
1604 .await?
1605 else {
1606 continue;
1607 };
1608 let build_cost = build_reader_start.elapsed();
1609 part_metrics.inc_build_reader_cost(build_cost);
1610
1611 let may_compat = range.compat_batch();
1612 let file_sequence_trusted = range
1613 .file_handle()
1614 .is_effective_target_sequence_trusted(stream_ctx.input.region_metadata().region_id);
1615
1616 let mapper = range.compaction_projection_mapper();
1617 while let Some(record_batch) = reader.next_batch().await? {
1618 let record_batch = if let Some(mapper) = mapper {
1619 mapper.project(record_batch)?
1620 } else {
1621 record_batch
1622 };
1623 let Some(record_batch) = filter_flat_batch_by_sequence(
1624 record_batch,
1625 stream_ctx.input.sequence_range,
1626 file_sequence_trusted,
1627 )? else {
1628 continue;
1629 };
1630
1631 if let Some(flat_compat) = may_compat {
1632 let batch = flat_compat.compat(record_batch)?;
1633 yield batch;
1634 } else {
1635 yield record_batch;
1636 }
1637 }
1638
1639 let prune_metrics = reader.metrics();
1640
1641 if let Some(file_metrics_map) = per_file_metrics.as_mut() {
1643 let file_id = range.file_handle().file_id();
1644 let file_metrics = file_metrics_map
1645 .entry(file_id)
1646 .or_insert_with(FileScanMetrics::default);
1647
1648 file_metrics.num_ranges += 1;
1649 file_metrics.num_rows += prune_metrics.num_rows;
1650 file_metrics.build_reader_cost += build_cost;
1651 file_metrics.scan_cost += prune_metrics.scan_cost;
1652 }
1653
1654 reader_metrics.merge_from(&prune_metrics);
1655 }
1656
1657 reader_metrics.observe_rows(read_type);
1659 part_metrics.merge_reader_metrics(reader_metrics, per_file_metrics.as_ref());
1660 }
1661}
1662
1663#[cfg(feature = "enterprise")]
1665pub(crate) async fn scan_flat_extension_range(
1666 context: Arc<StreamContext>,
1667 index: RowGroupIndex,
1668 partition_metrics: PartitionMetrics,
1669 options: crate::extension::ExtensionRangeReadOptions,
1670) -> Result<BoxedRecordBatchStream> {
1671 use snafu::ResultExt;
1672
1673 let range = context.input.extension_range(index.index);
1674 let reader = range.flat_reader(context.as_ref(), options);
1675 let stream = reader
1676 .read(context.clone(), partition_metrics, index)
1677 .await
1678 .context(crate::error::ScanExternalRangeSnafu)?;
1679 if context.input.read_cols.json_target_types().is_empty() {
1680 return Ok(stream);
1681 }
1682 Ok(Box::pin(stream.and_then(move |batch| {
1683 std::future::ready(align_query_source_batch(&context, batch))
1684 })))
1685}
1686
1687pub(crate) async fn maybe_scan_flat_other_ranges(
1688 context: &Arc<StreamContext>,
1689 index: RowGroupIndex,
1690 metrics: &PartitionMetrics,
1691 pre_filter_mode: PreFilterMode,
1692) -> Result<BoxedRecordBatchStream> {
1693 #[cfg(feature = "enterprise")]
1694 {
1695 let options = crate::extension::ExtensionRangeReadOptions { pre_filter_mode };
1696 scan_flat_extension_range(context.clone(), index, metrics.clone(), options).await
1697 }
1698
1699 #[cfg(not(feature = "enterprise"))]
1700 {
1701 let _ = context;
1702 let _ = index;
1703 let _ = metrics;
1704 let _ = pre_filter_mode;
1705
1706 crate::error::UnexpectedSnafu {
1707 reason: "no other ranges scannable in flat format",
1708 }
1709 .fail()
1710 }
1711}
1712
1713pub(crate) struct SplitRecordBatchStream<S> {
1715 inner: S,
1717 batches: VecDeque<RecordBatch>,
1719}
1720
1721impl<S> SplitRecordBatchStream<S> {
1722 pub(crate) fn new(inner: S) -> Self {
1724 Self {
1725 inner,
1726 batches: VecDeque::new(),
1727 }
1728 }
1729}
1730
1731impl<S> Stream for SplitRecordBatchStream<S>
1732where
1733 S: Stream<Item = Result<RecordBatch>> + Unpin,
1734{
1735 type Item = Result<RecordBatch>;
1736
1737 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1738 loop {
1739 if let Some(batch) = self.batches.pop_front() {
1741 return Poll::Ready(Some(Ok(batch)));
1742 }
1743
1744 let record_batch = match futures::ready!(Pin::new(&mut self.inner).poll_next(cx)) {
1746 Some(Ok(batch)) => batch,
1747 Some(Err(e)) => return Poll::Ready(Some(Err(e))),
1748 None => return Poll::Ready(None),
1749 };
1750
1751 split_record_batch(record_batch, &mut self.batches);
1753 }
1755 }
1756}
1757
1758pub(crate) fn split_record_batch(record_batch: RecordBatch, batches: &mut VecDeque<RecordBatch>) {
1763 let batch_rows = record_batch.num_rows();
1764 if batch_rows == 0 {
1765 return;
1766 }
1767 if batch_rows < 2 {
1768 batches.push_back(record_batch);
1769 return;
1770 }
1771
1772 let time_index_pos = time_index_column_index(record_batch.num_columns());
1773 let timestamps = record_batch.column(time_index_pos);
1774 let (ts_values, _unit) = timestamp_array_to_primitive(timestamps).unwrap();
1775 let mut offsets = Vec::with_capacity(16);
1776 offsets.push(0);
1777 let values = ts_values.values();
1778 for (i, &value) in values.iter().take(batch_rows - 1).enumerate() {
1779 if value >= values[i + 1] {
1780 offsets.push(i + 1);
1781 }
1782 }
1783 offsets.push(values.len());
1784
1785 for (i, &start) in offsets[..offsets.len() - 1].iter().enumerate() {
1787 let end = offsets[i + 1];
1788 let rows_in_batch = end - start;
1789 batches.push_back(record_batch.slice(start, rows_in_batch));
1790 }
1791}
1792
1793#[cfg(test)]
1794mod tests {
1795 use arrow_select::concat::concat_batches;
1796 use common_time::Timestamp;
1797 use datatypes::arrow::array::{BinaryArray, TimestampMillisecondArray, UInt64Array};
1798 use datatypes::types::json_type::JsonNativeType;
1799 use futures::TryStreamExt;
1800 use smallvec::{SmallVec, smallvec};
1801 use std::sync::Arc;
1802 use std::time::Instant;
1803 use store_api::storage::RegionId;
1804
1805 use super::*;
1806 use crate::cache::CacheStrategy;
1807 use crate::memtable::bulk::context::BulkIterContext;
1808 use crate::memtable::bulk::part_reader::BulkPartBatchIter;
1809 use crate::memtable::{
1810 BoxedBatchIterator, BoxedRecordBatchIterator, IterBuilder, MemtableRange,
1811 MemtableRangeContext, MemtableStats,
1812 };
1813 use crate::read::flat_projection::FlatProjectionMapper;
1814 use crate::read::range::{MemRangeBuilder, SourceIndex};
1815 use crate::read::read_columns::ReadColumns;
1816 use crate::read::scan_region::ScanInput;
1817 use crate::sst::file::{FileHandle, FileMeta};
1818 use crate::sst::file_purger::NoopFilePurger;
1819 use crate::test_util::memtable_util::metadata_for_test;
1820 use crate::test_util::scheduler_util::SchedulerEnv;
1821
1822 #[test]
1823 fn test_range_cache_reporter_preserves_metrics_and_lifetime() {
1824 let metrics = PartitionMetrics::new(
1825 RegionId::new(1, 1),
1826 0,
1827 "test_range_cache_reporter",
1828 Instant::now(),
1829 false,
1830 &ExecutionPlanMetricsSet::new(),
1831 );
1832 let weak = Arc::downgrade(&metrics.0);
1833 let reporter = metrics.range_cache_metrics_reporter();
1834 reporter.inc_range_cache_hit();
1835 reporter.inc_range_cache_miss();
1836 drop(metrics);
1837 reporter.inc_range_cache_size(128);
1839 {
1840 let inner = weak.upgrade().unwrap();
1841 let metrics = inner.metrics.lock().unwrap();
1842 assert_eq!(metrics.range_cache_hit, 1);
1843 assert_eq!(metrics.range_cache_miss, 1);
1844 assert_eq!(metrics.range_cache_size, 128);
1845 }
1846 drop(reporter);
1847 assert!(weak.upgrade().is_none());
1848 }
1849
1850 struct EmptyIterBuilder;
1851
1852 impl IterBuilder for EmptyIterBuilder {
1853 fn build(&self, _metrics: Option<MemScanMetrics>) -> Result<BoxedBatchIterator> {
1854 Ok(Box::new(std::iter::empty()))
1855 }
1856
1857 fn is_record_batch(&self) -> bool {
1858 true
1859 }
1860
1861 fn build_record_batch(
1862 &self,
1863 _time_range: Option<(Timestamp, Timestamp)>,
1864 _metrics: Option<MemScanMetrics>,
1865 ) -> Result<BoxedRecordBatchIterator> {
1866 Ok(Box::new(std::iter::empty()))
1867 }
1868 }
1869
1870 async fn new_test_stream_ctx(
1871 files: Vec<FileHandle>,
1872 memtables: Vec<MemRangeBuilder>,
1873 ) -> Arc<StreamContext> {
1874 let env = SchedulerEnv::new().await;
1875 let metadata = metadata_for_test();
1876 let mapper = FlatProjectionMapper::new(&metadata, [0, 2, 3]).unwrap();
1877 let input = ScanInput::builder(env.access_layer.clone(), mapper)
1878 .with_cache(CacheStrategy::Disabled)
1879 .with_memtables(memtables)
1880 .with_files(files)
1881 .build();
1882
1883 Arc::new(StreamContext {
1884 input,
1885 ranges: Vec::new(),
1886 query_start: Instant::now(),
1887 })
1888 }
1889
1890 fn new_test_file(num_rows: u64, num_series: u64) -> FileHandle {
1891 let meta = FileMeta {
1892 region_id: RegionId::new(123, 456),
1893 file_id: Default::default(),
1894 level: 1,
1895 time_range: (
1896 Timestamp::new_millisecond(0),
1897 Timestamp::new_millisecond(1000),
1898 ),
1899 num_rows,
1900 num_series,
1901 ..Default::default()
1902 };
1903 FileHandle::new(meta, Arc::new(NoopFilePurger))
1904 }
1905
1906 fn new_test_memtable(num_rows: usize, series_count: usize) -> MemRangeBuilder {
1907 let context = Arc::new(MemtableRangeContext::new(
1908 0,
1909 Box::new(EmptyIterBuilder),
1910 Default::default(),
1911 ));
1912 let stats = MemtableStats {
1913 time_range: Some((
1914 Timestamp::new_millisecond(0),
1915 Timestamp::new_millisecond(1000),
1916 )),
1917 num_rows,
1918 num_ranges: 1,
1919 series_count,
1920 ..Default::default()
1921 };
1922 let range = MemtableRange::new(context, stats.clone());
1923 MemRangeBuilder::new(range, stats)
1924 }
1925
1926 fn new_test_range_meta(row_group_indices: SmallVec<[RowGroupIndex; 2]>) -> RangeMeta {
1927 let indices = row_group_indices
1928 .iter()
1929 .map(|row_group_index| SourceIndex {
1930 index: row_group_index.index,
1931 num_row_groups: 1,
1932 })
1933 .collect();
1934
1935 RangeMeta {
1936 time_range: (
1937 Timestamp::new_millisecond(0),
1938 Timestamp::new_millisecond(1000),
1939 ),
1940 indices,
1941 row_group_indices,
1942 num_rows: 0,
1943 }
1944 }
1945
1946 #[tokio::test]
1947 async fn test_memtable_last_row_selects_before_json2_alignment()
1948 -> std::result::Result<(), Box<dyn std::error::Error>> {
1949 struct BatchIter(RecordBatch, Arc<BulkIterContext>, Option<SequenceRange>);
1950 impl IterBuilder for BatchIter {
1951 fn build(&self, _metrics: Option<MemScanMetrics>) -> Result<BoxedBatchIterator> {
1952 Ok(Box::new(std::iter::empty()))
1953 }
1954 fn is_record_batch(&self) -> bool {
1955 true
1956 }
1957 fn build_record_batch(
1958 &self,
1959 _time_range: Option<FileTimeRange>,
1960 metrics: Option<MemScanMetrics>,
1961 ) -> Result<BoxedRecordBatchIterator> {
1962 Ok(Box::new(BulkPartBatchIter::from_single(
1963 self.0.clone(),
1964 self.1.clone(),
1965 self.2,
1966 1,
1967 metrics,
1968 )))
1969 }
1970 }
1971 let (metadata, batch) = crate::test_util::sst_util::new_json2_record_batch()?;
1972 let batch = concat_batches(&batch.schema(), &[batch.clone(), batch.slice(0, 1)])?;
1973 let mut columns = batch.columns().to_vec();
1974 columns[1] = Arc::new(TimestampMillisecondArray::from(vec![1, 2, 3]));
1975 let seq_index = sequence_column_index(batch.num_columns());
1976 columns[seq_index] = Arc::new(UInt64Array::from(vec![1, 2, 3]));
1977 let batch = RecordBatch::try_new(batch.schema(), columns)?;
1978 let reader_context = Arc::new(BulkIterContext::new(
1979 metadata.clone(),
1980 None,
1981 None,
1982 false,
1983 DEFAULT_READ_BATCH_SIZE,
1984 )?);
1985 let time_range = (Timestamp::new_millisecond(1), Timestamp::new_millisecond(3));
1986 let env = SchedulerEnv::new().await;
1987 for (append, selector, sequence, count) in [
1988 (
1989 true,
1990 Some(TimeSeriesRowSelector::LastRow { after_merge: false }),
1991 None,
1992 1,
1993 ),
1994 (
1995 true,
1996 Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
1997 None,
1998 3,
1999 ),
2000 (
2001 false,
2002 Some(TimeSeriesRowSelector::LastRow { after_merge: false }),
2003 None,
2004 3,
2005 ),
2006 (true, None, None, 3),
2007 (
2008 true,
2009 Some(TimeSeriesRowSelector::LastRow { after_merge: false }),
2010 Some(SequenceRange::GtLtEq { min: 0, max: 2 }),
2011 1,
2012 ),
2013 ] {
2014 let range_context = Arc::new(MemtableRangeContext::new(
2015 0,
2016 Box::new(BatchIter(batch.clone(), reader_context.clone(), sequence)),
2017 Default::default(),
2018 ));
2019 let stats = MemtableStats {
2020 time_range: Some(time_range),
2021 num_rows: 3,
2022 num_ranges: 1,
2023 series_count: 1,
2024 ..Default::default()
2025 };
2026 let range = MemtableRange::new(range_context, stats.clone());
2027 let read_cols = ReadColumns::new([0, 1])
2028 .with_json_target_types([(0, JsonNativeType::Variant)].into_iter().collect());
2029 let mapper =
2030 FlatProjectionMapper::new_with_read_columns(&metadata, vec![0, 1], read_cols)?;
2031 let input = ScanInput::builder(env.access_layer.clone(), mapper)
2032 .with_append_mode(append)
2033 .with_series_row_selector(selector)
2034 .with_sequence_range(sequence)
2035 .with_memtables(vec![MemRangeBuilder::new(range, stats)])
2036 .build();
2037 let context = Arc::new(StreamContext {
2038 input,
2039 ranges: vec![],
2040 query_start: Instant::now(),
2041 });
2042 let metrics = PartitionMetrics::new(
2043 metadata.region_id,
2044 0,
2045 "test_json2",
2046 context.query_start,
2047 false,
2048 &ExecutionPlanMetricsSet::new(),
2049 );
2050 let mut raw_stream = Box::pin(scan_flat_mem_ranges(
2051 context.clone(),
2052 metrics,
2053 RowGroupIndex {
2054 index: 0,
2055 row_group_index: -1,
2056 },
2057 time_range,
2058 ));
2059 let raw = raw_stream.try_next().await?.ok_or("expected a raw batch")?;
2060 let expected_raw = if sequence.is_some() {
2061 batch.slice(0, 2)
2062 } else {
2063 batch.clone()
2064 };
2065 assert_eq!(expected_raw, raw);
2066 assert!(raw_stream.try_next().await?.is_none());
2067 let mut stream = prepare_flat_mem_stream(context, futures::stream::iter([Ok(raw)]));
2068 let output = stream.try_next().await?.ok_or("expected a JSON2 batch")?;
2069 assert_eq!(count, output.num_rows());
2070 let values = output
2071 .column(0)
2072 .as_any()
2073 .downcast_ref::<BinaryArray>()
2074 .ok_or("expected aligned JSONB")?;
2075 assert_eq!(
2076 datatypes::types::parse_string_to_jsonb(if sequence.is_some() {
2077 r#"{"a":2}"#
2078 } else {
2079 r#"{"a":1}"#
2080 })?,
2081 values.value(count - 1)
2082 );
2083 assert!(stream.try_next().await?.is_none());
2084 }
2085 Ok(())
2086 }
2087
2088 #[tokio::test]
2089 async fn test_should_split_flat_batches_for_merge_uses_splittable_file_rows_per_series() {
2090 let num_rows = SPLIT_ROW_THRESHOLD * 2;
2091 let num_series = (num_rows / 100).max(1);
2092 let stream_ctx =
2093 new_test_stream_ctx(vec![new_test_file(num_rows, num_series)], vec![]).await;
2094 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
2095 index: 0,
2096 row_group_index: 0,
2097 }]);
2098
2099 assert_eq!(
2100 Some((num_rows / num_series) as usize),
2101 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
2102 );
2103 }
2104
2105 #[tokio::test]
2106 async fn test_should_split_flat_batches_for_merge_skips_small_or_unknown_series_files() {
2107 let stream_ctx = new_test_stream_ctx(
2108 vec![
2109 new_test_file(SPLIT_ROW_THRESHOLD.saturating_sub(1), 1),
2110 new_test_file(SPLIT_ROW_THRESHOLD * 2, 0),
2111 ],
2112 vec![],
2113 )
2114 .await;
2115 let range_meta = new_test_range_meta(smallvec![
2116 RowGroupIndex {
2117 index: 0,
2118 row_group_index: 0,
2119 },
2120 RowGroupIndex {
2121 index: 1,
2122 row_group_index: 0,
2123 }
2124 ]);
2125
2126 assert_eq!(
2127 None,
2128 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
2129 );
2130 }
2131
2132 #[tokio::test]
2133 async fn test_should_split_flat_batches_for_merge_returns_none_for_unsplittable_file() {
2134 let num_series =
2135 (SPLIT_ROW_THRESHOLD / (BATCH_SIZE_THRESHOLD - 1)).max(NUM_SERIES_THRESHOLD) + 1;
2136 let stream_ctx =
2137 new_test_stream_ctx(vec![new_test_file(SPLIT_ROW_THRESHOLD, num_series)], vec![]).await;
2138 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
2139 index: 0,
2140 row_group_index: 0,
2141 }]);
2142
2143 assert_eq!(
2144 None,
2145 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
2146 );
2147 }
2148
2149 #[tokio::test]
2150 async fn test_should_split_flat_batches_for_merge_uses_level_zero_series_stats() {
2151 for (num_rows, num_series, expected) in [
2152 (70_144, 35_083, None),
2153 (100_000, 1_000, Some(100)),
2154 (100_000, 0, Some(DEFAULT_READ_BATCH_SIZE / 4)),
2155 ] {
2156 let mut meta = new_test_file(num_rows, num_series).meta_ref().clone();
2157 meta.level = 0;
2158 let file = FileHandle::new(meta, Arc::new(NoopFilePurger));
2159 let stream_ctx = new_test_stream_ctx(vec![file], vec![]).await;
2160 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
2161 index: 0,
2162 row_group_index: 0,
2163 }]);
2164
2165 assert_eq!(
2166 expected,
2167 should_split_flat_batches_for_merge(&stream_ctx, &range_meta),
2168 "rows={num_rows}, series={num_series}",
2169 );
2170 }
2171 }
2172
2173 #[tokio::test]
2174 async fn test_should_split_flat_batches_for_merge_falls_back_to_memtables() {
2175 let stream_ctx = new_test_stream_ctx(vec![], vec![new_test_memtable(5_000, 100)]).await;
2176 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
2177 index: 0,
2178 row_group_index: 0,
2179 }]);
2180
2181 assert_eq!(
2182 Some(50),
2183 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
2184 );
2185 }
2186
2187 #[tokio::test]
2188 async fn test_should_split_flat_batches_for_merge_clamps_estimate() {
2189 let stream_ctx =
2190 new_test_stream_ctx(vec![new_test_file(SPLIT_ROW_THRESHOLD * 2, 1)], vec![]).await;
2191 let range_meta = new_test_range_meta(smallvec![RowGroupIndex {
2192 index: 0,
2193 row_group_index: 0,
2194 }]);
2195
2196 assert_eq!(
2197 Some(DEFAULT_READ_BATCH_SIZE),
2198 should_split_flat_batches_for_merge(&stream_ctx, &range_meta)
2199 );
2200 }
2201
2202 #[test]
2203 fn test_compute_parallel_channel_size_clamps_to_max_for_small_batches() {
2204 assert_eq!(64, compute_parallel_channel_size(0));
2205 assert_eq!(64, compute_parallel_channel_size(1));
2206 }
2207
2208 #[test]
2209 fn test_compute_parallel_channel_size_returns_expected_mid_range_size() {
2210 assert_eq!(
2211 4,
2212 compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE / 2)
2213 );
2214 }
2215
2216 #[test]
2217 fn test_compute_parallel_channel_size_clamps_to_min_for_large_batches() {
2218 assert_eq!(2, compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE));
2219 assert_eq!(
2220 2,
2221 compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE * 2)
2222 );
2223 }
2224
2225 #[test]
2226 fn test_compute_average_batch_size_uses_arithmetic_mean() {
2227 assert_eq!(24, compute_average_batch_size([16, 24, 32]));
2228 }
2229
2230 #[test]
2231 fn test_compute_average_batch_size_clamps_values() {
2232 assert_eq!(
2233 DEFAULT_READ_BATCH_SIZE,
2234 compute_average_batch_size([DEFAULT_READ_BATCH_SIZE, DEFAULT_READ_BATCH_SIZE * 2])
2235 );
2236 assert_eq!(1, compute_average_batch_size([0, 1]));
2237 }
2238
2239 #[test]
2240 fn test_compute_average_batch_size_falls_back_when_empty() {
2241 assert_eq!(
2242 DEFAULT_READ_BATCH_SIZE,
2243 compute_average_batch_size(std::iter::empty())
2244 );
2245 }
2246
2247 fn flat_ts_batch(timestamps: &[i64]) -> RecordBatch {
2249 use datatypes::arrow::array::{TimestampMillisecondArray, UInt8Array, UInt64Array};
2250 use datatypes::arrow::datatypes::{DataType, Field, Schema, TimeUnit};
2251
2252 let num_rows = timestamps.len();
2253 let schema = Arc::new(Schema::new(vec![
2254 Field::new(
2255 "ts",
2256 DataType::Timestamp(TimeUnit::Millisecond, None),
2257 false,
2258 ),
2259 Field::new("pk", DataType::UInt64, false),
2260 Field::new("seq", DataType::UInt64, false),
2261 Field::new("op", DataType::UInt8, false),
2262 ]));
2263 RecordBatch::try_new(
2264 schema,
2265 vec![
2266 Arc::new(TimestampMillisecondArray::from(timestamps.to_vec())),
2267 Arc::new(UInt64Array::from(vec![0u64; num_rows])),
2268 Arc::new(UInt64Array::from(vec![0u64; num_rows])),
2269 Arc::new(UInt8Array::from(vec![0u8; num_rows])),
2270 ],
2271 )
2272 .unwrap()
2273 }
2274
2275 fn split_ts(timestamps: &[i64]) -> Vec<Vec<i64>> {
2277 let mut batches = VecDeque::new();
2278 split_record_batch(flat_ts_batch(timestamps), &mut batches);
2279 batches
2280 .iter()
2281 .map(|batch| {
2282 let pos = time_index_column_index(batch.num_columns());
2283 let (values, _) = timestamp_array_to_primitive(batch.column(pos)).unwrap();
2284 values.values().to_vec()
2285 })
2286 .collect()
2287 }
2288
2289 #[test]
2290 fn test_split_record_batch_on_equal_timestamps() {
2291 assert_eq!(
2293 split_ts(&[1, 2, 2, 3, 1]),
2294 vec![vec![1, 2], vec![2, 3], vec![1]]
2295 );
2296 assert_eq!(split_ts(&[5, 5, 5]), vec![vec![5], vec![5], vec![5]]);
2298 assert_eq!(split_ts(&[5, 5, 1, 2]), vec![vec![5], vec![5], vec![1, 2]]);
2300 assert_eq!(split_ts(&[1, 2, 5, 5]), vec![vec![1, 2, 5], vec![5]]);
2302 }
2303
2304 #[test]
2305 fn test_split_record_batch_on_decreasing_timestamps() {
2306 assert_eq!(split_ts(&[1, 2, 3]), vec![vec![1, 2, 3]]);
2307 assert_eq!(split_ts(&[1, 3, 2, 4]), vec![vec![1, 3], vec![2, 4]]);
2308 }
2309
2310 #[test]
2311 fn test_split_record_batch_empty_and_single_row() {
2312 let mut batches = VecDeque::new();
2313 split_record_batch(flat_ts_batch(&[]), &mut batches);
2314 assert!(batches.is_empty());
2315
2316 assert_eq!(split_ts(&[42]), vec![vec![42]]);
2317 }
2318}
2319
2320#[cfg(test)]
2321mod sequence_filter_tests {
2322 use std::sync::Arc;
2323
2324 use datatypes::arrow::array::{Int64Array, StringArray, UInt8Array, UInt64Array};
2325 use datatypes::arrow::datatypes::{DataType, Field, Schema};
2326 use datatypes::arrow::record_batch::RecordBatch;
2327 use store_api::storage::SequenceRange;
2328
2329 use super::filter_flat_batch_by_sequence;
2330
2331 fn batch(sequences: &[u64]) -> RecordBatch {
2333 let schema = Arc::new(Schema::new(vec![
2334 Field::new("tag_0", DataType::Utf8, false),
2335 Field::new("field_0", DataType::Int64, false),
2336 Field::new(
2337 "ts",
2338 DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Millisecond, None),
2339 false,
2340 ),
2341 Field::new("__primary_key", DataType::UInt8, false),
2342 Field::new("__sequence", DataType::UInt64, false),
2343 Field::new("__op_type", DataType::UInt8, false),
2344 ]));
2345 let tags = StringArray::from_iter_values((0..sequences.len()).map(|i| i.to_string()));
2346 let fields = Int64Array::from_iter_values(0..sequences.len() as i64);
2347 let ts = datatypes::arrow::array::TimestampMillisecondArray::from_iter_values(
2348 (0..sequences.len()).map(|i| i as i64 * 1000),
2349 );
2350 let pk = UInt8Array::from(vec![0u8; sequences.len()]);
2351 let seq = UInt64Array::from_iter_values(sequences.iter().copied());
2352 let op = UInt8Array::from(vec![0u8; sequences.len()]);
2353 RecordBatch::try_new(
2354 schema,
2355 vec![
2356 Arc::new(tags),
2357 Arc::new(fields),
2358 Arc::new(ts),
2359 Arc::new(pk),
2360 Arc::new(seq),
2361 Arc::new(op),
2362 ],
2363 )
2364 .unwrap()
2365 }
2366
2367 fn remaining_tags(batch: &RecordBatch) -> Vec<String> {
2368 batch
2369 .column(0)
2370 .as_any()
2371 .downcast_ref::<StringArray>()
2372 .unwrap()
2373 .iter()
2374 .map(|v| v.unwrap().to_string())
2375 .collect()
2376 }
2377
2378 #[test]
2379 fn test_filter_flat_batch_by_sequence_no_range_or_legacy_file() {
2380 let b = batch(&[1, 2, 3, 4]);
2381
2382 let out = filter_flat_batch_by_sequence(b.clone(), None, true).unwrap();
2383 assert_eq!(remaining_tags(&out.unwrap()), vec!["0", "1", "2", "3"]);
2384
2385 let out = filter_flat_batch_by_sequence(
2386 b.clone(),
2387 Some(SequenceRange::GtLtEq { min: 2, max: 3 }),
2388 false,
2389 )
2390 .unwrap();
2391 assert_eq!(remaining_tags(&out.unwrap()), vec!["0", "1", "2", "3"]);
2392 }
2393
2394 #[test]
2395 fn test_filter_flat_batch_by_sequence_exact_range() {
2396 let b = batch(&[1, 2, 3, 4]);
2397 let out = filter_flat_batch_by_sequence(
2398 b.clone(),
2399 Some(SequenceRange::GtLtEq { min: 2, max: 3 }),
2400 true,
2401 )
2402 .unwrap();
2403 assert_eq!(remaining_tags(&out.unwrap()), vec!["2"]);
2404
2405 let out = filter_flat_batch_by_sequence(
2406 b.clone(),
2407 Some(SequenceRange::GtLtEq { min: 10, max: 20 }),
2408 true,
2409 )
2410 .unwrap();
2411 assert!(out.is_none());
2412
2413 let empty = batch(&[]);
2414 let out = filter_flat_batch_by_sequence(
2415 empty,
2416 Some(SequenceRange::GtLtEq { min: 0, max: 10 }),
2417 true,
2418 )
2419 .unwrap();
2420 assert_eq!(out.unwrap().num_rows(), 0);
2421
2422 let out = filter_flat_batch_by_sequence(
2425 batch(&[1, 5, 9]),
2426 Some(SequenceRange::GtLtEq { min: 2, max: 8 }),
2427 true,
2428 )
2429 .unwrap();
2430 assert_eq!(remaining_tags(&out.unwrap()), vec!["1"]);
2431 }
2432}