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