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