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