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 std::time::Duration;
16
17use common_base::readable_size::ReadableSize;
18use common_config::{Configurable, KvBackendConfig};
19use common_event_recorder::EventRecorderOptions;
20use common_memory_manager::OnExhaustedPolicy;
21use common_options::memory::MemoryOptions;
22use common_telemetry::logging::{LoggingOptions, SlowQueryOptions, TracingOptions};
23use common_wal::config::DatanodeWalConfig;
24use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig};
25use file_engine::config::EngineConfig as FileEngineConfig;
26use flow::FlowConfig;
27use frontend::frontend::FrontendOptions;
28use frontend::service_config::{
29    InfluxdbOptions, JaegerOptions, MysqlOptions, OpentsdbOptions, OtlpOptions,
30    PendingRowsBatcherOptions, PostgresOptions, PromStoreOptions, WriteRetryOptions,
31};
32use mito2::config::MitoConfig;
33use pipeline::PipelineOptions;
34use query::options::QueryOptions;
35use serde::{Deserialize, Serialize};
36use servers::grpc::GrpcOptions;
37use servers::http::HttpOptions;
38
39#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
40#[serde(default)]
41pub struct StandaloneOptions {
42    pub enable_telemetry: bool,
43    pub default_timezone: Option<String>,
44    pub default_column_prefix: Option<String>,
45    /// Server-side global switch for auto table creation on write.
46    /// Upper bound: when `false`, missing tables are never auto-created even if a
47    /// request sets the `auto_create_table` hint to `true`. Default: `true`.
48    pub auto_create_table: 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    /// Maximum concurrent region-read streams. Zero means unlimited.
79    /// A single query can consume multiple permits.
80    pub max_concurrent_queries: usize,
81    /// Timeout to acquire a permit when `max_concurrent_queries` is reached.
82    /// Only effective when the limiter is enabled.
83    #[serde(with = "humantime_serde")]
84    pub concurrent_query_limiter_timeout: Duration,
85    pub slow_query: SlowQueryOptions,
86    pub query: QueryOptions,
87    pub memory: MemoryOptions,
88    /// The pipeline options.
89    pub pipeline: PipelineOptions,
90    /// The event recorder options.
91    pub event_recorder: EventRecorderOptions,
92    /// Environment variable keys to read and report in heartbeat messages.
93    pub heartbeat_env_vars: Vec<String>,
94}
95
96impl Default for StandaloneOptions {
97    fn default() -> Self {
98        Self {
99            enable_telemetry: true,
100            default_timezone: None,
101            default_column_prefix: None,
102            auto_create_table: true,
103            max_in_flight_write_bytes: ReadableSize(0),
104            write_bytes_exhausted_policy: OnExhaustedPolicy::default(),
105            http: HttpOptions::default(),
106            grpc: GrpcOptions::default(),
107            mysql: MysqlOptions::default(),
108            postgres: PostgresOptions::default(),
109            opentsdb: OpentsdbOptions::default(),
110            influxdb: InfluxdbOptions::default(),
111            pending_rows_batcher: PendingRowsBatcherOptions::default(),
112            jaeger: JaegerOptions::default(),
113            otlp: OtlpOptions::default(),
114            prom_store: PromStoreOptions::default(),
115            wal: DatanodeWalConfig::default(),
116            storage: StorageConfig::default(),
117            metadata_store: KvBackendConfig::default(),
118            procedure: ProcedureConfig::default(),
119            flow: FlowConfig::default(),
120            logging: LoggingOptions::default(),
121            user_provider: None,
122            region_engine: vec![
123                RegionEngineConfig::Mito(MitoConfig::default()),
124                RegionEngineConfig::File(FileEngineConfig::default()),
125            ],
126            tracing: TracingOptions::default(),
127            init_regions_in_background: false,
128            init_regions_parallelism: 16,
129            max_concurrent_queries: 0,
130            concurrent_query_limiter_timeout: Duration::from_millis(100),
131            slow_query: SlowQueryOptions::default(),
132            query: QueryOptions::default(),
133            memory: MemoryOptions::default(),
134            pipeline: PipelineOptions::default(),
135            event_recorder: EventRecorderOptions::default(),
136            heartbeat_env_vars: vec![],
137        }
138    }
139}
140
141impl Configurable for StandaloneOptions {
142    fn env_list_keys() -> Option<&'static [&'static str]> {
143        Some(&[
144            "heartbeat_env_vars",
145            "wal.broker_endpoints",
146            "event_recorder.event_types",
147            "pending_rows_batcher.protocols",
148            "pending_rows_batcher.logical_table.protocols",
149        ])
150    }
151
152    fn env_tagged_seq_keys() -> Option<&'static [&'static str]> {
153        Some(&["region_engine"])
154    }
155}
156
157/// The [`StandaloneOptions`] is only defined in `standalone` crate,
158/// we don't want to make `frontend` depends on it, so impl [`Into`]
159/// rather than [`From`].
160#[allow(clippy::from_over_into)]
161impl Into<FrontendOptions> for StandaloneOptions {
162    fn into(self) -> FrontendOptions {
163        self.frontend_options()
164    }
165}
166
167impl StandaloneOptions {
168    pub fn frontend_options(&self) -> FrontendOptions {
169        let cloned_opts = self.clone();
170        FrontendOptions {
171            default_timezone: cloned_opts.default_timezone,
172            auto_create_table: cloned_opts.auto_create_table,
173            max_in_flight_write_bytes: cloned_opts.max_in_flight_write_bytes,
174            write_bytes_exhausted_policy: cloned_opts.write_bytes_exhausted_policy,
175            http: cloned_opts.http,
176            grpc: cloned_opts.grpc,
177            mysql: cloned_opts.mysql,
178            postgres: cloned_opts.postgres,
179            opentsdb: cloned_opts.opentsdb,
180            influxdb: cloned_opts.influxdb,
181            pending_rows_batcher: cloned_opts.pending_rows_batcher,
182            jaeger: cloned_opts.jaeger,
183            otlp: cloned_opts.otlp,
184            prom_store: cloned_opts.prom_store,
185            meta_client: None,
186            // Local region calls consume requests directly and do not use remote write retries.
187            write_retry: WriteRetryOptions {
188                max_retries: 0,
189                ..Default::default()
190            },
191            logging: cloned_opts.logging,
192            user_provider: cloned_opts.user_provider,
193            query: cloned_opts.query,
194            slow_query: cloned_opts.slow_query,
195            pipeline: cloned_opts.pipeline,
196            event_recorder: cloned_opts.event_recorder,
197            heartbeat_env_vars: cloned_opts.heartbeat_env_vars.clone(),
198            ..Default::default()
199        }
200    }
201
202    pub fn datanode_options(&self) -> DatanodeOptions {
203        let cloned_opts = self.clone();
204        DatanodeOptions {
205            node_id: Some(0),
206            enable_telemetry: cloned_opts.enable_telemetry,
207            wal: cloned_opts.wal,
208            storage: cloned_opts.storage,
209            region_engine: cloned_opts.region_engine,
210            grpc: cloned_opts.grpc,
211            init_regions_in_background: cloned_opts.init_regions_in_background,
212            init_regions_parallelism: cloned_opts.init_regions_parallelism,
213            max_concurrent_queries: cloned_opts.max_concurrent_queries,
214            concurrent_query_limiter_timeout: cloned_opts.concurrent_query_limiter_timeout,
215            query: cloned_opts.query,
216            heartbeat_env_vars: cloned_opts.heartbeat_env_vars,
217            ..Default::default()
218        }
219    }
220
221    /// Sanitize the `StandaloneOptions` to ensure the config is valid.
222    pub fn sanitize(&mut self) {
223        if self.storage.is_object_storage() {
224            self.storage
225                .store
226                .cache_config_mut()
227                .unwrap()
228                .sanitize(&self.storage.data_home);
229        }
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use std::sync::Arc;
236
237    use common_event_recorder::EventTypeFilter;
238    use servers::batcher::BatchingProtocol;
239
240    use crate::options::*;
241
242    #[test]
243    fn test_concurrent_query_limiter_options() {
244        let defaults: StandaloneOptions = toml::from_str("").unwrap();
245        let datanode_defaults = DatanodeOptions::default();
246        assert_eq!(defaults.max_concurrent_queries, 0);
247        assert_eq!(
248            defaults.concurrent_query_limiter_timeout,
249            Duration::from_millis(100)
250        );
251        assert_eq!(
252            defaults.max_concurrent_queries,
253            datanode_defaults.max_concurrent_queries
254        );
255        assert_eq!(
256            defaults.concurrent_query_limiter_timeout,
257            datanode_defaults.concurrent_query_limiter_timeout
258        );
259
260        let options: StandaloneOptions = toml::from_str(
261            r#"max_concurrent_queries = 8
262concurrent_query_limiter_timeout = "250ms""#,
263        )
264        .unwrap();
265        let restored: StandaloneOptions =
266            toml::from_str(&toml::to_string(&options).unwrap()).unwrap();
267        for options in [options, restored] {
268            assert_eq!(options.max_concurrent_queries, 8);
269            assert_eq!(
270                options.concurrent_query_limiter_timeout,
271                Duration::from_millis(250)
272            );
273            let datanode = options.datanode_options();
274            assert_eq!(datanode.max_concurrent_queries, 8);
275            assert_eq!(
276                datanode.concurrent_query_limiter_timeout,
277                Duration::from_millis(250)
278            );
279        }
280        assert!(
281            toml::from_str::<StandaloneOptions>("concurrent_query_limiter_timeout = 'invalid'")
282                .is_err()
283        );
284    }
285
286    #[test]
287    fn test_concurrent_query_limiter_options_from_env() {
288        temp_env::with_vars(
289            [
290                ("STANDALONE_LIMITER_TEST__MAX_CONCURRENT_QUERIES", Some("4")),
291                (
292                    "STANDALONE_LIMITER_TEST__CONCURRENT_QUERY_LIMITER_TIMEOUT",
293                    Some("2s"),
294                ),
295            ],
296            || {
297                let options =
298                    StandaloneOptions::load_layered_options(None, "STANDALONE_LIMITER_TEST")
299                        .unwrap();
300                let datanode = options.datanode_options();
301                assert_eq!(datanode.max_concurrent_queries, 4);
302                assert_eq!(
303                    datanode.concurrent_query_limiter_timeout,
304                    Duration::from_secs(2)
305                );
306            },
307        );
308    }
309
310    #[test]
311    fn test_pipeline_host_access_config_forwarding() {
312        let defaults: StandaloneOptions = toml::from_str("").unwrap();
313        assert!(!defaults.pipeline.allow_vrl_host_access);
314        assert!(!defaults.frontend_options().pipeline.allow_vrl_host_access);
315        let options: StandaloneOptions =
316            toml::from_str("[pipeline]\nallow_vrl_host_access = true").unwrap();
317        assert!(options.frontend_options().pipeline.allow_vrl_host_access);
318        let serialized = toml::to_string(&options).unwrap();
319        let restored: StandaloneOptions = toml::from_str(&serialized).unwrap();
320        assert_eq!(options.pipeline, restored.pipeline);
321    }
322
323    #[test]
324    fn test_frontend_write_retries_disabled() {
325        assert!(FrontendOptions::default().write_retry.max_retries > 0);
326        assert_eq!(
327            StandaloneOptions::default()
328                .frontend_options()
329                .write_retry
330                .max_retries,
331            0
332        );
333    }
334
335    #[test]
336    fn test_logical_batcher_config_forwarding() {
337        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();
338        let frontend = opts.frontend_options();
339        assert_eq!(frontend.pending_rows_batcher, opts.pending_rows_batcher);
340        assert_eq!(
341            frontend.pending_rows_batcher.logical_table,
342            opts.pending_rows_batcher.logical_table
343        );
344        let restored: StandaloneOptions = toml::from_str(&toml::to_string(&opts).unwrap()).unwrap();
345        assert_eq!(
346            restored.pending_rows_batcher.logical_table,
347            opts.pending_rows_batcher.logical_table
348        );
349        assert!(
350            toml::from_str::<StandaloneOptions>(
351                "[pending_rows_batcher.logical_table]\nprotocols = ['logs']"
352            )
353            .is_err()
354        );
355    }
356
357    #[test]
358    fn test_logical_batcher_protocols_from_env() {
359        temp_env::with_vars(
360            [
361                (
362                    "STANDALONE_LOGICAL_TEST__PENDING_ROWS_BATCHER__PROTOCOLS",
363                    Some("otlp,influxdb"),
364                ),
365                (
366                    "STANDALONE_LOGICAL_TEST__PENDING_ROWS_BATCHER__LOGICAL_TABLE__PROTOCOLS",
367                    Some("prom,otlp"),
368                ),
369            ],
370            || {
371                let options =
372                    StandaloneOptions::load_layered_options(None, "STANDALONE_LOGICAL_TEST")
373                        .unwrap();
374                assert_eq!(options.pending_rows_batcher.table.protocols.len(), 2);
375                assert_eq!(
376                    options
377                        .pending_rows_batcher
378                        .logical_table
379                        .unwrap()
380                        .protocols,
381                    vec![BatchingProtocol::Prom, BatchingProtocol::Otlp]
382                );
383            },
384        );
385    }
386
387    #[test]
388    fn test_batcher_protocols_from_env() {
389        temp_env::with_vars(
390            [(
391                "STANDALONE_BATCHER_TEST__PENDING_ROWS_BATCHER__PROTOCOLS",
392                Some("influxdb,http_sql"),
393            )],
394            || {
395                let options =
396                    StandaloneOptions::load_layered_options(None, "STANDALONE_BATCHER_TEST")
397                        .unwrap();
398                assert_eq!(
399                    options.pending_rows_batcher.table.protocols,
400                    vec![BatchingProtocol::Influxdb, BatchingProtocol::HttpSql]
401                );
402            },
403        );
404    }
405
406    #[test]
407    fn test_protocol_pending_rows_batcher_config() {
408        let defaults: StandaloneOptions = toml::from_str("").unwrap();
409        assert!(
410            !defaults
411                .pending_rows_batcher
412                .table
413                .pending_rows_batching_enabled()
414        );
415        let options: StandaloneOptions = toml::from_str(
416            r#"
417[pending_rows_batcher]
418protocols = ["influxdb", "http_sql"]
419pending_rows_flush_interval = "5ms"
420max_batch_rows = 25
421flow_notification_queue_capacity = 17
422"#,
423        )
424        .unwrap();
425        assert_eq!(options.pending_rows_batcher.table.max_batch_rows, 25);
426        assert_eq!(options.pending_rows_batcher.table.protocols.len(), 2);
427        assert_eq!(
428            options
429                .pending_rows_batcher
430                .table
431                .flow_notification_queue_capacity
432                .get(),
433            17
434        );
435        assert!(
436            options
437                .pending_rows_batcher
438                .table
439                .pending_rows_batching_enabled()
440        );
441        let serialized = toml::to_string(&options).unwrap();
442        let parsed: StandaloneOptions = toml::from_str(&serialized).unwrap();
443        assert_eq!(options.influxdb, parsed.influxdb);
444        assert_eq!(options.opentsdb, parsed.opentsdb);
445        assert_eq!(options.pending_rows_batcher, parsed.pending_rows_batcher);
446        let frontend = options.frontend_options();
447        assert_eq!(options.influxdb, frontend.influxdb);
448        assert_eq!(options.opentsdb, frontend.opentsdb);
449        assert_eq!(options.pending_rows_batcher, frontend.pending_rows_batcher);
450    }
451
452    #[test]
453    fn test_event_recorder_event_types_preserve_filter_semantics() {
454        let all: StandaloneOptions = toml::from_str("").unwrap();
455        let none: StandaloneOptions = toml::from_str("[event_recorder]\nevent_types = []").unwrap();
456        let selected: StandaloneOptions =
457            toml::from_str("[event_recorder]\nevent_types = ['create_database']").unwrap();
458
459        assert!(all.event_recorder.event_types.allows("future_event"));
460        assert_eq!(
461            none.event_recorder.event_types.as_ref(),
462            &EventTypeFilter::Only(Default::default())
463        );
464        assert!(
465            selected
466                .event_recorder
467                .event_types
468                .allows("create_database")
469        );
470        assert!(!selected.event_recorder.event_types.allows("drop_database"));
471
472        let frontend_options = selected.frontend_options();
473        assert!(Arc::ptr_eq(
474            &selected.event_recorder.event_types,
475            &frontend_options.event_recorder.event_types,
476        ));
477    }
478
479    #[test]
480    fn test_query_options_propagated_to_components() {
481        let mut options = StandaloneOptions::default();
482        options.query.parallelism = 4;
483
484        assert_eq!(options.frontend_options().query.parallelism, 4);
485        assert_eq!(options.datanode_options().query.parallelism, 4);
486    }
487}