1use 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 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 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 async fn transform_request(
142 &self,
143 physical_region_id: RegionId,
144 logical_region_id: RegionId,
145 mut request: ScanRequest,
146 ) -> Result<ScanRequest> {
147 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 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 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 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 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 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 physical_projection.push(physical_metadata.column_index_by_name(name).unwrap());
214 }
215
216 Ok(physical_projection)
217 }
218
219 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 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;
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 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 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 let projection = Some(vec![0, 1, 2, 3, 4, 5, 6]);
429 let scan_req = ScanRequest {
430 projection,
431 filters: vec![],
432 ..Default::default()
433 };
434
435 let scan_req = env
436 .metric()
437 .inner
438 .transform_request(physical_region_id, logical_region_id, scan_req)
439 .await
440 .unwrap();
441
442 assert_eq!(
443 scan_req.projection.as_deref().unwrap(),
444 &[11, 10, 9, 8, 0, 1, 4]
445 );
446 assert_eq!(scan_req.filters.len(), 1);
447 assert_eq!(
448 scan_req.filters[0],
449 logical_expr::col(DATA_SCHEMA_TABLE_ID_COLUMN_NAME)
450 .eq(logical_expr::lit(logical_region_id.table_id()))
451 );
452
453 let scan_req = ScanRequest::default();
455 let scan_req = env
456 .metric()
457 .inner
458 .transform_request(physical_region_id, logical_region_id, scan_req)
459 .await
460 .unwrap();
461 assert_eq!(
462 scan_req.projection.as_deref().unwrap(),
463 &[11, 10, 9, 8, 0, 1, 4]
464 );
465 }
466
467 #[tokio::test]
468 async fn test_two_phase_series_scan_reads_metric_region() {
469 let env = TestEnv::with_mito_config(
470 "test_two_phase_series_scan",
471 MitoConfig {
472 experimental_series_scan_v2: true,
473 ..Default::default()
474 },
475 EngineConfig::default(),
476 )
477 .await;
478 env.init_metric_region().await;
479
480 let physical_region_id = env.default_physical_region_id();
481 let logical_region_id = env.default_logical_region_id();
482 let logical_region_id_2 = RegionId::new(1024, logical_region_id.region_number());
483 env.metric()
484 .handle_request(
485 logical_region_id_2,
486 RegionRequest::Create(create_logical_region_request(
487 &["job"],
488 physical_region_id,
489 "test_metric_region_2",
490 )),
491 )
492 .await
493 .unwrap();
494
495 let schema = test_util::row_schema_with_tags(&["job"]);
496 let put = |rows| {
497 RegionRequest::Put(RegionPutRequest {
498 rows: Rows {
499 schema: schema.clone(),
500 rows: test_util::build_rows(1, rows),
501 },
502 hint: None,
503 partition_expr_version: None,
504 })
505 };
506 env.metric()
507 .handle_request(logical_region_id, put(3))
508 .await
509 .unwrap();
510 env.metric()
511 .handle_request(
512 physical_region_id,
513 RegionRequest::Flush(RegionFlushRequest::default()),
514 )
515 .await
516 .unwrap();
517 env.metric()
518 .handle_request(logical_region_id, put(2))
519 .await
520 .unwrap();
521 env.metric()
522 .handle_request(logical_region_id_2, put(4))
523 .await
524 .unwrap();
525
526 let data_region_id = utils::to_data_region_id(physical_region_id);
527 let request = ScanRequest {
528 distribution: Some(TimeSeriesDistribution::PerSeries),
529 ..Default::default()
530 };
531 let mut scanner = env
532 .mito()
533 .handle_query(data_region_id, request)
534 .await
535 .unwrap();
536
537 let rows = tokio::time::timeout(
538 std::time::Duration::from_secs(30),
539 count_scanner_rows(&mut scanner, 2),
540 )
541 .await
542 .expect("two-phase series scan should not deadlock");
543 assert_eq!(7, rows);
544
545 let explain = ScannerDisplay(scanner.as_ref()).to_string();
546 assert!(explain.contains("\"mode\":\"two_phase\""), "{explain}");
547 }
548
549 #[tokio::test]
550 async fn test_series_scan_v2_flag_disables_two_phase_mode() {
551 let env = TestEnv::with_mito_config(
552 "test_disable_two_phase_series_scan",
553 MitoConfig {
554 experimental_series_scan_v2: false,
555 ..Default::default()
556 },
557 EngineConfig::default(),
558 )
559 .await;
560 env.init_metric_region().await;
561
562 let data_region_id = utils::to_data_region_id(env.default_physical_region_id());
563 let scanner = env
564 .mito()
565 .handle_query(
566 data_region_id,
567 ScanRequest {
568 distribution: Some(TimeSeriesDistribution::PerSeries),
569 ..Default::default()
570 },
571 )
572 .await
573 .unwrap();
574
575 let explain = ScannerDisplay(scanner.as_ref()).to_string();
576 assert!(explain.contains("\"mode\":\"legacy\""), "{explain}");
577 }
578}