metric_engine/engine/
options.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//! Specific options for the metric engine to create or open a region.
16
17use std::collections::HashMap;
18
19use store_api::metric_engine_consts::{
20    METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION,
21    METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION_DEFAULT,
22    METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION,
23    METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION_DEFAULT, METRIC_ENGINE_INDEX_TYPE_OPTION,
24};
25use store_api::mito_engine_options::MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING;
26
27use crate::error::{Error, ParseRegionOptionsSnafu, Result};
28
29/// The empirical value for the seg row count of the metric data region.
30/// Compared to the mito engine, the pattern of the metric engine constructs smaller indices.
31/// Therefore, compared to the default seg row count of 1024, by adjusting it to a smaller
32/// value and appropriately increasing the size of the index, it results in an improved indexing effect.
33const SEG_ROW_COUNT_FOR_DATA_REGION: u32 = 256;
34
35/// Physical region options.
36#[derive(Debug, Clone, Copy, PartialEq)]
37pub struct PhysicalRegionOptions {
38    pub index: IndexOptions,
39}
40
41/// Index options for auto created columns
42#[derive(Debug, Clone, Copy, Default, PartialEq)]
43pub enum IndexOptions {
44    #[default]
45    None,
46    Inverted,
47    Skipping {
48        granularity: u32,
49        false_positive_rate: f64,
50    },
51}
52
53/// Sets data region specific options.
54pub fn set_data_region_options(
55    options: &mut HashMap<String, String>,
56    sparse_primary_key_encoding_if_absent: bool,
57) {
58    options.remove(METRIC_ENGINE_INDEX_TYPE_OPTION);
59    options.remove(METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION);
60    options.remove(METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION);
61    options.insert(
62        "index.inverted_index.segment_row_count".to_string(),
63        SEG_ROW_COUNT_FOR_DATA_REGION.to_string(),
64    );
65    // Set memtable options for the data region.
66    options.insert("memtable.type".to_string(), "partition_tree".to_string());
67    if sparse_primary_key_encoding_if_absent
68        && !options.contains_key(MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING)
69    {
70        options.insert(
71            MEMTABLE_PARTITION_TREE_PRIMARY_KEY_ENCODING.to_string(),
72            "sparse".to_string(),
73        );
74    }
75}
76
77impl TryFrom<&HashMap<String, String>> for PhysicalRegionOptions {
78    type Error = Error;
79
80    fn try_from(value: &HashMap<String, String>) -> Result<Self> {
81        let index = match value
82            .get(METRIC_ENGINE_INDEX_TYPE_OPTION)
83            .map(|s| s.to_lowercase())
84        {
85            Some(ref index_type) if index_type == "inverted" => Ok(IndexOptions::Inverted),
86            Some(ref index_type) if index_type == "skipping" => {
87                let granularity = value
88                    .get(METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION)
89                    .map_or(
90                        Ok(METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION_DEFAULT),
91                        |g| {
92                            g.parse().map_err(|_| {
93                                ParseRegionOptionsSnafu {
94                                    reason: format!("Invalid granularity: {}", g),
95                                }
96                                .build()
97                            })
98                        },
99                    )?;
100                let false_positive_rate = value
101                    .get(METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION)
102                    .map_or(
103                        Ok(METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION_DEFAULT),
104                        |f| {
105                            f.parse().ok().filter(|f| *f > 0.0 && *f <= 1.0).ok_or(
106                                ParseRegionOptionsSnafu {
107                                    reason: format!("Invalid false positive rate: {}", f),
108                                }
109                                .build(),
110                            )
111                        },
112                    )?;
113                Ok(IndexOptions::Skipping {
114                    granularity,
115                    false_positive_rate,
116                })
117            }
118            Some(index_type) => ParseRegionOptionsSnafu {
119                reason: format!("Invalid index type: {}", index_type),
120            }
121            .fail(),
122            None => Ok(IndexOptions::default()),
123        }?;
124
125        Ok(PhysicalRegionOptions { index })
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_set_data_region_options_should_remove_metric_engine_options() {
135        let mut options = HashMap::new();
136        options.insert(
137            METRIC_ENGINE_INDEX_TYPE_OPTION.to_string(),
138            "inverted".to_string(),
139        );
140        options.insert(
141            METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION.to_string(),
142            "102400".to_string(),
143        );
144        options.insert(
145            METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION.to_string(),
146            "0.01".to_string(),
147        );
148        set_data_region_options(&mut options, false);
149
150        for key in [
151            METRIC_ENGINE_INDEX_TYPE_OPTION,
152            METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION,
153            METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION,
154        ] {
155            assert_eq!(options.get(key), None);
156        }
157    }
158
159    #[test]
160    fn test_deserialize_physical_region_options_from_hashmap() {
161        let mut options = HashMap::new();
162        options.insert(
163            METRIC_ENGINE_INDEX_TYPE_OPTION.to_string(),
164            "inverted".to_string(),
165        );
166        options.insert(
167            METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION.to_string(),
168            "102400".to_string(),
169        );
170        let physical_region_options = PhysicalRegionOptions::try_from(&options).unwrap();
171        assert_eq!(physical_region_options.index, IndexOptions::Inverted);
172
173        let mut options = HashMap::new();
174        options.insert(
175            METRIC_ENGINE_INDEX_TYPE_OPTION.to_string(),
176            "skipping".to_string(),
177        );
178        options.insert(
179            METRIC_ENGINE_INDEX_SKIPPING_INDEX_GRANULARITY_OPTION.to_string(),
180            "102400".to_string(),
181        );
182        options.insert(
183            METRIC_ENGINE_INDEX_SKIPPING_INDEX_FALSE_POSITIVE_RATE_OPTION.to_string(),
184            "0.01".to_string(),
185        );
186        let physical_region_options = PhysicalRegionOptions::try_from(&options).unwrap();
187        assert_eq!(
188            physical_region_options.index,
189            IndexOptions::Skipping {
190                granularity: 102400,
191                false_positive_rate: 0.01,
192            }
193        );
194    }
195}