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