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