1use std::fmt;
18use std::sync::Arc;
19use std::time::Instant;
20
21use async_stream::try_stream;
22use common_error::ext::BoxedError;
23use common_recordbatch::util::ChainedRecordBatchStream;
24use common_recordbatch::{RecordBatchStreamWrapper, SendableRecordBatchStream};
25use common_telemetry::tracing;
26use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
27use datafusion::physical_plan::{DisplayAs, DisplayFormatType};
28use datatypes::schema::SchemaRef;
29use futures::{StreamExt, TryStreamExt};
30use snafu::ensure;
31use store_api::metadata::RegionMetadataRef;
32use store_api::region_engine::{
33 PartitionRange, PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
34};
35use store_api::storage::TimeSeriesRowSelector;
36use tokio::sync::Semaphore;
37
38use crate::error::{PartitionOutOfRangeSnafu, Result, TooManyFilesToReadSnafu};
39use crate::metrics::SCAN_MAX_CONCURRENT_FILES;
40use crate::read::flat_dedup::{FlatDedupReader, FlatLastNonNull, FlatLastRow};
41use crate::read::flat_merge::FlatMergeReader;
42use crate::read::last_row::FlatLastRowReader;
43use crate::read::pruner::{PartitionPruner, Pruner};
44use crate::read::range::RangeMeta;
45use crate::read::range_cache::{CachedRangeReader, build_range_cache_key};
46use crate::read::scan_region::{ScanInput, StreamContext};
47use crate::read::scan_util::{
48 BATCH_SIZE_THRESHOLD, MIN_COMPACTION_SPLIT_ROWS, PartitionMetrics, PartitionMetricsList,
49 SplitRecordBatchStream, compute_parallel_channel_size, scan_flat_file_ranges,
50 scan_flat_mem_ranges, should_split_flat_batches_for_merge,
51};
52use crate::read::stream::{ConvertBatchStream, ScanBatch, ScanBatchStream};
53use crate::read::{BoxedRecordBatchStream, ScannerMetrics, scan_util};
54use crate::region::options::MergeMode;
55use crate::sst::parquet::DEFAULT_READ_BATCH_SIZE;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub(crate) enum FlatReadMode {
60 Sequential,
62 CrossRangeMerge,
64 Compaction,
66}
67
68pub struct SeqScan {
73 properties: ScannerProperties,
75 stream_ctx: Arc<StreamContext>,
77 pruner: Arc<Pruner>,
79 metrics_list: PartitionMetricsList,
82}
83
84impl SeqScan {
85 pub(crate) fn new(input: ScanInput) -> Self {
88 let mut properties = ScannerProperties::default()
89 .with_append_mode(input.append_mode)
90 .with_total_rows(input.total_rows())
91 .with_total_rows_is_exact(input.append_mode && input.total_rows_is_exact());
92 if let Some(counters) = input.query_stat_counters.clone() {
93 properties.set_query_stat_counters(counters);
94 }
95 let stream_ctx = Arc::new(StreamContext::seq_scan_ctx(input));
96 properties.partitions = vec![stream_ctx.partition_ranges()];
97
98 let num_workers = common_stat::get_total_cpu_cores().max(1);
100 let pruner = Arc::new(Pruner::new(stream_ctx.clone(), num_workers));
101
102 Self {
103 properties,
104 stream_ctx,
105 pruner,
106 metrics_list: PartitionMetricsList::default(),
107 }
108 }
109
110 #[tracing::instrument(
115 skip_all,
116 fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
117 )]
118 pub fn build_stream(&self) -> Result<SendableRecordBatchStream, BoxedError> {
119 let metrics_set = ExecutionPlanMetricsSet::new();
120 let ctx = QueryScanContext::default();
121 let streams = (0..self.properties.partitions.len())
122 .map(|partition: usize| self.scan_partition(&ctx, &metrics_set, partition))
123 .collect::<Result<Vec<_>, _>>()?;
124
125 let aggr_stream = ChainedRecordBatchStream::new(streams).map_err(BoxedError::new)?;
126 Ok(Box::pin(aggr_stream))
127 }
128
129 pub(crate) fn scan_all_partitions(&self) -> Result<ScanBatchStream> {
131 let metrics_set = ExecutionPlanMetricsSet::new();
132
133 let streams = (0..self.properties.partitions.len())
134 .map(|partition| {
135 let metrics = self.new_partition_metrics(false, &metrics_set, partition);
136 self.scan_flat_batch_in_partition(partition, metrics)
137 })
138 .collect::<Result<Vec<_>>>()?;
139
140 Ok(Box::pin(futures::stream::iter(streams).flatten()))
141 }
142
143 pub(crate) async fn build_flat_reader_for_compaction(&self) -> Result<BoxedRecordBatchStream> {
148 assert!(self.stream_ctx.input.compaction);
149
150 SCAN_MAX_CONCURRENT_FILES
152 .with_label_values(&["compaction"])
153 .observe(self.stream_ctx.input.num_files() as f64);
154
155 let metrics_set = ExecutionPlanMetricsSet::new();
156 let part_metrics = self.new_partition_metrics(false, &metrics_set, 0);
157 debug_assert_eq!(1, self.properties.partitions.len());
158 let partition_ranges = &self.properties.partitions[0];
159
160 let reader = Self::merge_all_flat_ranges_for_compaction(
161 &self.stream_ctx,
162 partition_ranges,
163 &part_metrics,
164 self.pruner.clone(),
165 )
166 .await?;
167 Ok(reader)
168 }
169
170 async fn merge_all_flat_ranges_for_compaction(
173 stream_ctx: &Arc<StreamContext>,
174 partition_ranges: &[PartitionRange],
175 part_metrics: &PartitionMetrics,
176 pruner: Arc<Pruner>,
177 ) -> Result<BoxedRecordBatchStream> {
178 pruner.add_partition_ranges(partition_ranges);
179 let partition_pruner = Arc::new(PartitionPruner::new(pruner, partition_ranges));
180
181 let mut sources = Vec::new();
182 for part_range in partition_ranges {
183 build_flat_sources(
184 stream_ctx,
185 part_range,
186 FlatReadMode::Compaction,
187 part_metrics,
188 partition_pruner.clone(),
189 &mut sources,
190 None,
191 )
192 .await?;
193 }
194
195 common_telemetry::debug!(
196 "Build flat reader to read all parts, region_id: {}, num_part_ranges: {}, num_sources: {}",
197 stream_ctx.input.mapper.metadata().region_id,
198 partition_ranges.len(),
199 sources.len()
200 );
201 Self::build_flat_reader_from_sources(
202 stream_ctx,
203 sources,
204 None,
205 None,
206 false,
207 compute_parallel_channel_size(DEFAULT_READ_BATCH_SIZE),
208 )
209 .await
210 }
211
212 #[tracing::instrument(level = tracing::Level::DEBUG, skip_all)]
216 pub(crate) async fn build_flat_reader_from_sources(
217 stream_ctx: &StreamContext,
218 mut sources: Vec<BoxedRecordBatchStream>,
219 semaphore: Option<Arc<Semaphore>>,
220 part_metrics: Option<&PartitionMetrics>,
221 skip_dedup: bool,
222 channel_size: usize,
223 ) -> Result<BoxedRecordBatchStream> {
224 if let Some(semaphore) = semaphore.as_ref() {
225 if sources.len() > 1 {
227 sources = stream_ctx.input.create_parallel_flat_sources(
228 sources,
229 semaphore.clone(),
230 channel_size,
231 )?;
232 }
233 }
234
235 let mapper = &stream_ctx.input.mapper;
236 let reader: BoxedRecordBatchStream = if sources.len() == 1 {
237 sources.pop().unwrap()
240 } else {
241 let schema = mapper.input_arrow_schema(stream_ctx.input.compaction);
242 let metrics_reporter = part_metrics.map(|m| m.merge_metrics_reporter());
243 let reader = FlatMergeReader::new(
244 schema,
245 sources,
246 stream_ctx.input.batch_size(),
247 metrics_reporter,
248 )
249 .await?;
250 Box::pin(reader.into_stream())
251 };
252
253 let dedup = !skip_dedup && !stream_ctx.input.append_mode;
254 let dedup_metrics_reporter = part_metrics.map(|m| m.dedup_metrics_reporter());
255 let reader = if dedup {
256 match stream_ctx.input.merge_mode {
257 MergeMode::LastRow => Box::pin(
258 FlatDedupReader::new(
259 reader,
260 FlatLastRow::new(stream_ctx.input.filter_deleted),
261 dedup_metrics_reporter,
262 )
263 .into_stream(),
264 ) as _,
265 MergeMode::LastNonNull => Box::pin(
266 FlatDedupReader::new(
267 reader,
268 FlatLastNonNull::new(
269 mapper.field_column_start(),
270 stream_ctx.input.filter_deleted,
271 ),
272 dedup_metrics_reporter,
273 )
274 .into_stream(),
275 ) as _,
276 }
277 } else {
278 reader
279 };
280
281 let reader = match &stream_ctx.input.series_row_selector {
282 Some(TimeSeriesRowSelector::LastRow { .. }) => {
283 Box::pin(FlatLastRowReader::new(reader).into_stream()) as _
284 }
285 None => reader,
286 };
287
288 Ok(reader)
289 }
290
291 pub(crate) async fn build_flat_partition_range_read(
293 stream_ctx: &Arc<StreamContext>,
294 part_range: &PartitionRange,
295 read_mode: FlatReadMode,
296 part_metrics: &PartitionMetrics,
297 partition_pruner: Arc<PartitionPruner>,
298 file_scan_semaphore: Option<Arc<Semaphore>>,
299 merge_semaphore: Option<Arc<Semaphore>>,
300 ) -> Result<(BoxedRecordBatchStream, usize)> {
301 let cache_key = build_range_cache_key(stream_ctx, part_range).map(|key| {
302 if read_mode == FlatReadMode::CrossRangeMerge {
303 key.for_cross_range_merge()
304 } else {
305 key
306 }
307 });
308 let ctx = stream_ctx.clone();
309 let range = *part_range;
310 let metrics = part_metrics.clone();
311 CachedRangeReader {
312 stream_ctx,
313 part_range,
314 key: cache_key,
315 metrics: part_metrics,
316 }
317 .build(move || async move {
318 Self::build_uncached_partition_range(
319 &ctx,
320 &range,
321 read_mode,
322 &metrics,
323 partition_pruner,
324 file_scan_semaphore,
325 merge_semaphore,
326 )
327 .await
328 })
329 .await
330 }
331
332 async fn build_uncached_partition_range(
333 stream_ctx: &Arc<StreamContext>,
334 part_range: &PartitionRange,
335 read_mode: FlatReadMode,
336 part_metrics: &PartitionMetrics,
337 partition_pruner: Arc<PartitionPruner>,
338 file_scan_semaphore: Option<Arc<Semaphore>>,
339 merge_semaphore: Option<Arc<Semaphore>>,
340 ) -> Result<(BoxedRecordBatchStream, usize)> {
341 let mut sources = Vec::new();
342 let split_batch_size = build_flat_sources(
343 stream_ctx,
344 part_range,
345 read_mode,
346 part_metrics,
347 partition_pruner,
348 &mut sources,
349 file_scan_semaphore,
350 )
351 .await?;
352 let estimated_rows_per_batch = split_batch_size.unwrap_or(DEFAULT_READ_BATCH_SIZE);
353 let channel_size = compute_parallel_channel_size(estimated_rows_per_batch);
354 let stream = Self::build_flat_reader_from_sources(
355 stream_ctx,
356 sources,
357 merge_semaphore,
358 Some(part_metrics),
359 false,
360 channel_size,
361 )
362 .await?;
363
364 Ok((stream, estimated_rows_per_batch))
365 }
366
367 fn scan_partition_impl(
370 &self,
371 ctx: &QueryScanContext,
372 metrics_set: &ExecutionPlanMetricsSet,
373 partition: usize,
374 ) -> Result<SendableRecordBatchStream> {
375 if ctx.explain_verbose {
376 common_telemetry::info!(
377 "SeqScan partition {}, region_id: {}",
378 partition,
379 self.stream_ctx.input.region_metadata().region_id
380 );
381 }
382
383 let metrics = self.new_partition_metrics(ctx.explain_verbose, metrics_set, partition);
384 let input = &self.stream_ctx.input;
385
386 let batch_stream = self.scan_flat_batch_in_partition(partition, metrics.clone())?;
387 let record_batch_stream = ConvertBatchStream::new(
388 batch_stream,
389 input.mapper.clone(),
390 input.cache_strategy.clone(),
391 metrics,
392 );
393
394 Ok(Box::pin(RecordBatchStreamWrapper::new(
395 input.mapper.output_schema(),
396 Box::pin(record_batch_stream),
397 )))
398 }
399
400 #[tracing::instrument(
401 skip_all,
402 fields(
403 region_id = %self.stream_ctx.input.mapper.metadata().region_id,
404 partition = partition
405 )
406 )]
407 fn scan_flat_batch_in_partition(
408 &self,
409 partition: usize,
410 part_metrics: PartitionMetrics,
411 ) -> Result<ScanBatchStream> {
412 ensure!(
413 partition < self.properties.partitions.len(),
414 PartitionOutOfRangeSnafu {
415 given: partition,
416 all: self.properties.partitions.len(),
417 }
418 );
419
420 if self.properties.partitions[partition].is_empty() {
421 return Ok(Box::pin(futures::stream::empty()));
422 }
423
424 let stream_ctx = self.stream_ctx.clone();
425 let semaphore = self.new_semaphore();
426 let partition_ranges = self.properties.partitions[partition].clone();
427 let compaction = self.stream_ctx.input.compaction;
428 let file_scan_semaphore = if compaction { None } else { semaphore.clone() };
429 let pruner = self.pruner.clone();
430 pruner.add_partition_ranges(&partition_ranges);
435 let partition_pruner = Arc::new(PartitionPruner::new(pruner, &partition_ranges));
436
437 let stream = try_stream! {
438 part_metrics.on_first_poll();
439 let mut fetch_start = Instant::now();
442
443 for part_range in partition_ranges {
445 let (mut reader, _) = Self::build_flat_partition_range_read(
446 &stream_ctx,
447 &part_range,
448 if compaction {
449 FlatReadMode::Compaction
450 } else {
451 FlatReadMode::Sequential
452 },
453 &part_metrics,
454 partition_pruner.clone(),
455 file_scan_semaphore.clone(),
456 semaphore.clone(),
457 )
458 .await?;
459
460 let mut metrics = ScannerMetrics {
461 scan_cost: fetch_start.elapsed(),
462 ..Default::default()
463 };
464 fetch_start = Instant::now();
465
466 while let Some(record_batch) = reader.try_next().await? {
467 metrics.scan_cost += fetch_start.elapsed();
468 metrics.num_batches += 1;
469 metrics.num_rows += record_batch.num_rows();
470
471 debug_assert!(record_batch.num_rows() > 0);
472 if record_batch.num_rows() == 0 {
473 fetch_start = Instant::now();
474 continue;
475 }
476
477 let yield_start = Instant::now();
478 yield ScanBatch::RecordBatch(record_batch);
479 metrics.yield_cost += yield_start.elapsed();
480
481 fetch_start = Instant::now();
482 }
483
484 metrics.scan_cost += fetch_start.elapsed();
485 fetch_start = Instant::now();
486 part_metrics.merge_metrics(&metrics);
487 }
488
489 part_metrics.on_finish();
490 };
491 Ok(Box::pin(stream))
492 }
493
494 fn new_semaphore(&self) -> Option<Arc<Semaphore>> {
495 if self.properties.target_partitions() > self.properties.num_partitions() {
496 Some(Arc::new(Semaphore::new(
502 self.properties.target_partitions() - self.properties.num_partitions() + 1,
503 )))
504 } else {
505 None
506 }
507 }
508
509 fn new_partition_metrics(
512 &self,
513 explain_verbose: bool,
514 metrics_set: &ExecutionPlanMetricsSet,
515 partition: usize,
516 ) -> PartitionMetrics {
517 let metrics = PartitionMetrics::new(
518 self.stream_ctx.input.mapper.metadata().region_id,
519 partition,
520 get_scanner_type(self.stream_ctx.input.compaction),
521 self.stream_ctx.query_start,
522 explain_verbose,
523 metrics_set,
524 );
525
526 if !self.stream_ctx.input.compaction {
527 self.metrics_list.set(partition, metrics.clone());
528 }
529
530 metrics
531 }
532
533 fn max_files_in_partition(ranges: &[RangeMeta], partition_ranges: &[PartitionRange]) -> usize {
535 partition_ranges
536 .iter()
537 .map(|part_range| {
538 let range_meta = &ranges[part_range.identifier];
539 range_meta.indices.len()
540 })
541 .max()
542 .unwrap_or(0)
543 }
544
545 pub(crate) fn check_scan_limit(&self) -> Result<()> {
547 let total_max_files: usize = self
549 .properties
550 .partitions
551 .iter()
552 .map(|partition| Self::max_files_in_partition(&self.stream_ctx.ranges, partition))
553 .sum();
554
555 if !self.stream_ctx.input.compaction {
556 SCAN_MAX_CONCURRENT_FILES
557 .with_label_values(&["query"])
558 .observe(total_max_files as f64);
559 }
560 if self.stream_ctx.input.uses_scan_memory_limit() {
561 return Ok(());
562 }
563
564 let max_concurrent_files = self.stream_ctx.input.max_concurrent_scan_files;
565 if total_max_files > max_concurrent_files {
566 return TooManyFilesToReadSnafu {
567 actual: total_max_files,
568 max: max_concurrent_files,
569 }
570 .fail();
571 }
572
573 Ok(())
574 }
575}
576
577impl RegionScanner for SeqScan {
578 fn name(&self) -> &str {
579 "SeqScan"
580 }
581
582 fn properties(&self) -> &ScannerProperties {
583 &self.properties
584 }
585
586 fn schema(&self) -> SchemaRef {
587 self.stream_ctx.input.mapper.output_schema()
588 }
589
590 fn metadata(&self) -> RegionMetadataRef {
591 self.stream_ctx.input.mapper.metadata().clone()
592 }
593
594 fn scan_partition(
595 &self,
596 ctx: &QueryScanContext,
597 metrics_set: &ExecutionPlanMetricsSet,
598 partition: usize,
599 ) -> Result<SendableRecordBatchStream, BoxedError> {
600 self.scan_partition_impl(ctx, metrics_set, partition)
601 .map_err(BoxedError::new)
602 }
603
604 fn prepare(&mut self, request: PrepareRequest) -> Result<(), BoxedError> {
605 self.properties.prepare(request);
606
607 self.check_scan_limit().map_err(BoxedError::new)?;
608
609 Ok(())
610 }
611
612 fn has_predicate_without_region(&self) -> bool {
613 let predicate = self
614 .stream_ctx
615 .input
616 .predicate_group()
617 .predicate_without_region();
618 predicate.is_some()
619 }
620
621 fn add_dyn_filter_to_predicate(
622 &mut self,
623 filter_exprs: Vec<Arc<dyn datafusion::physical_plan::PhysicalExpr>>,
624 ) -> Vec<bool> {
625 self.stream_ctx.add_dyn_filter_to_predicate(filter_exprs)
626 }
627
628 fn reset_state(&mut self) {
629 self.stream_ctx.input.predicate.clear_dyn_filters();
630 let num_workers = common_stat::get_total_cpu_cores().max(1);
631 self.pruner = Arc::new(Pruner::new(self.stream_ctx.clone(), num_workers));
632 self.metrics_list = PartitionMetricsList::default();
633 }
634
635 fn set_logical_region(&mut self, logical_region: bool) {
636 self.properties.set_logical_region(logical_region);
637 }
638
639 fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
640 self.properties.set_query_load_region_id(region_id);
641 }
642
643 fn snapshot_sequence(&self) -> Option<u64> {
644 self.stream_ctx.input.snapshot_sequence
645 }
646}
647
648impl DisplayAs for SeqScan {
649 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
650 write!(
651 f,
652 "SeqScan: region={}, ",
653 self.stream_ctx.input.mapper.metadata().region_id
654 )?;
655 match t {
656 DisplayFormatType::Default | DisplayFormatType::TreeRender => {
658 self.stream_ctx.format_for_explain(false, f)
659 }
660 DisplayFormatType::Verbose => {
661 self.stream_ctx.format_for_explain(true, f)?;
662 self.metrics_list.format_verbose_metrics(f)
663 }
664 }
665 }
666}
667
668impl fmt::Debug for SeqScan {
669 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670 f.debug_struct("SeqScan")
671 .field("num_ranges", &self.stream_ctx.ranges.len())
672 .finish()
673 }
674}
675
676pub(crate) async fn build_flat_sources(
679 stream_ctx: &Arc<StreamContext>,
680 part_range: &PartitionRange,
681 read_mode: FlatReadMode,
682 part_metrics: &PartitionMetrics,
683 partition_pruner: Arc<PartitionPruner>,
684 sources: &mut Vec<BoxedRecordBatchStream>,
685 semaphore: Option<Arc<Semaphore>>,
686) -> Result<Option<usize>> {
687 let compaction = read_mode == FlatReadMode::Compaction;
688 let range_meta = &stream_ctx.ranges[part_range.identifier];
690 #[cfg(debug_assertions)]
691 if compaction {
692 debug_assert_eq!(range_meta.indices.len(), range_meta.row_group_indices.len());
694 for (i, row_group_idx) in range_meta.row_group_indices.iter().enumerate() {
695 debug_assert_eq!(
697 -1, row_group_idx.row_group_index,
698 "Expect {} range scan all row groups, given: {}",
699 i, row_group_idx.row_group_index,
700 );
701 }
702 }
703
704 let read_type = if compaction {
705 "compaction"
706 } else {
707 "seq_scan_files"
708 };
709 let num_indices = range_meta.row_group_indices.len();
710 if num_indices == 0 {
711 return Ok(None);
712 }
713
714 sources.reserve(num_indices);
715 let mut ordered_sources = Vec::with_capacity(num_indices);
716 ordered_sources.resize_with(num_indices, || None);
717 let mut file_scan_tasks = Vec::new();
718 let pre_filter_mode = stream_ctx.range_pre_filter_mode(part_range);
719
720 for (position, index) in range_meta.row_group_indices.iter().enumerate() {
721 if stream_ctx.is_mem_range_index(*index) {
722 let stream = scan_flat_mem_ranges(
723 stream_ctx.clone(),
724 part_metrics.clone(),
725 *index,
726 range_meta.time_range,
727 );
728 ordered_sources[position] = Some(Box::pin(stream) as _);
729 } else if stream_ctx.is_file_range_index(*index) {
730 if !compaction
733 && partition_pruner.try_skip_manifest_pruned_file_range(*index, part_metrics)
734 {
735 continue;
736 }
737 if let Some(semaphore_ref) = semaphore.as_ref() {
738 let stream_ctx = stream_ctx.clone();
740 let part_metrics = part_metrics.clone();
741 let partition_pruner = partition_pruner.clone();
742 let semaphore = Arc::clone(semaphore_ref);
743 let row_group_index = *index;
744 file_scan_tasks.push(async move {
745 let _permit = semaphore.acquire().await.unwrap();
746 let stream = scan_flat_file_ranges(
747 stream_ctx,
748 part_metrics,
749 row_group_index,
750 read_type,
751 partition_pruner,
752 )
753 .await?;
754 Ok((position, Box::pin(stream) as _))
755 });
756 } else {
757 let stream = scan_flat_file_ranges(
759 stream_ctx.clone(),
760 part_metrics.clone(),
761 *index,
762 read_type,
763 partition_pruner.clone(),
764 )
765 .await?;
766 ordered_sources[position] = Some(Box::pin(stream) as _);
767 }
768 } else {
769 let stream = scan_util::maybe_scan_flat_other_ranges(
770 stream_ctx,
771 *index,
772 part_metrics,
773 pre_filter_mode,
774 )
775 .await?;
776 ordered_sources[position] = Some(stream);
777 }
778 }
779
780 if !file_scan_tasks.is_empty() {
781 let results = futures::future::try_join_all(file_scan_tasks).await?;
782 for (position, stream) in results {
783 ordered_sources[position] = Some(stream);
784 }
785 }
786
787 let needs_merge =
790 ordered_sources.iter().flatten().count() > 1 || (compaction && stream_ctx.ranges.len() > 1);
791 let split_batch_size = if needs_merge || read_mode == FlatReadMode::CrossRangeMerge {
792 should_split_flat_batches_for_merge(stream_ctx, range_meta).filter(|rows_per_series| {
793 if !needs_merge && *rows_per_series < BATCH_SIZE_THRESHOLD as usize {
796 return false;
797 }
798 !compaction || *rows_per_series >= MIN_COMPACTION_SPLIT_ROWS
801 })
802 } else {
803 None
804 };
805 let should_split = split_batch_size.is_some();
806
807 for stream in ordered_sources.into_iter().flatten() {
808 if should_split {
809 sources.push(Box::pin(SplitRecordBatchStream::new(stream)));
810 } else {
811 sources.push(stream);
812 }
813 }
814
815 if should_split {
816 common_telemetry::debug!(
817 "Splitting record batches, region: {}, sources: {}, part_range: {:?}",
818 stream_ctx.input.region_metadata().region_id,
819 sources.len(),
820 part_range,
821 );
822 }
823
824 Ok(split_batch_size)
825}
826
827#[cfg(test)]
828impl SeqScan {
829 pub(crate) fn input(&self) -> &ScanInput {
831 &self.stream_ctx.input
832 }
833}
834
835fn get_scanner_type(compaction: bool) -> &'static str {
837 if compaction {
838 "SeqScan(compaction)"
839 } else {
840 "SeqScan"
841 }
842}