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