Skip to main content

mito2/read/
unordered_scan.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Unordered scanner.
16
17use std::fmt;
18use std::sync::Arc;
19use std::time::Instant;
20
21use async_stream::{stream, try_stream};
22use common_error::ext::BoxedError;
23use common_recordbatch::{RecordBatchStreamWrapper, SendableRecordBatchStream};
24use common_telemetry::{tracing, warn};
25use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
26use datafusion::physical_plan::{DisplayAs, DisplayFormatType};
27use datatypes::arrow::record_batch::RecordBatch;
28use datatypes::schema::SchemaRef;
29use futures::{Stream, StreamExt};
30use snafu::ensure;
31use store_api::metadata::RegionMetadataRef;
32use store_api::region_engine::{
33    PrepareRequest, QueryScanContext, RegionScanner, ScannerProperties,
34};
35
36use crate::error::{PartitionOutOfRangeSnafu, Result};
37use crate::read::pruner::{PartitionPruner, Pruner};
38use crate::read::scan_region::{ScanInput, StreamContext};
39use crate::read::scan_util::{
40    PartitionMetrics, PartitionMetricsList, scan_flat_file_ranges, scan_flat_mem_ranges,
41};
42use crate::read::stream::{ConvertBatchStream, ScanBatch, ScanBatchStream};
43use crate::read::{ScannerMetrics, scan_util};
44
45/// Scans a region without providing any output ordering guarantee.
46///
47/// Only an append only table should use this scanner.
48pub struct UnorderedScan {
49    /// Properties of the scanner.
50    properties: ScannerProperties,
51    /// Context of streams.
52    stream_ctx: Arc<StreamContext>,
53    /// Shared pruner for file range building.
54    pruner: Arc<Pruner>,
55    /// Metrics for each partition.
56    metrics_list: PartitionMetricsList,
57}
58
59impl UnorderedScan {
60    /// Creates a new [UnorderedScan].
61    pub(crate) fn new(input: ScanInput) -> Self {
62        let mut properties = ScannerProperties::default()
63            .with_append_mode(input.append_mode)
64            .with_total_rows(input.total_rows())
65            .with_total_rows_is_exact(input.append_mode && input.total_rows_is_exact());
66        if let Some(counters) = input.query_stat_counters.clone() {
67            properties.set_query_stat_counters(counters);
68        }
69        let stream_ctx = Arc::new(StreamContext::unordered_scan_ctx(input));
70        properties.partitions = vec![stream_ctx.partition_ranges()];
71
72        // Create the shared pruner with number of workers equal to CPU cores.
73        let num_workers = common_stat::get_total_cpu_cores().max(1);
74        let pruner = Arc::new(Pruner::new(stream_ctx.clone(), num_workers));
75
76        Self {
77            properties,
78            stream_ctx,
79            pruner,
80            metrics_list: PartitionMetricsList::default(),
81        }
82    }
83
84    /// Scans the region and returns a stream.
85    #[tracing::instrument(
86        skip_all,
87        fields(region_id = %self.stream_ctx.input.mapper.metadata().region_id)
88    )]
89    pub(crate) async fn build_stream(&self) -> Result<SendableRecordBatchStream, BoxedError> {
90        let metrics_set = ExecutionPlanMetricsSet::new();
91        let part_num = self.properties.num_partitions();
92        let ctx = QueryScanContext::default();
93        let streams = (0..part_num)
94            .map(|i| self.scan_partition(&ctx, &metrics_set, i))
95            .collect::<Result<Vec<_>, BoxedError>>()?;
96        let stream = stream! {
97            for mut stream in streams {
98                while let Some(rb) = stream.next().await {
99                    yield rb;
100                }
101            }
102        };
103        let stream = Box::pin(RecordBatchStreamWrapper::new(
104            self.schema(),
105            Box::pin(stream),
106        ));
107        Ok(stream)
108    }
109
110    /// Scans a [PartitionRange] by its `identifier` and returns a flat stream of RecordBatch.
111    #[tracing::instrument(
112        skip_all,
113        fields(
114            region_id = %stream_ctx.input.region_metadata().region_id,
115            part_range_id = part_range_id
116        )
117    )]
118    fn scan_flat_partition_range(
119        stream_ctx: Arc<StreamContext>,
120        part_range_id: usize,
121        part_metrics: PartitionMetrics,
122        partition_pruner: Arc<PartitionPruner>,
123    ) -> impl Stream<Item = Result<RecordBatch>> {
124        try_stream! {
125            // Gets range meta.
126            let range_meta = &stream_ctx.ranges[part_range_id];
127            let part_range = range_meta.new_partition_range(part_range_id);
128            let pre_filter_mode = stream_ctx.range_pre_filter_mode(&part_range);
129            for index in &range_meta.row_group_indices {
130                if stream_ctx.is_mem_range_index(*index) {
131                    let stream = scan_flat_mem_ranges(
132                        stream_ctx.clone(),
133                        part_metrics.clone(),
134                        *index,
135                        range_meta.time_range,
136                    );
137                    for await record_batch in stream {
138                        yield record_batch?;
139                    }
140                } else if stream_ctx.is_file_range_index(*index) {
141                    // Common manifest-level fast-skip shared by UnorderedScan and SeqScan.
142                    if partition_pruner
143                        .try_skip_manifest_pruned_file_range(*index, &part_metrics)
144                    {
145                        continue;
146                    }
147                    let stream = scan_flat_file_ranges(
148                        stream_ctx.clone(),
149                        part_metrics.clone(),
150                        *index,
151                        "unordered_scan_files",
152                        partition_pruner.clone(),
153                    ).await?;
154                    for await record_batch in stream {
155                        yield record_batch?;
156                    }
157                } else {
158                    let stream = scan_util::maybe_scan_flat_other_ranges(
159                        &stream_ctx,
160                        *index,
161                        &part_metrics,
162                        pre_filter_mode,
163                    ).await?;
164                    for await record_batch in stream {
165                        yield record_batch?;
166                    }
167                }
168            }
169        }
170    }
171
172    /// Scan [`Batch`] in all partitions one by one.
173    pub(crate) fn scan_all_partitions(&self) -> Result<ScanBatchStream> {
174        let metrics_set = ExecutionPlanMetricsSet::new();
175
176        let streams = (0..self.properties.partitions.len())
177            .map(|partition| {
178                let metrics = self.partition_metrics(false, partition, &metrics_set);
179                self.scan_flat_batch_in_partition(partition, metrics)
180            })
181            .collect::<Result<Vec<_>>>()?;
182
183        Ok(Box::pin(futures::stream::iter(streams).flatten()))
184    }
185
186    fn partition_metrics(
187        &self,
188        explain_verbose: bool,
189        partition: usize,
190        metrics_set: &ExecutionPlanMetricsSet,
191    ) -> PartitionMetrics {
192        let part_metrics = PartitionMetrics::new(
193            self.stream_ctx.input.mapper.metadata().region_id,
194            partition,
195            "UnorderedScan",
196            self.stream_ctx.query_start,
197            explain_verbose,
198            metrics_set,
199        );
200        self.metrics_list.set(partition, part_metrics.clone());
201        part_metrics
202    }
203
204    #[tracing::instrument(
205        skip_all,
206        fields(
207            region_id = %self.stream_ctx.input.mapper.metadata().region_id,
208            partition = partition
209        )
210    )]
211    fn scan_partition_impl(
212        &self,
213        ctx: &QueryScanContext,
214        metrics_set: &ExecutionPlanMetricsSet,
215        partition: usize,
216    ) -> Result<SendableRecordBatchStream> {
217        if ctx.explain_verbose {
218            common_telemetry::info!(
219                "UnorderedScan partition {}, region_id: {}",
220                partition,
221                self.stream_ctx.input.region_metadata().region_id
222            );
223        }
224
225        let metrics = self.partition_metrics(ctx.explain_verbose, partition, metrics_set);
226        let input = &self.stream_ctx.input;
227
228        let batch_stream = self.scan_flat_batch_in_partition(partition, metrics.clone())?;
229
230        let record_batch_stream = ConvertBatchStream::new(
231            batch_stream,
232            input.mapper.clone(),
233            input.cache_strategy.clone(),
234            metrics,
235        );
236
237        Ok(Box::pin(RecordBatchStreamWrapper::new(
238            input.mapper.output_schema(),
239            Box::pin(record_batch_stream),
240        )))
241    }
242
243    #[tracing::instrument(
244        skip_all,
245        fields(
246            region_id = %self.stream_ctx.input.mapper.metadata().region_id,
247            partition = partition
248        )
249    )]
250    fn scan_flat_batch_in_partition(
251        &self,
252        partition: usize,
253        part_metrics: PartitionMetrics,
254    ) -> Result<ScanBatchStream> {
255        ensure!(
256            partition < self.properties.partitions.len(),
257            PartitionOutOfRangeSnafu {
258                given: partition,
259                all: self.properties.partitions.len(),
260            }
261        );
262
263        let stream_ctx = self.stream_ctx.clone();
264        let part_ranges = self.properties.partitions[partition].clone();
265        let pruner = self.pruner.clone();
266        // Initializes ref counts for the pruner.
267        // If we call scan_batch_in_partition() multiple times but don't read all batches from the stream,
268        // then the ref count won't be decremented.
269        // This is a rare case and keeping all remaining entries still uses less memory than a per partition cache.
270        pruner.add_partition_ranges(&part_ranges);
271        let partition_pruner = Arc::new(PartitionPruner::new(pruner, &part_ranges));
272
273        let stream = try_stream! {
274            part_metrics.on_first_poll();
275
276            // Scans each part.
277            for part_range in part_ranges {
278                let mut metrics = ScannerMetrics::default();
279                let mut fetch_start = Instant::now();
280
281                let stream = Self::scan_flat_partition_range(
282                    stream_ctx.clone(),
283                    part_range.identifier,
284                    part_metrics.clone(),
285                    partition_pruner.clone(),
286                );
287                for await record_batch in stream {
288                    let record_batch = record_batch?;
289                    metrics.scan_cost += fetch_start.elapsed();
290                    metrics.num_batches += 1;
291                    metrics.num_rows += record_batch.num_rows();
292
293                    debug_assert!(record_batch.num_rows() > 0);
294                    if record_batch.num_rows() == 0 {
295                        continue;
296                    }
297
298                    let yield_start = Instant::now();
299                    yield ScanBatch::RecordBatch(record_batch);
300                    metrics.yield_cost += yield_start.elapsed();
301
302                    fetch_start = Instant::now();
303                }
304
305                metrics.scan_cost += fetch_start.elapsed();
306                part_metrics.merge_metrics(&metrics);
307            }
308
309            part_metrics.on_finish();
310        };
311        Ok(Box::pin(stream))
312    }
313}
314
315impl RegionScanner for UnorderedScan {
316    fn name(&self) -> &str {
317        "UnorderedScan"
318    }
319
320    fn properties(&self) -> &ScannerProperties {
321        &self.properties
322    }
323
324    fn schema(&self) -> SchemaRef {
325        self.stream_ctx.input.mapper.output_schema()
326    }
327
328    fn metadata(&self) -> RegionMetadataRef {
329        self.stream_ctx.input.mapper.metadata().clone()
330    }
331
332    fn prepare(&mut self, request: PrepareRequest) -> Result<(), BoxedError> {
333        self.properties.prepare(request);
334
335        Ok(())
336    }
337
338    fn scan_partition(
339        &self,
340        ctx: &QueryScanContext,
341        metrics_set: &ExecutionPlanMetricsSet,
342        partition: usize,
343    ) -> Result<SendableRecordBatchStream, BoxedError> {
344        self.scan_partition_impl(ctx, metrics_set, partition)
345            .map_err(BoxedError::new)
346    }
347
348    /// If this scanner have predicate other than region partition exprs
349    fn has_predicate_without_region(&self) -> bool {
350        let predicate = self
351            .stream_ctx
352            .input
353            .predicate_group()
354            .predicate_without_region();
355        predicate.is_some()
356    }
357
358    fn add_dyn_filter_to_predicate(
359        &mut self,
360        filter_exprs: Vec<Arc<dyn datafusion::physical_plan::PhysicalExpr>>,
361    ) -> Vec<bool> {
362        self.stream_ctx.add_dyn_filter_to_predicate(filter_exprs)
363    }
364
365    fn reset_state(&mut self) {
366        self.stream_ctx.input.predicate.clear_dyn_filters();
367        let num_workers = common_stat::get_total_cpu_cores().max(1);
368        self.pruner = Arc::new(Pruner::new(self.stream_ctx.clone(), num_workers));
369        self.metrics_list = PartitionMetricsList::default();
370    }
371
372    fn set_logical_region(&mut self, logical_region: bool) {
373        self.properties.set_logical_region(logical_region);
374    }
375
376    fn set_query_load_region_id(&mut self, region_id: store_api::storage::RegionId) {
377        self.properties.set_query_load_region_id(region_id);
378    }
379
380    fn snapshot_sequence(&self) -> Option<u64> {
381        self.stream_ctx.input.snapshot_sequence
382    }
383}
384
385impl DisplayAs for UnorderedScan {
386    fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
387        write!(
388            f,
389            "UnorderedScan: region={}, ",
390            self.stream_ctx.input.mapper.metadata().region_id
391        )?;
392        match t {
393            DisplayFormatType::Default | DisplayFormatType::TreeRender => {
394                self.stream_ctx.format_for_explain(false, f)
395            }
396            DisplayFormatType::Verbose => {
397                self.stream_ctx.format_for_explain(true, f)?;
398                self.metrics_list.format_verbose_metrics(f)
399            }
400        }
401    }
402}
403
404impl fmt::Debug for UnorderedScan {
405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406        f.debug_struct("UnorderedScan")
407            .field("num_ranges", &self.stream_ctx.ranges.len())
408            .finish()
409    }
410}
411
412#[cfg(test)]
413impl UnorderedScan {
414    /// Returns the input.
415    pub(crate) fn input(&self) -> &ScanInput {
416        &self.stream_ctx.input
417    }
418}