Skip to main content

metric_engine/engine/
read.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
15use std::sync::Arc;
16
17use api::v1::SemanticType;
18use common_telemetry::{debug, error, tracing};
19use datafusion::logical_expr::{self, Expr};
20use snafu::{OptionExt, ResultExt};
21use store_api::metadata::{RegionMetadataBuilder, RegionMetadataRef};
22use store_api::metric_engine_consts::DATA_SCHEMA_TABLE_ID_COLUMN_NAME;
23use store_api::region_engine::{RegionEngine, RegionScannerRef};
24use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
25
26use crate::engine::MetricEngineInner;
27use crate::error::{
28    InvalidMetadataSnafu, InvalidRequestSnafu, LogicalRegionNotFoundSnafu, MitoReadOperationSnafu,
29    Result,
30};
31use crate::metrics::MITO_OPERATION_ELAPSED;
32use crate::utils;
33
34impl MetricEngineInner {
35    #[tracing::instrument(skip_all)]
36    pub async fn read_region(
37        &self,
38        region_id: RegionId,
39        request: ScanRequest,
40    ) -> Result<RegionScannerRef> {
41        let is_reading_physical_region = self.is_physical_region(region_id);
42
43        if is_reading_physical_region {
44            debug!(
45                "Metric region received read request {request:?} on physical region {region_id:?}"
46            );
47            self.read_physical_region(region_id, request).await
48        } else {
49            self.read_logical_region(region_id, request).await
50        }
51    }
52
53    /// Proxy the read request to underlying physical region (mito engine).
54    async fn read_physical_region(
55        &self,
56        region_id: RegionId,
57        request: ScanRequest,
58    ) -> Result<RegionScannerRef> {
59        let _timer = MITO_OPERATION_ELAPSED
60            .with_label_values(&["read_physical"])
61            .start_timer();
62
63        self.mito
64            .handle_query(region_id, request)
65            .await
66            .context(MitoReadOperationSnafu)
67    }
68
69    async fn read_logical_region(
70        &self,
71        logical_region_id: RegionId,
72        request: ScanRequest,
73    ) -> Result<RegionScannerRef> {
74        let _timer = MITO_OPERATION_ELAPSED
75            .with_label_values(&["read"])
76            .start_timer();
77
78        let physical_region_id = self.get_physical_region_id(logical_region_id).await?;
79        let data_region_id = utils::to_data_region_id(physical_region_id);
80        let request = self
81            .transform_request(physical_region_id, logical_region_id, request)
82            .await?;
83        let mut scanner = self
84            .mito
85            .handle_query(data_region_id, request)
86            .await
87            .context(MitoReadOperationSnafu)?;
88        scanner.set_logical_region(true);
89        scanner.set_query_load_region_id(data_region_id);
90
91        Ok(scanner)
92    }
93
94    pub async fn get_last_seq_num(&self, region_id: RegionId) -> Result<SequenceNumber> {
95        let region_id = if self.is_physical_region(region_id) {
96            region_id
97        } else {
98            let physical_region_id = self.get_physical_region_id(region_id).await?;
99            utils::to_data_region_id(physical_region_id)
100        };
101        self.mito
102            .get_committed_sequence(region_id)
103            .await
104            .context(MitoReadOperationSnafu)
105    }
106
107    pub async fn load_region_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef> {
108        let is_reading_physical_region =
109            self.state.read().unwrap().exist_physical_region(region_id);
110
111        if is_reading_physical_region {
112            self.mito
113                .get_metadata(region_id)
114                .await
115                .context(MitoReadOperationSnafu)
116        } else {
117            let physical_region_id = self.get_physical_region_id(region_id).await?;
118            self.logical_region_metadata(physical_region_id, region_id)
119                .await
120        }
121    }
122
123    /// Returns true if it's a physical region.
124    pub fn is_physical_region(&self, region_id: RegionId) -> bool {
125        self.state.read().unwrap().exist_physical_region(region_id)
126    }
127
128    async fn get_physical_region_id(&self, logical_region_id: RegionId) -> Result<RegionId> {
129        let state = &self.state.read().unwrap();
130        state
131            .get_physical_region_id(logical_region_id)
132            .with_context(|| {
133                error!("Trying to read an nonexistent region {logical_region_id}");
134                LogicalRegionNotFoundSnafu {
135                    region_id: logical_region_id,
136                }
137            })
138    }
139
140    /// Transform the [ScanRequest] from logical region to physical data region.
141    async fn transform_request(
142        &self,
143        physical_region_id: RegionId,
144        logical_region_id: RegionId,
145        mut request: ScanRequest,
146    ) -> Result<ScanRequest> {
147        // transform projection
148        let physical_projection = match request.projection.as_ref() {
149            Some(projection) => {
150                self.transform_projection(physical_region_id, logical_region_id, projection)
151                    .await?
152            }
153            None => {
154                self.default_projection(physical_region_id, logical_region_id)
155                    .await?
156            }
157        };
158
159        // Rewrite the projection from logical-region schema indices to
160        // physical-region schema indices.
161        request.projection = Some(physical_projection);
162
163        request
164            .filters
165            .push(self.table_id_filter(logical_region_id));
166
167        Ok(request)
168    }
169
170    /// Generate a filter on the table id column.
171    fn table_id_filter(&self, logical_region_id: RegionId) -> Expr {
172        logical_expr::col(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
173            .eq(logical_expr::lit(logical_region_id.table_id()))
174    }
175
176    /// Transform the projection from logical region to physical region.
177    ///
178    /// This method will not preserve internal columns.
179    pub async fn transform_projection(
180        &self,
181        physical_region_id: RegionId,
182        logical_region_id: RegionId,
183        origin_projection: &[usize],
184    ) -> Result<Vec<usize>> {
185        // project on logical columns
186        let all_logical_columns = self
187            .load_logical_column_names(physical_region_id, logical_region_id)
188            .await?;
189        let projected_logical_names = origin_projection
190            .iter()
191            .map(|&index| {
192                all_logical_columns
193                    .get(index)
194                    .map(String::as_str)
195                    .with_context(|| InvalidRequestSnafu {
196                        region_id: logical_region_id,
197                        reason: format!("projection index {index} is out of bounds"),
198                    })
199            })
200            .collect::<Result<Vec<_>>>()?;
201
202        // generate physical projection
203        let mut physical_projection = Vec::with_capacity(origin_projection.len());
204        let data_region_id = utils::to_data_region_id(physical_region_id);
205        let physical_metadata = self
206            .mito
207            .get_metadata(data_region_id)
208            .await
209            .context(MitoReadOperationSnafu)?;
210
211        for name in projected_logical_names {
212            // Safety: logical columns is a strict subset of physical columns
213            physical_projection.push(physical_metadata.column_index_by_name(name).unwrap());
214        }
215
216        Ok(physical_projection)
217    }
218
219    /// Default projection for a logical region. Includes non-internal columns
220    pub async fn default_projection(
221        &self,
222        physical_region_id: RegionId,
223        logical_region_id: RegionId,
224    ) -> Result<Vec<usize>> {
225        let logical_columns = self
226            .load_logical_column_names(physical_region_id, logical_region_id)
227            .await?;
228        let mut projection = Vec::with_capacity(logical_columns.len());
229        let data_region_id = utils::to_data_region_id(physical_region_id);
230        let physical_metadata = self
231            .mito
232            .get_metadata(data_region_id)
233            .await
234            .context(MitoReadOperationSnafu)?;
235        for name in logical_columns {
236            // Safety: logical columns is a strict subset of physical columns
237            projection.push(physical_metadata.column_index_by_name(&name).unwrap());
238        }
239
240        Ok(projection)
241    }
242
243    pub async fn logical_region_metadata(
244        &self,
245        physical_region_id: RegionId,
246        logical_region_id: RegionId,
247    ) -> Result<RegionMetadataRef> {
248        let logical_columns = self
249            .load_logical_columns(physical_region_id, logical_region_id)
250            .await?;
251
252        let primary_keys = logical_columns
253            .iter()
254            .filter_map(|col| {
255                if col.semantic_type == SemanticType::Tag {
256                    Some(col.column_id)
257                } else {
258                    None
259                }
260            })
261            .collect::<Vec<_>>();
262
263        let mut logical_metadata_builder = RegionMetadataBuilder::new(logical_region_id);
264        for col in logical_columns {
265            logical_metadata_builder.push_column_metadata(col);
266        }
267        logical_metadata_builder.primary_key(primary_keys);
268        let logical_metadata = logical_metadata_builder
269            .build()
270            .context(InvalidMetadataSnafu)?;
271
272        Ok(Arc::new(logical_metadata))
273    }
274}
275
276#[cfg(test)]
277impl MetricEngineInner {
278    pub async fn scan_to_stream(
279        &self,
280        region_id: RegionId,
281        request: ScanRequest,
282    ) -> Result<common_recordbatch::SendableRecordBatchStream, common_error::ext::BoxedError> {
283        let is_reading_physical_region = self.is_physical_region(region_id);
284
285        if is_reading_physical_region {
286            self.mito
287                .scan_to_stream(region_id, request)
288                .await
289                .map_err(common_error::ext::BoxedError::new)
290        } else {
291            let physical_region_id = self
292                .get_physical_region_id(region_id)
293                .await
294                .map_err(common_error::ext::BoxedError::new)?;
295            let request = self
296                .transform_request(physical_region_id, region_id, request)
297                .await
298                .map_err(common_error::ext::BoxedError::new)?;
299            self.mito
300                .scan_to_stream(physical_region_id, request)
301                .await
302                .map_err(common_error::ext::BoxedError::new)
303        }
304    }
305}
306
307#[cfg(test)]
308mod test {
309    use std::fmt;
310
311    use api::v1::Rows;
312    use common_error::ext::ErrorExt;
313    use common_error::status_code::StatusCode;
314    use datafusion::physical_plan::DisplayFormatType;
315    use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
316    use futures_util::TryStreamExt;
317    use futures_util::future::try_join_all;
318    use mito2::config::MitoConfig;
319    use store_api::region_engine::{PrepareRequest, QueryScanContext};
320    use store_api::region_request::{RegionFlushRequest, RegionPutRequest, RegionRequest};
321    use store_api::storage::{TimeSeriesDistribution, TimeSeriesRowSelector};
322
323    use super::*;
324    use crate::config::EngineConfig;
325    use crate::test_util::{
326        self, TestEnv, alter_logical_region_add_tag_columns, create_logical_region_request,
327    };
328
329    struct ScannerDisplay<'a>(&'a dyn store_api::region_engine::RegionScanner);
330
331    impl fmt::Display for ScannerDisplay<'_> {
332        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333            self.0.fmt_as(DisplayFormatType::Default, f)
334        }
335    }
336
337    async fn count_scanner_rows(scanner: &mut RegionScannerRef, partitions: usize) -> usize {
338        let ranges = scanner
339            .properties()
340            .partitions
341            .iter()
342            .flatten()
343            .copied()
344            .collect::<Vec<_>>();
345        let mut prepared = vec![Vec::new(); partitions];
346        prepared[0] = ranges;
347        scanner
348            .prepare(
349                PrepareRequest::default()
350                    .with_ranges(prepared)
351                    .with_target_partitions(partitions),
352            )
353            .unwrap();
354
355        let metrics = ExecutionPlanMetricsSet::default();
356        let context = QueryScanContext::default();
357        let streams = (0..partitions)
358            .map(|partition| {
359                scanner
360                    .scan_partition(&context, &metrics, partition)
361                    .unwrap()
362            })
363            .collect::<Vec<_>>();
364        try_join_all(streams.into_iter().map(|stream| async move {
365            stream
366                .try_fold(0, |rows, batch| async move { Ok(rows + batch.num_rows()) })
367                .await
368        }))
369        .await
370        .unwrap()
371        .into_iter()
372        .sum()
373    }
374
375    #[tokio::test]
376    async fn test_invalid_logical_projection() {
377        let env = TestEnv::new().await;
378        env.init_metric_region().await;
379
380        let logical_region_id = env.default_logical_region_id();
381        let invalid_index = usize::MAX;
382        let request = ScanRequest {
383            projection: Some(vec![invalid_index]),
384            ..Default::default()
385        };
386
387        let error =
388            match RegionEngine::handle_query(&env.metric(), logical_region_id, request).await {
389                Ok(_) => panic!("invalid logical projection unexpectedly succeeded"),
390                Err(error) => error,
391            };
392
393        assert_eq!(error.status_code(), StatusCode::InvalidArguments);
394        assert!(
395            error.to_string().contains(&format!(
396                "projection index {invalid_index} is out of bounds"
397            )),
398            "unexpected error: {error}"
399        );
400    }
401
402    #[tokio::test]
403    async fn test_transform_scan_req() {
404        let env = TestEnv::new().await;
405        env.init_metric_region().await;
406
407        let logical_region_id = env.default_logical_region_id();
408        let physical_region_id = env.default_physical_region_id();
409
410        // create another logical region
411        let logical_region_id2 = RegionId::new(1112345678, 999);
412        let create_request =
413            create_logical_region_request(&["123", "456", "789"], physical_region_id, "blabla");
414        env.metric()
415            .handle_request(logical_region_id2, RegionRequest::Create(create_request))
416            .await
417            .unwrap();
418
419        // add columns to the first logical region
420        let alter_request =
421            alter_logical_region_add_tag_columns(123456, &["987", "798", "654", "321"]);
422        env.metric()
423            .handle_request(logical_region_id, RegionRequest::Alter(alter_request))
424            .await
425            .unwrap();
426
427        // check explicit projection
428        let projection = Some(vec![0, 1, 2, 3, 4, 5, 6]);
429        let scan_req = ScanRequest {
430            projection,
431            filters: vec![],
432            series_row_selector: Some(TimeSeriesRowSelector::LastRow { after_merge: true }),
433            ..Default::default()
434        };
435
436        let scan_req = env
437            .metric()
438            .inner
439            .transform_request(physical_region_id, logical_region_id, scan_req)
440            .await
441            .unwrap();
442
443        assert_eq!(
444            scan_req.projection.as_deref().unwrap(),
445            &[11, 10, 9, 8, 0, 1, 4]
446        );
447        assert_eq!(scan_req.filters.len(), 1);
448        assert_eq!(
449            scan_req.series_row_selector,
450            Some(TimeSeriesRowSelector::LastRow { after_merge: true })
451        );
452        assert_eq!(
453            scan_req.filters[0],
454            logical_expr::col(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
455                .eq(logical_expr::lit(logical_region_id.table_id()))
456        );
457
458        // check default projection
459        let scan_req = ScanRequest::default();
460        let scan_req = env
461            .metric()
462            .inner
463            .transform_request(physical_region_id, logical_region_id, scan_req)
464            .await
465            .unwrap();
466        assert_eq!(
467            scan_req.projection.as_deref().unwrap(),
468            &[11, 10, 9, 8, 0, 1, 4]
469        );
470    }
471
472    #[tokio::test]
473    async fn test_two_phase_series_scan_reads_metric_region() {
474        let env = TestEnv::with_mito_config(
475            "test_two_phase_series_scan",
476            MitoConfig {
477                experimental_series_scan_v2: true,
478                ..Default::default()
479            },
480            EngineConfig::default(),
481        )
482        .await;
483        env.init_metric_region().await;
484
485        let physical_region_id = env.default_physical_region_id();
486        let logical_region_id = env.default_logical_region_id();
487        let logical_region_id_2 = RegionId::new(1024, logical_region_id.region_number());
488        env.metric()
489            .handle_request(
490                logical_region_id_2,
491                RegionRequest::Create(create_logical_region_request(
492                    &["job"],
493                    physical_region_id,
494                    "test_metric_region_2",
495                )),
496            )
497            .await
498            .unwrap();
499
500        let schema = test_util::row_schema_with_tags(&["job"]);
501        let put = |rows| {
502            RegionRequest::Put(RegionPutRequest {
503                skip_wal: false,
504                rows: Rows {
505                    schema: schema.clone(),
506                    rows: test_util::build_rows(1, rows),
507                },
508                hint: None,
509                partition_expr_version: None,
510            })
511        };
512        env.metric()
513            .handle_request(logical_region_id, put(3))
514            .await
515            .unwrap();
516        env.metric()
517            .handle_request(
518                physical_region_id,
519                RegionRequest::Flush(RegionFlushRequest::default()),
520            )
521            .await
522            .unwrap();
523        env.metric()
524            .handle_request(logical_region_id, put(2))
525            .await
526            .unwrap();
527        env.metric()
528            .handle_request(logical_region_id_2, put(4))
529            .await
530            .unwrap();
531
532        let data_region_id = utils::to_data_region_id(physical_region_id);
533        let request = ScanRequest {
534            distribution: Some(TimeSeriesDistribution::PerSeries),
535            ..Default::default()
536        };
537        let mut scanner = env
538            .mito()
539            .handle_query(data_region_id, request)
540            .await
541            .unwrap();
542
543        let rows = tokio::time::timeout(
544            std::time::Duration::from_secs(30),
545            count_scanner_rows(&mut scanner, 2),
546        )
547        .await
548        .expect("two-phase series scan should not deadlock");
549        assert_eq!(7, rows);
550
551        let explain = ScannerDisplay(scanner.as_ref()).to_string();
552        assert!(explain.contains("\"mode\":\"two_phase\""), "{explain}");
553    }
554
555    #[tokio::test]
556    async fn test_series_scan_v2_flag_disables_two_phase_mode() {
557        let env = TestEnv::with_mito_config(
558            "test_disable_two_phase_series_scan",
559            MitoConfig {
560                experimental_series_scan_v2: false,
561                ..Default::default()
562            },
563            EngineConfig::default(),
564        )
565        .await;
566        env.init_metric_region().await;
567
568        let data_region_id = utils::to_data_region_id(env.default_physical_region_id());
569        let scanner = env
570            .mito()
571            .handle_query(
572                data_region_id,
573                ScanRequest {
574                    distribution: Some(TimeSeriesDistribution::PerSeries),
575                    ..Default::default()
576                },
577            )
578            .await
579            .unwrap();
580
581        let explain = ScannerDisplay(scanner.as_ref()).to_string();
582        assert!(explain.contains("\"mode\":\"legacy\""), "{explain}");
583    }
584}