Skip to main content

standalone/
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
15use common_base::readable_size::ReadableSize;
16use common_config::{Configurable, KvBackendConfig};
17use common_event_recorder::EventRecorderOptions;
18use common_memory_manager::OnExhaustedPolicy;
19use common_options::memory::MemoryOptions;
20use common_telemetry::logging::{LoggingOptions, SlowQueryOptions, TracingOptions};
21use common_wal::config::DatanodeWalConfig;
22use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig};
23use file_engine::config::EngineConfig as FileEngineConfig;
24use flow::FlowConfig;
25use frontend::frontend::FrontendOptions;
26use frontend::service_config::{
27    InfluxdbOptions, JaegerOptions, MysqlOptions, OpentsdbOptions, OtlpOptions,
28    PendingRowsBatcherOptions, PostgresOptions, PromStoreOptions,
29};
30use mito2::config::MitoConfig;
31use pipeline::PipelineOptions;
32use query::options::QueryOptions;
33use serde::{Deserialize, Serialize};
34use servers::grpc::GrpcOptions;
35use servers::http::HttpOptions;
36
37#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
38#[serde(default)]
39pub struct StandaloneOptions {
40    pub enable_telemetry: bool,
41    pub default_timezone: Option<String>,
42    pub default_column_prefix: Option<String>,
43    /// Server-side global switch for auto table creation on write.
44    /// Upper bound: when `false`, missing tables are never auto-created even if a
45    /// request sets the `auto_create_table` hint to `true`. Default: `true`.
46    pub auto_create_table: bool,
47    /// Enables experimental Parquet database exports using shared Metric scans.
48    pub experimental_metric_export: bool,
49    /// Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight).
50    /// Set to 0 to disable the limit. Default: "0" (unlimited)
51    pub max_in_flight_write_bytes: ReadableSize,
52    /// Policy when write bytes quota is exhausted.
53    /// Options: "wait" (default, 10s), "wait(<duration>)", "fail"
54    pub write_bytes_exhausted_policy: OnExhaustedPolicy,
55    pub http: HttpOptions,
56    pub grpc: GrpcOptions,
57    pub mysql: MysqlOptions,
58    pub postgres: PostgresOptions,
59    pub opentsdb: OpentsdbOptions,
60    pub influxdb: InfluxdbOptions,
61    /// Ordinary-table batching with independent logical-table controls.
62    pub pending_rows_batcher: PendingRowsBatcherOptions,
63    pub jaeger: JaegerOptions,
64    pub otlp: OtlpOptions,
65    pub prom_store: PromStoreOptions,
66    pub wal: DatanodeWalConfig,
67    pub storage: StorageConfig,
68    pub metadata_store: KvBackendConfig,
69    pub procedure: ProcedureConfig,
70    pub flow: FlowConfig,
71    pub logging: LoggingOptions,
72    pub user_provider: Option<String>,
73    /// Options for different store engines.
74    pub region_engine: Vec<RegionEngineConfig>,
75    pub tracing: TracingOptions,
76    pub init_regions_in_background: bool,
77    pub init_regions_parallelism: usize,
78    pub slow_query: SlowQueryOptions,
79    pub query: QueryOptions,
80    pub memory: MemoryOptions,
81    /// The pipeline options.
82    pub pipeline: PipelineOptions,
83    /// The event recorder options.
84    pub event_recorder: EventRecorderOptions,
85    /// Environment variable keys to read and report in heartbeat messages.
86    pub heartbeat_env_vars: Vec<String>,
87}
88
89impl Default for StandaloneOptions {
90    fn default() -> Self {
91        Self {
92            enable_telemetry: true,
93            default_timezone: None,
94            default_column_prefix: None,
95            auto_create_table: true,
96            experimental_metric_export: false,
97            max_in_flight_write_bytes: ReadableSize(0),
98            write_bytes_exhausted_policy: OnExhaustedPolicy::default(),
99            http: HttpOptions::default(),
100            grpc: GrpcOptions::default(),
101            mysql: MysqlOptions::default(),
102            postgres: PostgresOptions::default(),
103            opentsdb: OpentsdbOptions::default(),
104            influxdb: InfluxdbOptions::default(),
105            pending_rows_batcher: PendingRowsBatcherOptions::default(),
106            jaeger: JaegerOptions::default(),
107            otlp: OtlpOptions::default(),
108            prom_store: PromStoreOptions::default(),
109            wal: DatanodeWalConfig::default(),
110            storage: StorageConfig::default(),
111            metadata_store: KvBackendConfig::default(),
112            procedure: ProcedureConfig::default(),
113            flow: FlowConfig::default(),
114            logging: LoggingOptions::default(),
115            user_provider: None,
116            region_engine: vec![
117                RegionEngineConfig::Mito(MitoConfig::default()),
118                RegionEngineConfig::File(FileEngineConfig::default()),
119            ],
120            tracing: TracingOptions::default(),
121            init_regions_in_background: false,
122            init_regions_parallelism: 16,
123            slow_query: SlowQueryOptions::default(),
124            query: QueryOptions::default(),
125            memory: MemoryOptions::default(),
126            pipeline: PipelineOptions::default(),
127            event_recorder: EventRecorderOptions::default(),
128            heartbeat_env_vars: vec![],
129        }
130    }
131}
132
133impl Configurable for StandaloneOptions {
134    fn env_list_keys() -> Option<&'static [&'static str]> {
135        Some(&[
136            "heartbeat_env_vars",
137            "wal.broker_endpoints",
138            "event_recorder.event_types",
139            "pending_rows_batcher.protocols",
140            "pending_rows_batcher.logical_table.protocols",
141        ])
142    }
143}
144
145/// The [`StandaloneOptions`] is only defined in `standalone` crate,
146/// we don't want to make `frontend` depends on it, so impl [`Into`]
147/// rather than [`From`].
148#[allow(clippy::from_over_into)]
149impl Into<FrontendOptions> for StandaloneOptions {
150    fn into(self) -> FrontendOptions {
151        self.frontend_options()
152    }
153}
154
155impl StandaloneOptions {
156    pub fn frontend_options(&self) -> FrontendOptions {
157        let cloned_opts = self.clone();
158        FrontendOptions {
159            default_timezone: cloned_opts.default_timezone,
160            auto_create_table: cloned_opts.auto_create_table,
161            experimental_metric_export: cloned_opts.experimental_metric_export,
162            max_in_flight_write_bytes: cloned_opts.max_in_flight_write_bytes,
163            write_bytes_exhausted_policy: cloned_opts.write_bytes_exhausted_policy,
164            http: cloned_opts.http,
165            grpc: cloned_opts.grpc,
166            mysql: cloned_opts.mysql,
167            postgres: cloned_opts.postgres,
168            opentsdb: cloned_opts.opentsdb,
169            influxdb: cloned_opts.influxdb,
170            pending_rows_batcher: cloned_opts.pending_rows_batcher,
171            jaeger: cloned_opts.jaeger,
172            otlp: cloned_opts.otlp,
173            prom_store: cloned_opts.prom_store,
174            meta_client: None,
175            logging: cloned_opts.logging,
176            user_provider: cloned_opts.user_provider,
177            query: cloned_opts.query,
178            slow_query: cloned_opts.slow_query,
179            pipeline: cloned_opts.pipeline,
180            event_recorder: cloned_opts.event_recorder,
181            heartbeat_env_vars: cloned_opts.heartbeat_env_vars.clone(),
182            ..Default::default()
183        }
184    }
185
186    pub fn datanode_options(&self) -> DatanodeOptions {
187        let cloned_opts = self.clone();
188        DatanodeOptions {
189            node_id: Some(0),
190            enable_telemetry: cloned_opts.enable_telemetry,
191            wal: cloned_opts.wal,
192            storage: cloned_opts.storage,
193            region_engine: cloned_opts.region_engine,
194            grpc: cloned_opts.grpc,
195            init_regions_in_background: cloned_opts.init_regions_in_background,
196            init_regions_parallelism: cloned_opts.init_regions_parallelism,
197            query: cloned_opts.query,
198            heartbeat_env_vars: cloned_opts.heartbeat_env_vars,
199            ..Default::default()
200        }
201    }
202
203    /// Sanitize the `StandaloneOptions` to ensure the config is valid.
204    pub fn sanitize(&mut self) {
205        if self.storage.is_object_storage() {
206            self.storage
207                .store
208                .cache_config_mut()
209                .unwrap()
210                .sanitize(&self.storage.data_home);
211        }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use std::sync::Arc;
218
219    use common_event_recorder::EventTypeFilter;
220    use servers::batcher::BatchingProtocol;
221
222    use crate::options::*;
223
224    #[test]
225    fn test_logical_batcher_config_forwarding() {
226        let opts: StandaloneOptions = toml::from_str("[pending_rows_batcher]\nprotocols = ['influxdb']\n[pending_rows_batcher.logical_table]\nprotocols = ['otlp', 'prom']\npending_rows_flush_interval = '10ms'").unwrap();
227        let frontend = opts.frontend_options();
228        assert_eq!(frontend.pending_rows_batcher, opts.pending_rows_batcher);
229        assert_eq!(
230            frontend.pending_rows_batcher.logical_table,
231            opts.pending_rows_batcher.logical_table
232        );
233        let restored: StandaloneOptions = toml::from_str(&toml::to_string(&opts).unwrap()).unwrap();
234        assert_eq!(
235            restored.pending_rows_batcher.logical_table,
236            opts.pending_rows_batcher.logical_table
237        );
238        assert!(
239            toml::from_str::<StandaloneOptions>(
240                "[pending_rows_batcher.logical_table]\nprotocols = ['logs']"
241            )
242            .is_err()
243        );
244    }
245
246    #[test]
247    fn test_logical_batcher_protocols_from_env() {
248        temp_env::with_vars(
249            [
250                (
251                    "STANDALONE_LOGICAL_TEST__PENDING_ROWS_BATCHER__PROTOCOLS",
252                    Some("otlp,influxdb"),
253                ),
254                (
255                    "STANDALONE_LOGICAL_TEST__PENDING_ROWS_BATCHER__LOGICAL_TABLE__PROTOCOLS",
256                    Some("prom,otlp"),
257                ),
258            ],
259            || {
260                let options =
261                    StandaloneOptions::load_layered_options(None, "STANDALONE_LOGICAL_TEST")
262                        .unwrap();
263                assert_eq!(options.pending_rows_batcher.table.protocols.len(), 2);
264                assert_eq!(
265                    options
266                        .pending_rows_batcher
267                        .logical_table
268                        .unwrap()
269                        .protocols,
270                    vec![BatchingProtocol::Prom, BatchingProtocol::Otlp]
271                );
272            },
273        );
274    }
275
276    #[test]
277    fn test_batcher_protocols_from_env() {
278        temp_env::with_vars(
279            [(
280                "STANDALONE_BATCHER_TEST__PENDING_ROWS_BATCHER__PROTOCOLS",
281                Some("influxdb,http_sql"),
282            )],
283            || {
284                let options =
285                    StandaloneOptions::load_layered_options(None, "STANDALONE_BATCHER_TEST")
286                        .unwrap();
287                assert_eq!(
288                    options.pending_rows_batcher.table.protocols,
289                    vec![BatchingProtocol::Influxdb, BatchingProtocol::HttpSql]
290                );
291            },
292        );
293    }
294
295    #[test]
296    fn test_protocol_pending_rows_batcher_config() {
297        let defaults: StandaloneOptions = toml::from_str("").unwrap();
298        assert!(
299            !defaults
300                .pending_rows_batcher
301                .table
302                .pending_rows_batching_enabled()
303        );
304        let options: StandaloneOptions = toml::from_str(
305            r#"
306[pending_rows_batcher]
307protocols = ["influxdb", "http_sql"]
308pending_rows_flush_interval = "5ms"
309max_batch_rows = 25
310flow_notification_queue_capacity = 17
311"#,
312        )
313        .unwrap();
314        assert_eq!(options.pending_rows_batcher.table.max_batch_rows, 25);
315        assert_eq!(options.pending_rows_batcher.table.protocols.len(), 2);
316        assert_eq!(
317            options
318                .pending_rows_batcher
319                .table
320                .flow_notification_queue_capacity
321                .get(),
322            17
323        );
324        assert!(
325            options
326                .pending_rows_batcher
327                .table
328                .pending_rows_batching_enabled()
329        );
330        let serialized = toml::to_string(&options).unwrap();
331        let parsed: StandaloneOptions = toml::from_str(&serialized).unwrap();
332        assert_eq!(options.influxdb, parsed.influxdb);
333        assert_eq!(options.opentsdb, parsed.opentsdb);
334        assert_eq!(options.pending_rows_batcher, parsed.pending_rows_batcher);
335        let frontend = options.frontend_options();
336        assert_eq!(options.influxdb, frontend.influxdb);
337        assert_eq!(options.opentsdb, frontend.opentsdb);
338        assert_eq!(options.pending_rows_batcher, frontend.pending_rows_batcher);
339    }
340
341    #[test]
342    fn test_event_recorder_event_types_preserve_filter_semantics() {
343        let all: StandaloneOptions = toml::from_str("").unwrap();
344        let none: StandaloneOptions = toml::from_str("[event_recorder]\nevent_types = []").unwrap();
345        let selected: StandaloneOptions =
346            toml::from_str("[event_recorder]\nevent_types = ['create_database']").unwrap();
347
348        assert!(all.event_recorder.event_types.allows("future_event"));
349        assert_eq!(
350            none.event_recorder.event_types.as_ref(),
351            &EventTypeFilter::Only(Default::default())
352        );
353        assert!(
354            selected
355                .event_recorder
356                .event_types
357                .allows("create_database")
358        );
359        assert!(!selected.event_recorder.event_types.allows("drop_database"));
360
361        let frontend_options = selected.frontend_options();
362        assert!(Arc::ptr_eq(
363            &selected.event_recorder.event_types,
364            &frontend_options.event_recorder.event_types,
365        ));
366    }
367
368    #[test]
369    fn test_query_options_propagated_to_components() {
370        let mut options = StandaloneOptions::default();
371        assert!(!options.frontend_options().experimental_metric_export);
372        options.experimental_metric_export = true;
373        assert!(options.frontend_options().experimental_metric_export);
374        options.query.parallelism = 4;
375
376        assert_eq!(options.frontend_options().query.parallelism, 4);
377        assert_eq!(options.datanode_options().query.parallelism, 4);
378    }
379}