Skip to main content

metric_engine/
engine.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
15mod alter;
16mod bulk_insert;
17mod catchup;
18mod close;
19mod create;
20mod drop;
21mod flush;
22mod open;
23mod options;
24mod put;
25mod read;
26mod region_metadata;
27mod staging;
28mod state;
29mod sync;
30
31use std::any::Any;
32use std::collections::HashMap;
33use std::sync::{Arc, RwLock};
34
35use api::region::RegionResponse;
36use async_trait::async_trait;
37use common_error::ext::{BoxedError, ErrorExt};
38use common_error::status_code::StatusCode;
39use common_runtime::RepeatedTask;
40use mito2::engine::MitoEngine;
41pub(crate) use options::IndexOptions;
42use snafu::{OptionExt, ResultExt};
43pub(crate) use state::MetricEngineState;
44use store_api::metadata::RegionMetadataRef;
45use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
46use store_api::region_engine::{
47    BatchResponses, RegionEngine, RegionRole, RegionScannerRef, RegionStatistic,
48    RemapManifestsRequest, RemapManifestsResponse, SetRegionRoleStateResponse,
49    SetRegionRoleStateSuccess, SettableRegionRoleState, SyncRegionFromRequest,
50    SyncRegionFromResponse,
51};
52use store_api::region_request::{
53    AffectedRows, BatchRegionDdlRequest, RegionCatchupRequest, RegionOpenRequest, RegionPutRequest,
54    RegionRequest, RegionTruncateRequest,
55};
56use store_api::storage::{RegionId, ScanRequest, SequenceNumber};
57
58use crate::config::EngineConfig;
59use crate::data_region::DataRegion;
60use crate::error::{
61    self, Error, Result, StartRepeatedTaskSnafu, UnsupportedRegionRequestSnafu,
62    UnsupportedRemapManifestsRequestSnafu,
63};
64use crate::metadata_region::MetadataRegion;
65use crate::repeated_task::FlushMetadataRegionTask;
66use crate::row_modifier::RowModifier;
67use crate::utils::{self, get_region_statistic};
68
69#[cfg_attr(doc, aquamarine::aquamarine)]
70/// # Metric Engine
71///
72/// ## Regions
73///
74/// Regions in this metric engine has several roles. There is `PhysicalRegion`,
75/// which refer to the region that actually stores the data. And `LogicalRegion`
76/// that is "simulated" over physical regions. Each logical region is associated
77/// with one physical region group, which is a group of two physical regions.
78/// Their relationship is illustrated below:
79///
80/// ```mermaid
81/// erDiagram
82///     LogicalRegion ||--o{ PhysicalRegionGroup : corresponds
83///     PhysicalRegionGroup ||--|| DataRegion : contains
84///     PhysicalRegionGroup ||--|| MetadataRegion : contains
85/// ```
86///
87/// Metric engine uses two region groups. One is for data region
88/// ([METRIC_DATA_REGION_GROUP](crate::consts::METRIC_DATA_REGION_GROUP)), and the
89/// other is for metadata region ([METRIC_METADATA_REGION_GROUP](crate::consts::METRIC_METADATA_REGION_GROUP)).
90/// From the definition of [`RegionId`], we can convert between these two physical
91/// region ids easily. Thus in the code base we usually refer to one "physical
92/// region id", and convert it to the other one when necessary.
93///
94/// The logical region, in contrast, is a virtual region. It doesn't has dedicated
95/// storage or region group. Only a region id that is allocated by meta server.
96/// And all other things is shared with other logical region that are associated
97/// with the same physical region group.
98///
99/// For more document about physical regions, please refer to [`MetadataRegion`]
100/// and [`DataRegion`].
101///
102/// ## Operations
103///
104/// Both physical and logical region are accessible to user. But the operation
105/// they support are different. List below:
106///
107/// | Operations | Logical Region | Physical Region |
108/// | ---------- | -------------- | --------------- |
109/// |   Create   |       ✅        |        ✅        |
110/// |    Drop    |       ✅        |        ❓*       |
111/// |   Write    |       ✅        |        ❌        |
112/// |    Read    |       ✅        |        ✅        |
113/// |   Close    |       ✅        |        ✅        |
114/// |    Open    |       ✅        |        ✅        |
115/// |   Alter    |       ✅        |        ❓*       |
116///
117/// *: Physical region can be dropped only when all related logical regions are dropped.
118/// *: Alter: Physical regions only support altering region options.
119///
120/// ## Internal Columns
121///
122/// The physical data region contains two internal columns. Should
123/// mention that "internal" here is for metric engine itself. Mito
124/// engine will add it's internal columns to the region as well.
125///
126/// Their column id is registered in [`ReservedColumnId`]. And column name is
127/// defined in [`DATA_SCHEMA_TSID_COLUMN_NAME`] and [`DATA_SCHEMA_TABLE_ID_COLUMN_NAME`].
128///
129/// Tsid is generated by hashing all tags. And table id is retrieved from logical region
130/// id to distinguish data from different logical tables.
131#[derive(Clone)]
132pub struct MetricEngine {
133    inner: Arc<MetricEngineInner>,
134}
135
136#[async_trait]
137impl RegionEngine for MetricEngine {
138    /// Name of this engine
139    fn name(&self) -> &str {
140        METRIC_ENGINE_NAME
141    }
142
143    async fn handle_batch_open_requests(
144        &self,
145        parallelism: usize,
146        requests: Vec<(RegionId, RegionOpenRequest)>,
147    ) -> Result<BatchResponses, BoxedError> {
148        self.inner
149            .handle_batch_open_requests(parallelism, requests)
150            .await
151            .map_err(BoxedError::new)
152    }
153
154    async fn handle_batch_catchup_requests(
155        &self,
156        parallelism: usize,
157        requests: Vec<(RegionId, RegionCatchupRequest)>,
158    ) -> Result<BatchResponses, BoxedError> {
159        self.inner
160            .handle_batch_catchup_requests(parallelism, requests)
161            .await
162            .map_err(BoxedError::new)
163    }
164
165    async fn handle_batch_ddl_requests(
166        &self,
167        batch_request: BatchRegionDdlRequest,
168    ) -> Result<RegionResponse, BoxedError> {
169        match batch_request {
170            BatchRegionDdlRequest::Create(requests) => {
171                let mut extension_return_value = HashMap::new();
172                let rows = self
173                    .inner
174                    .create_regions(requests, &mut extension_return_value)
175                    .await
176                    .map_err(BoxedError::new)?;
177
178                Ok(RegionResponse {
179                    affected_rows: rows,
180                    extensions: extension_return_value,
181                    metadata: Vec::new(),
182                })
183            }
184            BatchRegionDdlRequest::Alter(requests) => {
185                let mut extension_return_value = HashMap::new();
186                let rows = self
187                    .inner
188                    .alter_regions(requests, &mut extension_return_value)
189                    .await
190                    .map_err(BoxedError::new)?;
191
192                Ok(RegionResponse {
193                    affected_rows: rows,
194                    extensions: extension_return_value,
195                    metadata: Vec::new(),
196                })
197            }
198            BatchRegionDdlRequest::Drop(requests) => {
199                self.handle_requests(
200                    requests
201                        .into_iter()
202                        .map(|(region_id, req)| (region_id, RegionRequest::Drop(req))),
203                )
204                .await
205            }
206        }
207    }
208
209    /// Handles non-query request to the region. Returns the count of affected rows.
210    async fn handle_request(
211        &self,
212        region_id: RegionId,
213        request: RegionRequest,
214    ) -> Result<RegionResponse, BoxedError> {
215        let mut extension_return_value = HashMap::new();
216
217        let result = match request {
218            RegionRequest::EnterStaging(_) => {
219                if self.inner.is_physical_region(region_id) {
220                    self.handle_enter_staging_request(region_id, request).await
221                } else {
222                    UnsupportedRegionRequestSnafu { request }.fail()
223                }
224            }
225            RegionRequest::ApplyStagingManifest(_) => {
226                if self.inner.is_physical_region(region_id) {
227                    return self.inner.mito.handle_request(region_id, request).await;
228                } else {
229                    UnsupportedRegionRequestSnafu { request }.fail()
230                }
231            }
232            RegionRequest::Put(put) => self.inner.put_region(region_id, put).await,
233            RegionRequest::Create(create) => {
234                self.inner
235                    .create_regions(vec![(region_id, create)], &mut extension_return_value)
236                    .await
237            }
238            RegionRequest::Drop(drop) => self.inner.drop_region(region_id, drop).await,
239            RegionRequest::Open(open) => self.inner.open_region(region_id, open).await,
240            RegionRequest::CleanUp(clean_up) => {
241                self.inner.clean_up_region(region_id, clean_up).await
242            }
243            RegionRequest::Close(close) => self.inner.close_region(region_id, close).await,
244            RegionRequest::Alter(alter) => {
245                self.inner
246                    .alter_regions(vec![(region_id, alter)], &mut extension_return_value)
247                    .await
248            }
249            RegionRequest::Compact(_) => {
250                if self.inner.is_physical_region(region_id) {
251                    self.inner
252                        .mito
253                        .handle_request(region_id, request)
254                        .await
255                        .context(error::MitoFlushOperationSnafu)
256                        .map(|response| response.affected_rows)
257                } else {
258                    UnsupportedRegionRequestSnafu { request }.fail()
259                }
260            }
261            RegionRequest::Flush(req) => self.inner.flush_region(region_id, req).await,
262            RegionRequest::BuildIndex(_) => {
263                if self.inner.is_physical_region(region_id) {
264                    self.inner
265                        .mito
266                        .handle_request(region_id, request)
267                        .await
268                        .context(error::MitoFlushOperationSnafu)
269                        .map(|response| response.affected_rows)
270                } else {
271                    UnsupportedRegionRequestSnafu { request }.fail()
272                }
273            }
274            RegionRequest::Truncate(RegionTruncateRequest::Unflushed) => {
275                if self.inner.is_physical_region(region_id) {
276                    self.inner
277                        .mito
278                        .handle_request(
279                            utils::to_data_region_id(region_id),
280                            RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
281                        )
282                        .await
283                        .context(error::MitoTruncateOperationSnafu)
284                        .map(|response| response.affected_rows)
285                } else {
286                    UnsupportedRegionRequestSnafu {
287                        request: RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
288                    }
289                    .fail()
290                }
291            }
292            RegionRequest::Truncate(request) => UnsupportedRegionRequestSnafu {
293                request: RegionRequest::Truncate(request),
294            }
295            .fail(),
296            RegionRequest::Delete(delete) => self.inner.delete_region(region_id, delete).await,
297            RegionRequest::Catchup(_) => {
298                let mut response = self
299                    .inner
300                    .handle_batch_catchup_requests(
301                        1,
302                        vec![(region_id, RegionCatchupRequest::default())],
303                    )
304                    .await
305                    .map_err(BoxedError::new)?;
306                debug_assert_eq!(response.len(), 1);
307                let (resp_region_id, response) = response
308                    .pop()
309                    .context(error::UnexpectedRequestSnafu {
310                        reason: "expected 1 response, but got zero responses",
311                    })
312                    .map_err(BoxedError::new)?;
313                debug_assert_eq!(region_id, resp_region_id);
314                return response;
315            }
316            RegionRequest::BulkInserts(bulk) => {
317                self.inner.bulk_insert_region(region_id, bulk).await
318            }
319        };
320
321        result.map_err(BoxedError::new).map(|rows| RegionResponse {
322            affected_rows: rows,
323            extensions: extension_return_value,
324            metadata: Vec::new(),
325        })
326    }
327
328    async fn handle_query(
329        &self,
330        region_id: RegionId,
331        request: ScanRequest,
332    ) -> Result<RegionScannerRef, BoxedError> {
333        self.handle_query(region_id, request).await
334    }
335
336    async fn get_committed_sequence(
337        &self,
338        region_id: RegionId,
339    ) -> Result<SequenceNumber, BoxedError> {
340        self.inner
341            .get_last_seq_num(region_id)
342            .await
343            .map_err(BoxedError::new)
344    }
345
346    /// Retrieves region's metadata.
347    async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
348        self.inner
349            .load_region_metadata(region_id)
350            .await
351            .map_err(BoxedError::new)
352    }
353
354    /// Retrieves region's disk usage.
355    ///
356    /// Note: Returns `None` if it's a logical region.
357    fn region_statistic(&self, region_id: RegionId) -> Option<RegionStatistic> {
358        if self.inner.is_physical_region(region_id) {
359            get_region_statistic(&self.inner.mito, region_id)
360        } else {
361            None
362        }
363    }
364
365    /// Stops the engine
366    async fn stop(&self) -> Result<(), BoxedError> {
367        // don't need to stop the underlying mito engine
368        Ok(())
369    }
370
371    fn set_region_role(&self, region_id: RegionId, role: RegionRole) -> Result<(), BoxedError> {
372        // ignore the region not found error
373        for x in [
374            utils::to_metadata_region_id(region_id),
375            utils::to_data_region_id(region_id),
376        ] {
377            if let Err(e) = self.inner.mito.set_region_role(x, role)
378                && e.status_code() != StatusCode::RegionNotFound
379            {
380                return Err(e);
381            }
382        }
383        Ok(())
384    }
385
386    async fn sync_region(
387        &self,
388        region_id: RegionId,
389        request: SyncRegionFromRequest,
390    ) -> Result<SyncRegionFromResponse, BoxedError> {
391        match request {
392            SyncRegionFromRequest::FromManifest(manifest_info) => self
393                .inner
394                .sync_region_from_manifest(region_id, manifest_info)
395                .await
396                .map_err(BoxedError::new),
397            SyncRegionFromRequest::FromRegion {
398                source_region_id,
399                parallelism,
400            } => {
401                if self.inner.is_physical_region(region_id) {
402                    self.inner
403                        .sync_region_from_region(region_id, source_region_id, parallelism)
404                        .await
405                        .map_err(BoxedError::new)
406                } else {
407                    Err(BoxedError::new(
408                        error::UnsupportedSyncRegionFromRequestSnafu { region_id }.build(),
409                    ))
410                }
411            }
412        }
413    }
414
415    async fn remap_manifests(
416        &self,
417        request: RemapManifestsRequest,
418    ) -> Result<RemapManifestsResponse, BoxedError> {
419        let region_id = request.region_id;
420        if self.inner.is_physical_region(region_id) {
421            self.inner.mito.remap_manifests(request).await
422        } else {
423            Err(BoxedError::new(
424                UnsupportedRemapManifestsRequestSnafu { region_id }.build(),
425            ))
426        }
427    }
428
429    async fn set_region_role_state_gracefully(
430        &self,
431        region_id: RegionId,
432        region_role_state: SettableRegionRoleState,
433    ) -> std::result::Result<SetRegionRoleStateResponse, BoxedError> {
434        let metadata_result = match self
435            .inner
436            .mito
437            .set_region_role_state_gracefully(
438                utils::to_metadata_region_id(region_id),
439                region_role_state,
440            )
441            .await?
442        {
443            SetRegionRoleStateResponse::Success(success) => success,
444            SetRegionRoleStateResponse::NotFound => {
445                return Ok(SetRegionRoleStateResponse::NotFound);
446            }
447            SetRegionRoleStateResponse::InvalidTransition(error) => {
448                return Ok(SetRegionRoleStateResponse::InvalidTransition(error));
449            }
450        };
451
452        let data_result = match self
453            .inner
454            .mito
455            .set_region_role_state_gracefully(region_id, region_role_state)
456            .await?
457        {
458            SetRegionRoleStateResponse::Success(success) => success,
459            SetRegionRoleStateResponse::NotFound => {
460                return Ok(SetRegionRoleStateResponse::NotFound);
461            }
462            SetRegionRoleStateResponse::InvalidTransition(error) => {
463                return Ok(SetRegionRoleStateResponse::InvalidTransition(error));
464            }
465        };
466
467        Ok(SetRegionRoleStateResponse::success(
468            SetRegionRoleStateSuccess::metric(
469                data_result.last_entry_id().unwrap_or_default(),
470                metadata_result.last_entry_id().unwrap_or_default(),
471            ),
472        ))
473    }
474
475    /// Returns the physical region role.
476    ///
477    /// Note: Returns `None` if it's a logical region.
478    fn role(&self, region_id: RegionId) -> Option<RegionRole> {
479        if self.inner.is_physical_region(region_id) {
480            self.inner.mito.role(region_id)
481        } else {
482            None
483        }
484    }
485
486    fn as_any(&self) -> &dyn Any {
487        self
488    }
489}
490
491/// Outcome of a group of input requests.
492///
493/// Requests rejected before physical-region grouping produce singleton failures.
494#[derive(Debug)]
495pub struct PutGroupResult {
496    /// Positions in the input batch, including separate entries for the same region.
497    pub request_indices: Vec<usize>,
498    /// Aggregate rows on success; no other physical group's outcome is implied.
499    pub result: Result<AffectedRows>,
500}
501
502impl MetricEngine {
503    pub fn try_new(mito: MitoEngine, mut config: EngineConfig) -> Result<Self> {
504        let metadata_region = MetadataRegion::new(mito.clone());
505        let data_region = DataRegion::new(mito.clone());
506        let state = Arc::new(RwLock::default());
507        config.sanitize();
508        let flush_interval = config.flush_metadata_region_interval;
509        let inner = Arc::new(MetricEngineInner {
510            mito: mito.clone(),
511            metadata_region,
512            data_region,
513            state: state.clone(),
514            row_modifier: RowModifier::default(),
515            flush_task: RepeatedTask::new(
516                flush_interval,
517                Box::new(FlushMetadataRegionTask {
518                    state: state.clone(),
519                    mito: mito.clone(),
520                }),
521            ),
522        });
523        inner
524            .flush_task
525            .start(common_runtime::global_runtime())
526            .context(StartRepeatedTaskSnafu { name: "flush_task" })?;
527        Ok(Self { inner })
528    }
529
530    pub fn mito(&self) -> MitoEngine {
531        self.inner.mito.clone()
532    }
533
534    /// Writes all physical-region groups and retains each group's outcome.
535    pub async fn put_regions_batch(
536        &self,
537        requests: impl ExactSizeIterator<Item = (RegionId, RegionPutRequest)>,
538    ) -> Vec<PutGroupResult> {
539        self.inner.put_regions_batch(requests).await
540    }
541
542    /// Returns all logical regions associated with the physical region.
543    pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result<Vec<RegionId>> {
544        self.inner
545            .metadata_region
546            .logical_regions(physical_region_id)
547            .await
548    }
549
550    /// Handles substrait query and return a stream of record batches
551    async fn handle_query(
552        &self,
553        region_id: RegionId,
554        request: ScanRequest,
555    ) -> Result<RegionScannerRef, BoxedError> {
556        self.inner
557            .read_region(region_id, request)
558            .await
559            .map_err(BoxedError::new)
560    }
561
562    async fn handle_requests(
563        &self,
564        requests: impl IntoIterator<Item = (RegionId, RegionRequest)>,
565    ) -> Result<RegionResponse, BoxedError> {
566        let mut affected_rows = 0;
567        let mut extensions = HashMap::new();
568        for (region_id, request) in requests {
569            let response = self.handle_request(region_id, request).await?;
570            affected_rows += response.affected_rows;
571            extensions.extend(response.extensions);
572        }
573
574        Ok(RegionResponse {
575            affected_rows,
576            extensions,
577            metadata: Vec::new(),
578        })
579    }
580}
581
582/// Shared definitions must agree before CREATE/ALTER mutates either region.
583fn validate_shared_column(
584    region_id: store_api::storage::RegionId,
585    existing: &store_api::metadata::ColumnMetadata,
586    proposed: &store_api::metadata::ColumnMetadata,
587) -> crate::error::Result<()> {
588    snafu::ensure!(
589        existing.column_schema.data_type == proposed.column_schema.data_type
590            && existing.semantic_type == proposed.semantic_type,
591        crate::error::InvalidRequestSnafu {
592            region_id,
593            reason: format!(
594                "incompatible shared physical column `{}`",
595                proposed.column_schema.name
596            ),
597        }
598    );
599    Ok(())
600}
601
602#[cfg(test)]
603impl MetricEngine {
604    pub async fn scan_to_stream(
605        &self,
606        region_id: RegionId,
607        request: ScanRequest,
608    ) -> Result<common_recordbatch::SendableRecordBatchStream, BoxedError> {
609        self.inner.scan_to_stream(region_id, request).await
610    }
611}
612
613struct MetricEngineInner {
614    mito: MitoEngine,
615    metadata_region: MetadataRegion,
616    data_region: DataRegion,
617    state: Arc<RwLock<MetricEngineState>>,
618    row_modifier: RowModifier,
619    flush_task: RepeatedTask<Error>,
620}
621
622#[cfg(test)]
623mod test {
624    use std::assert_matches;
625    use std::collections::HashMap;
626
627    use api::v1::Rows;
628    use common_recordbatch::RecordBatches;
629    use common_telemetry::info;
630    use common_wal::options::{KafkaWalOptions, WalOptions};
631    use mito2::sst::location::region_dir_from_table_dir;
632    use mito2::test_util::{kafka_log_store_factory, prepare_test_for_kafka_log_store};
633    use store_api::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
634    use store_api::mito_engine_options::WAL_OPTIONS_KEY;
635    use store_api::region_request::{
636        PathType, RegionCleanUpRequest, RegionCloseRequest, RegionDropRequest, RegionFlushRequest,
637        RegionOpenRequest, RegionPutRequest, RegionRequest, RegionTruncateRequest,
638    };
639
640    use super::*;
641    use crate::maybe_skip_kafka_log_store_integration_test;
642    use crate::test_util::{
643        TestEnv, build_rows, create_logical_region_request, row_schema_with_tags,
644    };
645
646    #[tokio::test]
647    async fn test_build_series_index_forwarding() {
648        use api::v1::region::build_index_request;
649        use store_api::region_request::RegionBuildIndexRequest;
650
651        let env = TestEnv::new().await;
652        env.init_metric_region().await;
653        let request = || {
654            RegionRequest::BuildIndex(RegionBuildIndexRequest {
655                options: Some(build_index_request::Options::SeriesIndex(Default::default())),
656            })
657        };
658        // Default Mito config disables series indexes: reaching that error proves forwarding.
659        let error = env
660            .metric()
661            .handle_request(env.default_physical_region_id(), request())
662            .await
663            .unwrap_err();
664        let error = format!("{error:?}");
665        assert!(error.contains("series index is disabled"), "{error}");
666        let error = env
667            .metric()
668            .handle_request(env.default_logical_region_id(), request())
669            .await
670            .unwrap_err();
671        assert_eq!(
672            common_error::status_code::StatusCode::Unsupported,
673            error.status_code()
674        );
675    }
676
677    #[tokio::test]
678    async fn test_discard_unflushed_data_only() {
679        let env = TestEnv::new().await;
680        env.init_metric_region().await;
681
682        let engine = env.metric();
683        let mito = env.mito();
684        let physical_region_id = env.default_physical_region_id();
685        let logical_region_id = env.default_logical_region_id();
686        let data_region_id = utils::to_data_region_id(physical_region_id);
687        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
688
689        let metadata_memtable_size = mito
690            .region_statistic(metadata_region_id)
691            .unwrap()
692            .memtable_size;
693        assert!(metadata_memtable_size > 0);
694
695        engine
696            .handle_request(
697                logical_region_id,
698                RegionRequest::Put(RegionPutRequest {
699                    skip_wal: false,
700                    rows: Rows {
701                        schema: row_schema_with_tags(&["job"]),
702                        rows: build_rows(1, 5),
703                    },
704                    hint: None,
705                    partition_expr_version: None,
706                }),
707            )
708            .await
709            .unwrap();
710        assert!(mito.region_statistic(data_region_id).unwrap().memtable_size > 0);
711
712        engine
713            .handle_request(
714                physical_region_id,
715                RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
716            )
717            .await
718            .unwrap();
719
720        assert_eq!(
721            0,
722            mito.region_statistic(data_region_id).unwrap().memtable_size
723        );
724        assert_eq!(
725            metadata_memtable_size,
726            mito.region_statistic(metadata_region_id)
727                .unwrap()
728                .memtable_size
729        );
730
731        let stream = engine
732            .scan_to_stream(logical_region_id, ScanRequest::default())
733            .await
734            .unwrap();
735        let batches = RecordBatches::try_collect(stream).await.unwrap();
736        assert_eq!(
737            0,
738            batches.iter().map(|batch| batch.num_rows()).sum::<usize>()
739        );
740
741        // This maintenance operation applies to a physical region group only.
742        engine
743            .handle_request(
744                logical_region_id,
745                RegionRequest::Truncate(RegionTruncateRequest::Unflushed),
746            )
747            .await
748            .unwrap_err();
749    }
750
751    #[tokio::test]
752    async fn close_open_regions() {
753        let env = TestEnv::new().await;
754        env.init_metric_region().await;
755        let engine = env.metric();
756
757        // close physical region
758        let physical_region_id = env.default_physical_region_id();
759        engine
760            .handle_request(
761                physical_region_id,
762                RegionRequest::Close(RegionCloseRequest::default()),
763            )
764            .await
765            .unwrap();
766
767        // reopen physical region
768        let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
769            .into_iter()
770            .collect();
771        let open_request = RegionOpenRequest {
772            engine: METRIC_ENGINE_NAME.to_string(),
773            table_dir: TestEnv::default_table_dir(),
774            path_type: PathType::Bare, // Use Bare path type for engine regions
775            options: physical_region_option,
776            skip_wal_replay: false,
777            checkpoint: None,
778            requirements: Default::default(),
779        };
780        engine
781            .handle_request(physical_region_id, RegionRequest::Open(open_request))
782            .await
783            .unwrap();
784
785        // close nonexistent region won't report error
786        let nonexistent_region_id = RegionId::new(12313, 12);
787        engine
788            .handle_request(
789                nonexistent_region_id,
790                RegionRequest::Close(RegionCloseRequest::default()),
791            )
792            .await
793            .unwrap();
794
795        // open nonexistent region won't report error
796        let invalid_open_request = RegionOpenRequest {
797            engine: METRIC_ENGINE_NAME.to_string(),
798            table_dir: TestEnv::default_table_dir(),
799            path_type: PathType::Bare, // Use Bare path type for engine regions
800            options: HashMap::new(),
801            skip_wal_replay: false,
802            checkpoint: None,
803            requirements: Default::default(),
804        };
805        engine
806            .handle_request(
807                nonexistent_region_id,
808                RegionRequest::Open(invalid_open_request),
809            )
810            .await
811            .unwrap();
812    }
813
814    #[tokio::test]
815    async fn test_offline_cleanup_physical_region() {
816        let env = TestEnv::new().await;
817        env.init_metric_region().await;
818        let engine = env.metric();
819        let mito = env.mito();
820        let physical_region_id = env.default_physical_region_id();
821        let metadata_region_id = crate::utils::to_metadata_region_id(physical_region_id);
822        let data_region_id = crate::utils::to_data_region_id(physical_region_id);
823
824        engine
825            .handle_request(
826                physical_region_id,
827                RegionRequest::Close(RegionCloseRequest::default()),
828            )
829            .await
830            .unwrap();
831
832        let object_store = env.get_object_store().unwrap();
833        let metadata_region_dir = region_dir_from_table_dir(
834            &TestEnv::default_table_dir(),
835            metadata_region_id,
836            PathType::Metadata,
837        );
838        let data_region_dir = region_dir_from_table_dir(
839            &TestEnv::default_table_dir(),
840            data_region_id,
841            PathType::Data,
842        );
843        assert!(object_store.exists(&metadata_region_dir).await.unwrap());
844        assert!(object_store.exists(&data_region_dir).await.unwrap());
845
846        let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
847            .into_iter()
848            .collect();
849        let clean_up_request = RegionCleanUpRequest {
850            engine: METRIC_ENGINE_NAME.to_string(),
851            table_dir: TestEnv::default_table_dir(),
852            path_type: PathType::Bare,
853            options: physical_region_option,
854        };
855        engine
856            .handle_request(physical_region_id, RegionRequest::CleanUp(clean_up_request))
857            .await
858            .unwrap();
859
860        assert!(!mito.is_region_exists(metadata_region_id));
861        assert!(!mito.is_region_exists(data_region_id));
862        assert!(!object_store.exists(&metadata_region_dir).await.unwrap());
863        assert!(!object_store.exists(&data_region_dir).await.unwrap());
864    }
865
866    #[tokio::test]
867    async fn test_role() {
868        let env = TestEnv::new().await;
869        env.init_metric_region().await;
870
871        let logical_region_id = env.default_logical_region_id();
872        let physical_region_id = env.default_physical_region_id();
873
874        assert!(env.metric().role(logical_region_id).is_none());
875        assert!(env.metric().role(physical_region_id).is_some());
876    }
877
878    #[tokio::test]
879    async fn test_region_disk_usage() {
880        let env = TestEnv::new().await;
881        env.init_metric_region().await;
882
883        let logical_region_id = env.default_logical_region_id();
884        let physical_region_id = env.default_physical_region_id();
885
886        assert!(env.metric().region_statistic(logical_region_id).is_none());
887        assert!(env.metric().region_statistic(physical_region_id).is_some());
888    }
889
890    #[tokio::test]
891    async fn test_open_region_failure() {
892        let env = TestEnv::new().await;
893        env.init_metric_region().await;
894        let physical_region_id = env.default_physical_region_id();
895
896        let metric_engine = env.metric();
897        metric_engine
898            .handle_request(
899                physical_region_id,
900                RegionRequest::Flush(RegionFlushRequest::default()),
901            )
902            .await
903            .unwrap();
904
905        let path = region_dir_from_table_dir(
906            &TestEnv::default_table_dir(),
907            physical_region_id,
908            PathType::Metadata,
909        );
910        let object_store = env.get_object_store().unwrap();
911        let list = object_store.list(&path).await.unwrap();
912        // Delete parquet files in metadata region
913        for entry in list {
914            if entry.metadata().is_dir() {
915                continue;
916            }
917            if entry.name().ends_with("parquet") {
918                info!("deleting {}", entry.path());
919                object_store.delete(entry.path()).await.unwrap();
920            }
921        }
922
923        let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
924            .into_iter()
925            .collect();
926        let open_request = RegionOpenRequest {
927            engine: METRIC_ENGINE_NAME.to_string(),
928            table_dir: TestEnv::default_table_dir(),
929            path_type: PathType::Bare,
930            options: physical_region_option,
931            skip_wal_replay: false,
932            checkpoint: None,
933            requirements: Default::default(),
934        };
935        // Opening an already opened region should succeed.
936        // Since the region is already open, no metadata recovery operations will be performed.
937        metric_engine
938            .handle_request(physical_region_id, RegionRequest::Open(open_request))
939            .await
940            .unwrap();
941
942        // Close the region
943        metric_engine
944            .handle_request(
945                physical_region_id,
946                RegionRequest::Close(RegionCloseRequest::default()),
947            )
948            .await
949            .unwrap();
950
951        // Try to reopen region.
952        let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
953            .into_iter()
954            .collect();
955        let open_request = RegionOpenRequest {
956            engine: METRIC_ENGINE_NAME.to_string(),
957            table_dir: TestEnv::default_table_dir(),
958            path_type: PathType::Bare,
959            options: physical_region_option,
960            skip_wal_replay: false,
961            checkpoint: None,
962            requirements: Default::default(),
963        };
964        let err = metric_engine
965            .handle_request(physical_region_id, RegionRequest::Open(open_request))
966            .await
967            .unwrap_err();
968        // Failed to open region because of missing parquet files.
969        assert_eq!(err.status_code(), StatusCode::StorageUnavailable);
970
971        let mito_engine = metric_engine.mito();
972        let data_region_id = utils::to_data_region_id(physical_region_id);
973        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
974        // The metadata/data region should be closed.
975        let err = mito_engine.get_metadata(data_region_id).await.unwrap_err();
976        assert_eq!(err.status_code(), StatusCode::RegionNotFound);
977        let err = mito_engine
978            .get_metadata(metadata_region_id)
979            .await
980            .unwrap_err();
981        assert_eq!(err.status_code(), StatusCode::RegionNotFound);
982    }
983
984    #[tokio::test]
985    async fn test_catchup_regions() {
986        common_telemetry::init_default_ut_logging();
987        maybe_skip_kafka_log_store_integration_test!();
988        let kafka_log_store_factory = kafka_log_store_factory().unwrap();
989        let mito_env = mito2::test_util::TestEnv::new()
990            .await
991            .with_log_store_factory(kafka_log_store_factory.clone());
992        let env = TestEnv::with_mito_env(mito_env).await;
993        let table_dir = |region_id| format!("table/{region_id}");
994        let mut physical_region_ids = vec![];
995        let mut logical_region_ids = vec![];
996
997        let num_topics = 3;
998        let num_physical_regions = 8;
999        let num_logical_regions = 16;
1000        let parallelism = 2;
1001        let mut topics = Vec::with_capacity(num_topics);
1002        for _ in 0..num_topics {
1003            let topic = prepare_test_for_kafka_log_store(&kafka_log_store_factory)
1004                .await
1005                .unwrap();
1006            topics.push(topic);
1007        }
1008
1009        let topic_idx = |id| (id as usize) % num_topics;
1010        // Creates physical regions
1011        for i in 0..num_physical_regions {
1012            let physical_region_id = RegionId::new(1, i);
1013            physical_region_ids.push(physical_region_id);
1014
1015            let wal_options = WalOptions::Kafka(KafkaWalOptions::new(topics[topic_idx(i)].clone()));
1016            env.create_physical_region(
1017                physical_region_id,
1018                &table_dir(physical_region_id),
1019                vec![(
1020                    WAL_OPTIONS_KEY.to_string(),
1021                    serde_json::to_string(&wal_options).unwrap(),
1022                )],
1023            )
1024            .await;
1025            // Creates logical regions for each physical region
1026            for j in 0..num_logical_regions {
1027                let logical_region_id = RegionId::new(1024 + i, j);
1028                logical_region_ids.push(logical_region_id);
1029                env.create_logical_region(physical_region_id, logical_region_id)
1030                    .await;
1031            }
1032        }
1033
1034        let metric_engine = env.metric();
1035        // Closes all regions
1036        for region_id in logical_region_ids.iter().chain(physical_region_ids.iter()) {
1037            metric_engine
1038                .handle_request(
1039                    *region_id,
1040                    RegionRequest::Close(RegionCloseRequest::default()),
1041                )
1042                .await
1043                .unwrap();
1044        }
1045
1046        // Opens all regions and skip the wal
1047        let requests = physical_region_ids
1048            .iter()
1049            .enumerate()
1050            .map(|(idx, region_id)| {
1051                let mut options = HashMap::new();
1052                let wal_options =
1053                    WalOptions::Kafka(KafkaWalOptions::new(topics[topic_idx(idx as u32)].clone()));
1054                options.insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new());
1055                options.insert(
1056                    WAL_OPTIONS_KEY.to_string(),
1057                    serde_json::to_string(&wal_options).unwrap(),
1058                );
1059                (
1060                    *region_id,
1061                    RegionOpenRequest {
1062                        engine: METRIC_ENGINE_NAME.to_string(),
1063                        table_dir: table_dir(*region_id),
1064                        path_type: PathType::Bare,
1065                        options: options.clone(),
1066                        skip_wal_replay: true,
1067                        checkpoint: None,
1068                        requirements: Default::default(),
1069                    },
1070                )
1071            })
1072            .collect::<Vec<_>>();
1073        info!("Open batch regions with parallelism: {parallelism}");
1074        metric_engine
1075            .handle_batch_open_requests(parallelism, requests)
1076            .await
1077            .unwrap();
1078        {
1079            let state = metric_engine.inner.state.read().unwrap();
1080            for logical_region in &logical_region_ids {
1081                assert!(!state.logical_regions().contains_key(logical_region));
1082            }
1083        }
1084
1085        let catch_requests = physical_region_ids
1086            .iter()
1087            .map(|region_id| {
1088                (
1089                    *region_id,
1090                    RegionCatchupRequest {
1091                        set_writable: true,
1092                        ..Default::default()
1093                    },
1094                )
1095            })
1096            .collect::<Vec<_>>();
1097        metric_engine
1098            .handle_batch_catchup_requests(parallelism, catch_requests)
1099            .await
1100            .unwrap();
1101        {
1102            let state = metric_engine.inner.state.read().unwrap();
1103            for logical_region in &logical_region_ids {
1104                assert!(state.logical_regions().contains_key(logical_region));
1105            }
1106        }
1107    }
1108
1109    #[tokio::test]
1110    async fn test_drop_region() {
1111        let env = TestEnv::new().await;
1112        let engine = env.metric();
1113        let physical_region_id1 = RegionId::new(1024, 0);
1114        let logical_region_id1 = RegionId::new(1025, 0);
1115        env.create_physical_region(physical_region_id1, "/test_dir1", vec![])
1116            .await;
1117        let region_create_request1 =
1118            create_logical_region_request(&["job"], physical_region_id1, "logical1");
1119        engine
1120            .handle_batch_ddl_requests(BatchRegionDdlRequest::Create(vec![(
1121                logical_region_id1,
1122                region_create_request1,
1123            )]))
1124            .await
1125            .unwrap();
1126        let err = engine
1127            .handle_request(
1128                physical_region_id1,
1129                RegionRequest::Drop(RegionDropRequest {
1130                    fast_path: false,
1131                    force: false,
1132                    partial_drop: false,
1133                }),
1134            )
1135            .await
1136            .unwrap_err();
1137        assert_matches!(
1138            err.as_any().downcast_ref::<Error>().unwrap(),
1139            &Error::PhysicalRegionBusy { .. }
1140        );
1141
1142        engine
1143            .handle_request(
1144                physical_region_id1,
1145                RegionRequest::Drop(RegionDropRequest {
1146                    fast_path: false,
1147                    force: true,
1148                    partial_drop: false,
1149                }),
1150            )
1151            .await
1152            .unwrap();
1153        assert!(
1154            engine
1155                .inner
1156                .state
1157                .read()
1158                .unwrap()
1159                .physical_region_states()
1160                .get(&physical_region_id1)
1161                .is_none()
1162        );
1163        assert!(
1164            engine
1165                .inner
1166                .state
1167                .read()
1168                .unwrap()
1169                .logical_regions()
1170                .get(&logical_region_id1)
1171                .is_none()
1172        );
1173    }
1174}