Skip to main content

metric_engine/engine/
open.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
15//! Open a metric region.
16
17use api::region::RegionResponse;
18use api::v1::SemanticType;
19use common_error::ext::BoxedError;
20use common_telemetry::{error, info, warn};
21use datafusion::common::HashMap;
22use mito2::engine::MITO_ENGINE_NAME;
23use snafu::{OptionExt, ResultExt};
24use store_api::region_engine::{BatchResponses, RegionEngine};
25use store_api::region_request::{AffectedRows, PathType, RegionOpenRequest, ReplayCheckpoint};
26use store_api::storage::RegionId;
27
28use crate::engine::MetricEngineInner;
29use crate::engine::create::region_options_for_metadata_region;
30use crate::engine::options::{PhysicalRegionOptions, set_data_region_options};
31use crate::error::{
32    BatchOpenMitoRegionSnafu, NoOpenRegionResultSnafu, OpenMitoRegionSnafu,
33    PhysicalRegionNotFoundSnafu, Result,
34};
35use crate::metrics::{LOGICAL_REGION_COUNT, PHYSICAL_REGION_COUNT};
36use crate::utils;
37
38impl MetricEngineInner {
39    pub async fn handle_batch_open_requests(
40        &self,
41        parallelism: usize,
42        requests: Vec<(RegionId, RegionOpenRequest)>,
43    ) -> Result<BatchResponses> {
44        // We need to open metadata region and data region for each request.
45        let mut all_requests = Vec::with_capacity(requests.len() * 2);
46        let mut physical_region_ids = HashMap::with_capacity(requests.len());
47
48        for (region_id, request) in requests {
49            if !request.is_physical_table() {
50                warn!("Skipping non-physical table open request: {region_id}");
51                continue;
52            }
53            let physical_region_options = PhysicalRegionOptions::try_from(&request.options)?;
54            let metadata_region_id = utils::to_metadata_region_id(region_id);
55            let data_region_id = utils::to_data_region_id(region_id);
56            let (open_metadata_region_request, open_data_region_request) =
57                self.transform_open_physical_region_request(request);
58            all_requests.push((metadata_region_id, open_metadata_region_request));
59            all_requests.push((data_region_id, open_data_region_request));
60            physical_region_ids.insert(region_id, physical_region_options);
61        }
62
63        let mut results = self
64            .mito
65            .handle_batch_open_requests(parallelism, all_requests)
66            .await
67            .context(BatchOpenMitoRegionSnafu {})?
68            .into_iter()
69            .collect::<HashMap<_, _>>();
70
71        let mut responses = Vec::with_capacity(physical_region_ids.len());
72        for (physical_region_id, physical_region_options) in physical_region_ids {
73            let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
74            let data_region_id = utils::to_data_region_id(physical_region_id);
75            let metadata_region_result = results.remove(&metadata_region_id);
76            let data_region_result: Option<std::result::Result<RegionResponse, BoxedError>> =
77                results.remove(&data_region_id);
78            // Pass the optional `metadata_region_result` and `data_region_result` to
79            // `recover_physical_region_with_results`. This function handles errors for each
80            // open physical region request, allowing the process to continue with the
81            // remaining regions even if some requests fail.
82            let response = self
83                .recover_physical_region_with_results(
84                    metadata_region_result,
85                    data_region_result,
86                    physical_region_id,
87                    physical_region_options,
88                    true,
89                )
90                .await
91                .map_err(BoxedError::new);
92            responses.push((physical_region_id, response));
93        }
94
95        Ok(responses)
96    }
97
98    // If the metadata region is opened with a stale manifest,
99    // the metric engine may fail to recover logical tables from the metadata region,
100    // as the manifest could reference files that have already been deleted
101    // due to compaction operations performed by the region leader.
102    async fn close_physical_region_on_recovery_failure(&self, physical_region_id: RegionId) {
103        info!(
104            "Closing metadata region {} and data region {} on metadata recovery failure",
105            utils::to_metadata_region_id(physical_region_id),
106            utils::to_data_region_id(physical_region_id)
107        );
108        if let Err(err) = self.close_physical_region(physical_region_id).await {
109            error!(err; "Failed to close physical region {}", physical_region_id);
110        }
111    }
112
113    pub(crate) async fn recover_physical_region_with_results(
114        &self,
115        metadata_region_result: Option<std::result::Result<RegionResponse, BoxedError>>,
116        data_region_result: Option<std::result::Result<RegionResponse, BoxedError>>,
117        physical_region_id: RegionId,
118        physical_region_options: PhysicalRegionOptions,
119        close_region_on_failure: bool,
120    ) -> Result<RegionResponse> {
121        let metadata_region_id = utils::to_metadata_region_id(physical_region_id);
122        let data_region_id = utils::to_data_region_id(physical_region_id);
123        let _ = metadata_region_result
124            .context(NoOpenRegionResultSnafu {
125                region_id: metadata_region_id,
126            })?
127            .context(OpenMitoRegionSnafu {
128                region_type: "metadata",
129            })?;
130
131        let data_region_response = data_region_result
132            .context(NoOpenRegionResultSnafu {
133                region_id: data_region_id,
134            })?
135            .context(OpenMitoRegionSnafu {
136                region_type: "data",
137            })?;
138
139        if let Err(err) = self
140            .recover_states(physical_region_id, physical_region_options)
141            .await
142        {
143            if close_region_on_failure {
144                self.close_physical_region_on_recovery_failure(physical_region_id)
145                    .await;
146            }
147            return Err(err);
148        }
149        Ok(data_region_response)
150    }
151
152    /// Open a metric region.
153    ///
154    /// Only open requests to a physical region matter. Those to logical regions are
155    /// actually an empty operation -- it only check if the request is valid. Since
156    /// logical regions are multiplexed over physical regions, they are always "open".
157    ///
158    /// If trying to open a logical region whose physical region is not open, metric
159    /// engine will throw a [RegionNotFound](common_error::status_code::StatusCode::RegionNotFound)
160    /// error.
161    pub async fn open_region(
162        &self,
163        region_id: RegionId,
164        request: RegionOpenRequest,
165    ) -> Result<AffectedRows> {
166        if request.is_physical_table() {
167            if self
168                .state
169                .read()
170                .unwrap()
171                .physical_region_states()
172                .get(&region_id)
173                .is_some()
174            {
175                warn!(
176                    "The physical region {} is already open, ignore the open request",
177                    region_id
178                );
179                return Ok(0);
180            }
181            // open physical region and recover states
182            let physical_region_options = PhysicalRegionOptions::try_from(&request.options)?;
183            self.open_physical_region(region_id, request).await?;
184            if let Err(err) = self
185                .recover_states(region_id, physical_region_options)
186                .await
187            {
188                self.close_physical_region_on_recovery_failure(region_id)
189                    .await;
190                return Err(err);
191            }
192
193            Ok(0)
194        } else {
195            // Don't check if the logical region exist. Because a logical region cannot be opened
196            // individually, it is always "open" if its physical region is open. But the engine
197            // can't tell if the logical region is not exist or the physical region is not opened
198            // yet. Thus simply return `Ok` here to ignore all those errors.
199            Ok(0)
200        }
201    }
202
203    /// Transform the open request to open metadata region and data region.
204    ///
205    /// Returns:
206    /// - The open request for metadata region.
207    /// - The open request for data region.
208    fn transform_open_physical_region_request(
209        &self,
210        request: RegionOpenRequest,
211    ) -> (RegionOpenRequest, RegionOpenRequest) {
212        let metadata_region_options = region_options_for_metadata_region(&request.options);
213        let checkpoint = request.checkpoint;
214
215        let open_metadata_region_request = RegionOpenRequest {
216            table_dir: request.table_dir.clone(),
217            path_type: PathType::Metadata,
218            options: metadata_region_options,
219            engine: MITO_ENGINE_NAME.to_string(),
220            skip_wal_replay: request.skip_wal_replay,
221            checkpoint: checkpoint.map(|checkpoint| ReplayCheckpoint {
222                entry_id: checkpoint.metadata_entry_id.unwrap_or_default(),
223                metadata_entry_id: None,
224            }),
225            requirements: request.requirements,
226        };
227
228        let mut data_region_options = request.options;
229        set_data_region_options(
230            &mut data_region_options,
231            self.config.sparse_primary_key_encoding,
232        );
233        let open_data_region_request = RegionOpenRequest {
234            table_dir: request.table_dir.clone(),
235            path_type: PathType::Data,
236            options: data_region_options,
237            engine: MITO_ENGINE_NAME.to_string(),
238            skip_wal_replay: request.skip_wal_replay,
239            checkpoint: checkpoint.map(|checkpoint| ReplayCheckpoint {
240                entry_id: checkpoint.entry_id,
241                metadata_entry_id: None,
242            }),
243            requirements: request.requirements,
244        };
245
246        (open_metadata_region_request, open_data_region_request)
247    }
248
249    /// Invokes mito engine to open physical regions (data and metadata).
250    async fn open_physical_region(
251        &self,
252        region_id: RegionId,
253        request: RegionOpenRequest,
254    ) -> Result<AffectedRows> {
255        let metadata_region_id = utils::to_metadata_region_id(region_id);
256        let data_region_id = utils::to_data_region_id(region_id);
257        let (open_metadata_region_request, open_data_region_request) =
258            self.transform_open_physical_region_request(request);
259        let _ = self
260            .mito
261            .handle_batch_open_requests(
262                2,
263                vec![
264                    (metadata_region_id, open_metadata_region_request),
265                    (data_region_id, open_data_region_request),
266                ],
267            )
268            .await
269            .context(BatchOpenMitoRegionSnafu {})?;
270
271        info!("Opened physical metric region {region_id}");
272        PHYSICAL_REGION_COUNT.inc();
273
274        Ok(0)
275    }
276
277    /// Recovers [MetricEngineState](crate::engine::state::MetricEngineState) from
278    /// physical region (idnefied by the given region id).
279    ///
280    /// Includes:
281    /// - Record physical region's column names
282    /// - Record the mapping between logical region id and physical region id
283    ///
284    /// Returns new opened logical region ids.
285    pub(crate) async fn recover_states(
286        &self,
287        physical_region_id: RegionId,
288        physical_region_options: PhysicalRegionOptions,
289    ) -> Result<Vec<RegionId>> {
290        // load logical regions and physical column names
291        let logical_regions = self
292            .metadata_region
293            .logical_regions(physical_region_id)
294            .await?;
295        common_telemetry::debug!(
296            "Recover states for physical region {}, logical regions: {:?}",
297            physical_region_id,
298            logical_regions
299        );
300        let physical_columns = self
301            .data_region
302            .physical_columns(physical_region_id)
303            .await?;
304        let primary_key_encoding = self
305            .mito
306            .get_primary_key_encoding(physical_region_id)
307            .context(PhysicalRegionNotFoundSnafu {
308                region_id: physical_region_id,
309            })?;
310
311        {
312            let mut state = self.state.write().unwrap();
313            // recover physical column names
314            // Safety: The physical columns are loaded from the data region, which always
315            // has a time index.
316            let time_index_unit = physical_columns
317                .iter()
318                .find_map(|col| {
319                    if col.semantic_type == SemanticType::Timestamp {
320                        col.column_schema
321                            .data_type
322                            .as_timestamp()
323                            .map(|data_type| data_type.unit())
324                    } else {
325                        None
326                    }
327                })
328                .unwrap();
329            let physical_columns = physical_columns
330                .into_iter()
331                .map(|col| (col.column_schema.name.clone(), col))
332                .collect();
333            state.add_physical_region(
334                physical_region_id,
335                physical_columns,
336                primary_key_encoding,
337                physical_region_options,
338                time_index_unit,
339            );
340            // recover logical regions
341            for logical_region_id in &logical_regions {
342                state.add_logical_region(physical_region_id, *logical_region_id);
343            }
344        }
345
346        let mut opened_logical_region_ids = Vec::new();
347        // The `recover_states` may be called multiple times, we only count the logical regions
348        // that are opened for the first time.
349        for logical_region_id in logical_regions {
350            if self
351                .metadata_region
352                .open_logical_region(logical_region_id)
353                .await
354            {
355                opened_logical_region_ids.push(logical_region_id);
356            }
357        }
358
359        LOGICAL_REGION_COUNT.add(opened_logical_region_ids.len() as i64);
360
361        Ok(opened_logical_region_ids)
362    }
363}
364
365// Unit tests in engine.rs