mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-14 17:23:09 +00:00
* feat: table route for metric engine * feat: register logical regions * fix: open logical region (#96) --------- Co-authored-by: JeremyHi <jiachun_feng@proton.me>
300 lines
10 KiB
Rust
300 lines
10 KiB
Rust
// Copyright 2023 Greptime Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
mod alter;
|
|
mod close;
|
|
mod create;
|
|
mod open;
|
|
mod put;
|
|
mod read;
|
|
mod region_metadata;
|
|
mod state;
|
|
|
|
use std::any::Any;
|
|
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use common_error::ext::{BoxedError, ErrorExt};
|
|
use common_error::status_code::StatusCode;
|
|
use common_query::Output;
|
|
use common_recordbatch::SendableRecordBatchStream;
|
|
use mito2::engine::MitoEngine;
|
|
use store_api::metadata::RegionMetadataRef;
|
|
use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
|
|
use store_api::region_engine::{RegionEngine, RegionRole, SetReadonlyResponse};
|
|
use store_api::region_request::{AffectedRows, RegionRequest};
|
|
use store_api::storage::{RegionId, ScanRequest};
|
|
use tokio::sync::RwLock;
|
|
|
|
use self::state::MetricEngineState;
|
|
use crate::data_region::DataRegion;
|
|
use crate::error::Result;
|
|
use crate::metadata_region::MetadataRegion;
|
|
use crate::utils;
|
|
|
|
#[cfg_attr(doc, aquamarine::aquamarine)]
|
|
/// # Metric Engine
|
|
///
|
|
/// ## Regions
|
|
///
|
|
/// Regions in this metric engine has several roles. There is `PhysicalRegion`,
|
|
/// which refer to the region that actually stores the data. And `LogicalRegion`
|
|
/// that is "simulated" over physical regions. Each logical region is associated
|
|
/// with one physical region group, which is a group of two physical regions.
|
|
/// Their relationship is illustrated below:
|
|
///
|
|
/// ```mermaid
|
|
/// erDiagram
|
|
/// LogicalRegion ||--o{ PhysicalRegionGroup : corresponds
|
|
/// PhysicalRegionGroup ||--|| DataRegion : contains
|
|
/// PhysicalRegionGroup ||--|| MetadataRegion : contains
|
|
/// ```
|
|
///
|
|
/// Metric engine uses two region groups. One is for data region
|
|
/// ([METRIC_DATA_REGION_GROUP](crate::consts::METRIC_DATA_REGION_GROUP)), and the
|
|
/// other is for metadata region ([METRIC_METADATA_REGION_GROUP](crate::consts::METRIC_METADATA_REGION_GROUP)).
|
|
/// From the definition of [`RegionId`], we can convert between these two physical
|
|
/// region ids easily. Thus in the code base we usually refer to one "physical
|
|
/// region id", and convert it to the other one when necessary.
|
|
///
|
|
/// The logical region, in contrast, is a virtual region. It doesn't has dedicated
|
|
/// storage or region group. Only a region id that is allocated by meta server.
|
|
/// And all other things is shared with other logical region that are associated
|
|
/// with the same physical region group.
|
|
///
|
|
/// For more document about physical regions, please refer to [`MetadataRegion`]
|
|
/// and [`DataRegion`].
|
|
///
|
|
/// ## Operations
|
|
///
|
|
/// Both physical and logical region are accessible to user. But the operation
|
|
/// they support are different. List below:
|
|
///
|
|
/// | Operations | Logical Region | Physical Region |
|
|
/// | ---------- | -------------- | --------------- |
|
|
/// | Create | ✅ | ✅ |
|
|
/// | Drop | ✅ | ❌ |
|
|
/// | Write | ✅ | ❌ |
|
|
/// | Read | ✅ | ✅ |
|
|
/// | Close | ✅ | ✅ |
|
|
/// | Open | ✅ | ✅ |
|
|
/// | Alter | ✅ | ❌ |
|
|
///
|
|
/// ## Internal Columns
|
|
///
|
|
/// The physical data region contains two internal columns. Should
|
|
/// mention that "internal" here is for metric engine itself. Mito
|
|
/// engine will add it's internal columns to the region as well.
|
|
///
|
|
/// Their column id is registered in [`ReservedColumnId`]. And column name is
|
|
/// defined in [`DATA_SCHEMA_TSID_COLUMN_NAME`] and [`DATA_SCHEMA_TABLE_ID_COLUMN_NAME`].
|
|
///
|
|
/// Tsid is generated by hashing all tags. And table id is retrieved from logical region
|
|
/// id to distinguish data from different logical tables.
|
|
#[derive(Clone)]
|
|
pub struct MetricEngine {
|
|
inner: Arc<MetricEngineInner>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl RegionEngine for MetricEngine {
|
|
/// Name of this engine
|
|
fn name(&self) -> &str {
|
|
METRIC_ENGINE_NAME
|
|
}
|
|
|
|
/// Handles non-query request to the region. Returns the count of affected rows.
|
|
async fn handle_request(
|
|
&self,
|
|
region_id: RegionId,
|
|
request: RegionRequest,
|
|
) -> Result<AffectedRows, BoxedError> {
|
|
let result = match request {
|
|
RegionRequest::Put(put) => self.inner.put_region(region_id, put).await,
|
|
RegionRequest::Delete(_) => todo!(),
|
|
RegionRequest::Create(create) => self.inner.create_region(region_id, create).await,
|
|
RegionRequest::Drop(_) => todo!(),
|
|
RegionRequest::Open(open) => self.inner.open_region(region_id, open).await,
|
|
RegionRequest::Close(close) => self.inner.close_region(region_id, close).await,
|
|
RegionRequest::Alter(alter) => self.inner.alter_region(region_id, alter).await,
|
|
RegionRequest::Flush(_) => todo!(),
|
|
RegionRequest::Compact(_) => todo!(),
|
|
RegionRequest::Truncate(_) => todo!(),
|
|
/// It always Ok(0), all data is latest.
|
|
RegionRequest::Catchup(_) => Ok(0),
|
|
};
|
|
|
|
result.map_err(BoxedError::new)
|
|
}
|
|
|
|
/// Handles substrait query and return a stream of record batches
|
|
async fn handle_query(
|
|
&self,
|
|
region_id: RegionId,
|
|
request: ScanRequest,
|
|
) -> Result<SendableRecordBatchStream, BoxedError> {
|
|
self.inner
|
|
.read_region(region_id, request)
|
|
.await
|
|
.map_err(BoxedError::new)
|
|
}
|
|
|
|
/// Retrieves region's metadata.
|
|
async fn get_metadata(&self, region_id: RegionId) -> Result<RegionMetadataRef, BoxedError> {
|
|
self.inner
|
|
.load_region_metadata(region_id)
|
|
.await
|
|
.map_err(BoxedError::new)
|
|
}
|
|
|
|
/// Retrieves region's disk usage.
|
|
async fn region_disk_usage(&self, region_id: RegionId) -> Option<i64> {
|
|
todo!()
|
|
}
|
|
|
|
/// Stops the engine
|
|
async fn stop(&self) -> Result<(), BoxedError> {
|
|
// don't need to stop the underlying mito engine
|
|
Ok(())
|
|
}
|
|
|
|
fn set_writable(&self, region_id: RegionId, writable: bool) -> Result<(), BoxedError> {
|
|
// ignore the region not found error
|
|
for x in [
|
|
utils::to_metadata_region_id(region_id),
|
|
utils::to_data_region_id(region_id),
|
|
region_id,
|
|
] {
|
|
if let Err(e) = self.inner.mito.set_writable(x, writable)
|
|
&& e.status_code() != StatusCode::RegionNotFound
|
|
{
|
|
return Err(e);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn set_readonly_gracefully(
|
|
&self,
|
|
region_id: RegionId,
|
|
) -> std::result::Result<SetReadonlyResponse, BoxedError> {
|
|
self.inner.mito.set_readonly_gracefully(region_id).await
|
|
}
|
|
|
|
fn role(&self, region_id: RegionId) -> Option<RegionRole> {
|
|
todo!()
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl MetricEngine {
|
|
pub fn new(mito: MitoEngine) -> Self {
|
|
let metadata_region = MetadataRegion::new(mito.clone());
|
|
let data_region = DataRegion::new(mito.clone());
|
|
Self {
|
|
inner: Arc::new(MetricEngineInner {
|
|
mito,
|
|
metadata_region,
|
|
data_region,
|
|
state: RwLock::default(),
|
|
}),
|
|
}
|
|
}
|
|
|
|
pub async fn logical_regions(&self, physical_region_id: RegionId) -> Result<Vec<RegionId>> {
|
|
self.inner
|
|
.metadata_region
|
|
.logical_regions(physical_region_id)
|
|
.await
|
|
}
|
|
}
|
|
|
|
struct MetricEngineInner {
|
|
mito: MitoEngine,
|
|
metadata_region: MetadataRegion,
|
|
data_region: DataRegion,
|
|
state: RwLock<MetricEngineState>,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::collections::HashMap;
|
|
|
|
use store_api::metric_engine_consts::PHYSICAL_TABLE_METADATA_KEY;
|
|
use store_api::region_request::{RegionCloseRequest, RegionOpenRequest};
|
|
|
|
use super::*;
|
|
use crate::test_util::TestEnv;
|
|
|
|
#[tokio::test]
|
|
async fn close_open_regions() {
|
|
let env = TestEnv::new().await;
|
|
env.init_metric_region().await;
|
|
let engine = env.metric();
|
|
|
|
// close physical region
|
|
let physical_region_id = env.default_physical_region_id();
|
|
engine
|
|
.handle_request(
|
|
physical_region_id,
|
|
RegionRequest::Close(RegionCloseRequest {}),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// reopen physical region
|
|
let physical_region_option = [(PHYSICAL_TABLE_METADATA_KEY.to_string(), String::new())]
|
|
.into_iter()
|
|
.collect();
|
|
let open_request = RegionOpenRequest {
|
|
engine: METRIC_ENGINE_NAME.to_string(),
|
|
region_dir: env.default_region_dir(),
|
|
options: physical_region_option,
|
|
skip_wal_replay: false,
|
|
};
|
|
engine
|
|
.handle_request(physical_region_id, RegionRequest::Open(open_request))
|
|
.await
|
|
.unwrap();
|
|
|
|
// close nonexistent region
|
|
let nonexistent_region_id = RegionId::new(12313, 12);
|
|
engine
|
|
.handle_request(
|
|
nonexistent_region_id,
|
|
RegionRequest::Close(RegionCloseRequest {}),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
// open nonexistent region won't report error
|
|
let invalid_open_request = RegionOpenRequest {
|
|
engine: METRIC_ENGINE_NAME.to_string(),
|
|
region_dir: env.default_region_dir(),
|
|
options: HashMap::new(),
|
|
skip_wal_replay: false,
|
|
};
|
|
engine
|
|
.handle_request(
|
|
nonexistent_region_id,
|
|
RegionRequest::Open(invalid_open_request),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
}
|