From 238b796a76f6ba36a4dbd27a8e3e14f47c7d7cd3 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:56:31 +0100 Subject: [PATCH 01/31] Use local data folder when creating configuration manager --- crates/modelardb_server/src/configuration.rs | 6 ++++-- crates/modelardb_server/src/context.rs | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 90ff04a9..b9af6b23 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -19,6 +19,7 @@ use std::env; use std::sync::Arc; +use modelardb_storage::data_folder::DataFolder; use modelardb_types::flight::protocol; use prost::Message; use tokio::sync::RwLock; @@ -55,7 +56,7 @@ pub struct ConfigurationManager { } impl ConfigurationManager { - pub fn new(cluster_mode: ClusterMode) -> Self { + pub fn new(local_data_folder: DataFolder, cluster_mode: ClusterMode) -> Self { let multivariate_reserved_memory_in_bytes = env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); @@ -417,12 +418,13 @@ mod tests { let data_folders = DataFolders::new( local_data_folder.clone(), Some(remote_data_folder), - local_data_folder, + local_data_folder.clone(), ); let manager = Manager::new(Uuid::new_v4().to_string()); let configuration_manager = Arc::new(RwLock::new(ConfigurationManager::new( + local_data_folder, ClusterMode::MultiNode(manager), ))); diff --git a/crates/modelardb_server/src/context.rs b/crates/modelardb_server/src/context.rs index 3c7bc54a..d73d2317 100644 --- a/crates/modelardb_server/src/context.rs +++ b/crates/modelardb_server/src/context.rs @@ -45,7 +45,10 @@ impl Context { /// metadata manager or storage engine could not be created, [`ModelarDbServerError`] is /// returned. pub async fn try_new(data_folders: DataFolders, cluster_mode: ClusterMode) -> Result { - let configuration_manager = Arc::new(RwLock::new(ConfigurationManager::new(cluster_mode))); + let configuration_manager = Arc::new(RwLock::new(ConfigurationManager::new( + data_folders.local_data_folder.clone(), + cluster_mode, + ))); let storage_engine = Arc::new(RwLock::new( StorageEngine::try_new(data_folders.clone(), &configuration_manager).await?, From 6cc6018f108f042d289c2a3d1ff45828bd259a07 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:33:23 +0100 Subject: [PATCH 02/31] Add serde and toml to dependencies --- Cargo.lock | 56 ++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ crates/modelardb_server/Cargo.toml | 2 ++ 3 files changed, 60 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3e9bbcb2..ece9bc31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3338,10 +3338,12 @@ dependencies = [ "object_store", "proptest", "prost", + "serde", "snmalloc-rs", "tempfile", "tokio", "tokio-stream", + "toml", "tonic", "tracing", "tracing-subscriber", @@ -4561,6 +4563,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5031,6 +5042,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "tonic" version = "0.13.1" @@ -5791,6 +5841,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + [[package]] name = "wit-bindgen-rt" version = "0.33.0" diff --git a/Cargo.toml b/Cargo.toml index 8f6910cd..ef932a13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,12 +37,14 @@ prost = "0.13.1" prost-build = "0.13.1" rand = "0.9.2" rustyline = "17.0.2" +serde = { version = "1.0.228", features = ["derive"] } snmalloc-rs = "0.3.8" sqlparser = "0.58.0" sysinfo = "0.37.2" tempfile = "3.23.0" tokio = "1.48.0" tokio-stream = "0.1.17" +toml = "0.9.8" tonic = "0.13.1" tracing = "0.1.41" tracing-subscriber = "0.3.20" diff --git a/crates/modelardb_server/Cargo.toml b/crates/modelardb_server/Cargo.toml index 4e8e8de7..68ed1a0a 100644 --- a/crates/modelardb_server/Cargo.toml +++ b/crates/modelardb_server/Cargo.toml @@ -38,9 +38,11 @@ modelardb_storage = { path = "../modelardb_storage" } modelardb_types = { path = "../modelardb_types" } object_store = { workspace = true, features = ["aws", "azure"] } prost.workspace = true +serde.workspace = true snmalloc-rs = { workspace = true, features = ["build_cc"] } tokio = { workspace = true, features = ["rt-multi-thread", "signal"] } tokio-stream.workspace = true +toml.workspace = true tonic.workspace = true uuid.workspace = true From 38de80f39ef24d108c112d1e06bba6c24c2897fb Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:33:55 +0100 Subject: [PATCH 03/31] Add default for ClusterMode to handle serde deserialization --- crates/modelardb_server/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/modelardb_server/src/main.rs b/crates/modelardb_server/src/main.rs index 784ada52..df98cad0 100644 --- a/crates/modelardb_server/src/main.rs +++ b/crates/modelardb_server/src/main.rs @@ -42,8 +42,9 @@ pub static PORT: LazyLock = /// The different possible modes that a ModelarDB server can be deployed in, assigned when the /// server is started. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq)] pub enum ClusterMode { + #[default] SingleNode, MultiNode(Manager), } From d4e369b7a636210fc9e2f1e9d78e44a289df6619 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:05:27 +0100 Subject: [PATCH 04/31] Add TOML deserialize error to ModelarDbServerError --- crates/modelardb_server/src/error.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/modelardb_server/src/error.rs b/crates/modelardb_server/src/error.rs index 545e8b9f..38b6d4c5 100644 --- a/crates/modelardb_server/src/error.rs +++ b/crates/modelardb_server/src/error.rs @@ -30,6 +30,7 @@ use modelardb_types::error::ModelarDbTypesError; use object_store::Error as ObjectStoreError; use object_store::path::Error as ObjectStorePathError; use prost::DecodeError; +use toml::de::Error as TomlDeserializeError; use tonic::Status as TonicStatusError; use tonic::transport::Error as TonicTransportError; @@ -65,6 +66,8 @@ pub enum ModelarDbServerError { ObjectStorePath(ObjectStorePathError), /// Error returned by Prost when decoding a message that is not valid. ProstDecode(DecodeError), + /// Error returned by TOML when deserializing a configuration file. + TomlDeserialize(TomlDeserializeError), /// Status returned by Tonic. TonicStatus(Box), /// Error returned by Tonic. @@ -87,6 +90,7 @@ impl Display for ModelarDbServerError { Self::ObjectStore(reason) => write!(f, "Object Store Error: {reason}"), Self::ObjectStorePath(reason) => write!(f, "Object Store Path Error: {reason}"), Self::ProstDecode(reason) => write!(f, "Prost Decode Error: {reason}"), + Self::TomlDeserialize(reason) => write!(f, "TOML Deserialize Error: {reason}"), Self::TonicStatus(reason) => write!(f, "Tonic Status Error: {reason}"), Self::TonicTransport(reason) => write!(f, "Tonic Transport Error: {reason}"), } @@ -110,6 +114,7 @@ impl Error for ModelarDbServerError { Self::ObjectStore(reason) => Some(reason), Self::ObjectStorePath(reason) => Some(reason), Self::ProstDecode(reason) => Some(reason), + Self::TomlDeserialize(reason) => Some(reason), Self::TonicStatus(reason) => Some(reason), Self::TonicTransport(reason) => Some(reason), } @@ -182,6 +187,12 @@ impl From for ModelarDbServerError { } } +impl From for ModelarDbServerError { + fn from(error: TomlDeserializeError) -> Self { + Self::TomlDeserialize(error) + } +} + impl From for ModelarDbServerError { fn from(error: TonicStatusError) -> Self { Self::TonicStatus(Box::new(error)) From b0f5a7e61ecbe864500a58d62b75513b88983923 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:15:58 +0100 Subject: [PATCH 05/31] Add TOML serializer error to ModelarDbServerError --- crates/modelardb_server/src/error.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/modelardb_server/src/error.rs b/crates/modelardb_server/src/error.rs index 38b6d4c5..e0eded77 100644 --- a/crates/modelardb_server/src/error.rs +++ b/crates/modelardb_server/src/error.rs @@ -31,6 +31,7 @@ use object_store::Error as ObjectStoreError; use object_store::path::Error as ObjectStorePathError; use prost::DecodeError; use toml::de::Error as TomlDeserializeError; +use toml::ser::Error as TomlSerializeError; use tonic::Status as TonicStatusError; use tonic::transport::Error as TonicTransportError; @@ -68,6 +69,8 @@ pub enum ModelarDbServerError { ProstDecode(DecodeError), /// Error returned by TOML when deserializing a configuration file. TomlDeserialize(TomlDeserializeError), + /// Error returned by TOML when serializing a configuration file. + TomlSerialize(TomlSerializeError), /// Status returned by Tonic. TonicStatus(Box), /// Error returned by Tonic. @@ -91,6 +94,7 @@ impl Display for ModelarDbServerError { Self::ObjectStorePath(reason) => write!(f, "Object Store Path Error: {reason}"), Self::ProstDecode(reason) => write!(f, "Prost Decode Error: {reason}"), Self::TomlDeserialize(reason) => write!(f, "TOML Deserialize Error: {reason}"), + Self::TomlSerialize(reason) => write!(f, "TOML Serialize Error: {reason}"), Self::TonicStatus(reason) => write!(f, "Tonic Status Error: {reason}"), Self::TonicTransport(reason) => write!(f, "Tonic Transport Error: {reason}"), } @@ -115,6 +119,7 @@ impl Error for ModelarDbServerError { Self::ObjectStorePath(reason) => Some(reason), Self::ProstDecode(reason) => Some(reason), Self::TomlDeserialize(reason) => Some(reason), + Self::TomlSerialize(reason) => Some(reason), Self::TonicStatus(reason) => Some(reason), Self::TonicTransport(reason) => Some(reason), } @@ -193,6 +198,12 @@ impl From for ModelarDbServerError { } } +impl From for ModelarDbServerError { + fn from(error: TomlSerializeError) -> Self { + Self::TomlSerialize(error) + } +} + impl From for ModelarDbServerError { fn from(error: TonicStatusError) -> Self { Self::TonicStatus(Box::new(error)) From 26042b31caa60a68d55f4ef9300b7fd9824feed2 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:31:08 +0100 Subject: [PATCH 06/31] Try to retrieve config from file when creating configuration manager --- crates/modelardb_server/src/configuration.rs | 123 +++++++++++++------ crates/modelardb_server/src/context.rs | 8 +- 2 files changed, 88 insertions(+), 43 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index b9af6b23..a4d4a6fd 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -21,18 +21,22 @@ use std::sync::Arc; use modelardb_storage::data_folder::DataFolder; use modelardb_types::flight::protocol; +use object_store::path::Path; +use object_store::{Error, ObjectStore, PutPayload}; use prost::Message; +use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; use crate::ClusterMode; -use crate::error::Result; +use crate::error::{ModelarDbServerError, Result}; use crate::storage::StorageEngine; /// Manages the system's configuration and provides functionality for updating the configuration. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct ConfigurationManager { /// The mode of the cluster used to determine the behaviour when starting the server, /// creating tables, updating the remote object store, and querying. + #[serde(skip)] pub(crate) cluster_mode: ClusterMode, /// Amount of memory to reserve for storing multivariate time series. multivariate_reserved_memory_in_bytes: u64, @@ -56,39 +60,79 @@ pub struct ConfigurationManager { } impl ConfigurationManager { - pub fn new(local_data_folder: DataFolder, cluster_mode: ClusterMode) -> Self { - let multivariate_reserved_memory_in_bytes = - env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") - .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); - - let uncompressed_reserved_memory_in_bytes = - env::var("MODELARDBD_UNCOMPRESSED_RESERVED_MEMORY_IN_BYTES") - .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); - - let compressed_reserved_memory_in_bytes = - env::var("MODELARDBD_COMPRESSED_RESERVED_MEMORY_IN_BYTES") - .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); - - let transfer_batch_size_in_bytes = env::var("MODELARDBD_TRANSFER_BATCH_SIZE_IN_BYTES") - .map_or(Some(64 * 1024 * 1024), |value| Some(value.parse().unwrap())); - - let transfer_time_in_seconds = env::var("MODELARDBD_TRANSFER_TIME_IN_SECONDS") - .map_or(None, |value| Some(value.parse().unwrap())); - - Self { - cluster_mode, - multivariate_reserved_memory_in_bytes, - uncompressed_reserved_memory_in_bytes, - compressed_reserved_memory_in_bytes, - transfer_batch_size_in_bytes, - transfer_time_in_seconds, - // TODO: Add support for running multiple threads per component. The individual - // components in the storage engine have not been validated with multiple threads, e.g., - // UncompressedDataManager may have race conditions finishing buffers if multiple - // different data points are added by multiple different clients in parallel. - ingestion_threads: 1, - compression_threads: 1, - writer_threads: 1, + /// Create a new [`ConfigurationManager`] using the `modelardb.toml` configuration file in the + /// local data folder. If the file does not exist, a configuration file is created with the + /// values from the environment variables or default values. If the configuration file could + /// not be read or created, [`ModelarDbServerError`] is returned. + pub async fn try_new(local_data_folder: DataFolder, cluster_mode: ClusterMode) -> Result { + // Check if there is a configuration file in the local data folder. + let object_store = local_data_folder.object_store(); + let conf_file_path = &Path::from("modelardb.toml"); + let maybe_conf_file = object_store.get(conf_file_path).await; + + match maybe_conf_file { + Ok(conf_file) => { + // If the configuration file exists, load the configuration from the file. + let bytes = conf_file.bytes().await?; + let mut configuration_from_file = toml::from_slice::(&bytes)?; + + // The cluster mode is always ClusterMode::SingleNode when loading the configuration + // from the file. + configuration_from_file.cluster_mode = cluster_mode; + + Ok(configuration_from_file) + } + Err(error) => match error { + Error::NotFound { .. } => { + // If the configuration file does not exist, create one with the configuration + // from the environment variables and default. + let multivariate_reserved_memory_in_bytes = + env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") + .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); + + let uncompressed_reserved_memory_in_bytes = + env::var("MODELARDBD_UNCOMPRESSED_RESERVED_MEMORY_IN_BYTES") + .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); + + let compressed_reserved_memory_in_bytes = + env::var("MODELARDBD_COMPRESSED_RESERVED_MEMORY_IN_BYTES") + .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); + + let transfer_batch_size_in_bytes = + env::var("MODELARDBD_TRANSFER_BATCH_SIZE_IN_BYTES") + .map_or(Some(64 * 1024 * 1024), |value| Some(value.parse().unwrap())); + + let transfer_time_in_seconds = env::var("MODELARDBD_TRANSFER_TIME_IN_SECONDS") + .map_or(None, |value| Some(value.parse().unwrap())); + + let configuration_manager = Self { + cluster_mode, + multivariate_reserved_memory_in_bytes, + uncompressed_reserved_memory_in_bytes, + compressed_reserved_memory_in_bytes, + transfer_batch_size_in_bytes, + transfer_time_in_seconds, + // TODO: Add support for running multiple threads per component. The individual + // components in the storage engine have not been validated with multiple threads, e.g., + // UncompressedDataManager may have race conditions finishing buffers if multiple + // different data points are added by multiple different clients in parallel. + ingestion_threads: 1, + compression_threads: 1, + writer_threads: 1, + }; + + // Create the TOML file. + let toml = toml::to_string(&configuration_manager)?; + object_store + .put(conf_file_path, PutPayload::from(toml.into_bytes())) + .await?; + + Ok(configuration_manager) + } + _ => Err(ModelarDbServerError::InvalidState(format!( + "Configuration file '{conf_file_path}' cannot be read." + ))), + }, } } @@ -423,10 +467,11 @@ mod tests { let manager = Manager::new(Uuid::new_v4().to_string()); - let configuration_manager = Arc::new(RwLock::new(ConfigurationManager::new( - local_data_folder, - ClusterMode::MultiNode(manager), - ))); + let configuration_manager = Arc::new(RwLock::new( + ConfigurationManager::try_new(local_data_folder, ClusterMode::MultiNode(manager)) + .await + .unwrap(), + )); let storage_engine = Arc::new(RwLock::new( StorageEngine::try_new(data_folders, &configuration_manager) diff --git a/crates/modelardb_server/src/context.rs b/crates/modelardb_server/src/context.rs index d73d2317..982c63b2 100644 --- a/crates/modelardb_server/src/context.rs +++ b/crates/modelardb_server/src/context.rs @@ -45,10 +45,10 @@ impl Context { /// metadata manager or storage engine could not be created, [`ModelarDbServerError`] is /// returned. pub async fn try_new(data_folders: DataFolders, cluster_mode: ClusterMode) -> Result { - let configuration_manager = Arc::new(RwLock::new(ConfigurationManager::new( - data_folders.local_data_folder.clone(), - cluster_mode, - ))); + let configuration_manager = Arc::new(RwLock::new( + ConfigurationManager::try_new(data_folders.local_data_folder.clone(), cluster_mode) + .await?, + )); let storage_engine = Arc::new(RwLock::new( StorageEngine::try_new(data_folders.clone(), &configuration_manager).await?, From b21e73ee4a9c447cd3d57737c3c2c1298e72f2d3 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:45:28 +0100 Subject: [PATCH 07/31] Add validation method that checks that the configuration is valid --- crates/modelardb_server/src/configuration.rs | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index a4d4a6fd..bcc00fbc 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -80,7 +80,7 @@ impl ConfigurationManager { // from the file. configuration_from_file.cluster_mode = cluster_mode; - Ok(configuration_from_file) + Self::validate_configuration(configuration_from_file) } Err(error) => match error { Error::NotFound { .. } => { @@ -112,10 +112,6 @@ impl ConfigurationManager { compressed_reserved_memory_in_bytes, transfer_batch_size_in_bytes, transfer_time_in_seconds, - // TODO: Add support for running multiple threads per component. The individual - // components in the storage engine have not been validated with multiple threads, e.g., - // UncompressedDataManager may have race conditions finishing buffers if multiple - // different data points are added by multiple different clients in parallel. ingestion_threads: 1, compression_threads: 1, writer_threads: 1, @@ -127,7 +123,7 @@ impl ConfigurationManager { .put(conf_file_path, PutPayload::from(toml.into_bytes())) .await?; - Ok(configuration_manager) + Self::validate_configuration(configuration_manager) } _ => Err(ModelarDbServerError::InvalidState(format!( "Configuration file '{conf_file_path}' cannot be read." @@ -136,6 +132,25 @@ impl ConfigurationManager { } } + /// Validate the fields in `configuration` and return the [`ConfigurationManager`] if it is + /// valid. If the configuration is invalid, return [`ModelarDbServerError`]. + fn validate_configuration(configuration: Self) -> Result { + // TODO: Add support for running multiple threads per component. The individual + // components in the storage engine have not been validated with multiple threads, e.g., + // UncompressedDataManager may have race conditions finishing buffers if multiple + // different data points are added by multiple different clients in parallel. + if configuration.ingestion_threads != 1 + || configuration.compression_threads != 1 + || configuration.writer_threads != 1 + { + Err(ModelarDbServerError::InvalidState( + "Only one thread per component is currently supported.".to_string(), + )) + } else { + Ok(configuration) + } + } + pub(crate) fn multivariate_reserved_memory_in_bytes(&self) -> u64 { self.multivariate_reserved_memory_in_bytes } From bc44af8c5e6c9000ec5d05c8fa71f1c234e24e01 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:06:23 +0100 Subject: [PATCH 08/31] Fix doc error in configuration --- crates/modelardb_server/src/configuration.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index bcc00fbc..8c32cff3 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -180,8 +180,8 @@ impl ConfigurationManager { } /// Set the new value and update the amount of memory for uncompressed data in the storage - /// engine. Returns [`ModelarDbServerError`](crate::error::ModelarDbServerError) if the memory - /// cannot be updated because a buffer cannot be spilled. + /// engine. Returns [`ModelarDbServerError`] if the memory cannot be updated because a buffer + /// cannot be spilled. pub(crate) async fn set_uncompressed_reserved_memory_in_bytes( &mut self, new_uncompressed_reserved_memory_in_bytes: u64, @@ -208,8 +208,7 @@ impl ConfigurationManager { } /// Set the new value and update the amount of memory for compressed data in the storage engine. - /// If the value was updated, return [`Ok`], otherwise return - /// [`ModelarDbServerError`](crate::error::ModelarDbServerError). + /// If the value was updated, return [`Ok`], otherwise return [`ModelarDbServerError`]. pub(crate) async fn set_compressed_reserved_memory_in_bytes( &mut self, new_compressed_reserved_memory_in_bytes: u64, @@ -236,8 +235,7 @@ impl ConfigurationManager { } /// Set the new value and update the transfer batch size in the storage engine. If the value was - /// updated, return [`Ok`], otherwise return - /// [`ModelarDbServerError`](crate::error::ModelarDbServerError). + /// updated, return [`Ok`], otherwise return [`ModelarDbServerError`]. pub(crate) async fn set_transfer_batch_size_in_bytes( &mut self, new_transfer_batch_size_in_bytes: Option, @@ -259,8 +257,7 @@ impl ConfigurationManager { } /// Set the new value and update the transfer time in the storage engine. If the value was - /// updated, return [`Ok`], otherwise return - /// [`ModelarDbServerError`](crate::error::ModelarDbServerError). + /// updated, return [`Ok`], otherwise return [`ModelarDbServerError`]. pub(crate) async fn set_transfer_time_in_seconds( &mut self, new_transfer_time_in_seconds: Option, From e979fb11b17cfc86ceea7b3da55d524bef35b01c Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:24:25 +0100 Subject: [PATCH 09/31] Make it clear in the documentation that UpdateConfiguration updates the conf file --- crates/modelardb_server/src/remote.rs | 3 ++- docs/user/README.md | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/modelardb_server/src/remote.rs b/crates/modelardb_server/src/remote.rs index 8df16abe..993dcf14 100644 --- a/crates/modelardb_server/src/remote.rs +++ b/crates/modelardb_server/src/remote.rs @@ -640,7 +640,8 @@ impl FlightService for FlightServiceHandler { /// configuration is returned in a [`Configuration`](protocol::Configuration) protobuf message. /// * `UpdateConfiguration`: Update a single setting in the configuration. The setting to update /// and the new value are provided in the [`UpdateConfiguration`](protocol::UpdateConfiguration) - /// protobuf message in the action body. + /// protobuf message in the action body. The setting is updated in the live server configuration + /// and the change is persisted in the `modelardb.toml` configuration file. /// * `NodeType`: Get the type of the node. The type is always `server`. The type of the node /// is returned as a string. async fn do_action( diff --git a/docs/user/README.md b/docs/user/README.md index b6f3c3ab..84e8e4dc 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -402,6 +402,10 @@ variables is provided here. If an environment variable is not set, the specified | MODELARDBD_TRANSFER_TIME_IN_SECONDS | Disabled | The number of seconds between each transfer of data to the remote object store. | | MODELARDBD_UNCOMPRESSED_DATA_BUFFER_CAPACITY | 65536 | The capacity of each uncompressed data buffer as the number of elements in the buffer where each element is a timestamp and a value. Note that the resulting size of the buffer has to be a multiple of 64 bytes to avoid the actual capacity being larger than the requested due to internal alignment. | +When the server is started for the first time, a configuration file is created in the root of the data folder named +`modelardb.toml`. This file is used to persist updates to the configuration made using the `UpdateConfiguration` +action. If the file is changed manually, the changes are only applied when the server is restarted. + ## Docker Two different [Docker](https://docs.docker.com/) environments are included to make it easy to experiment with the different use cases of ModelarDB. The first environment sets up a single instance of `modelardbd` that only uses local From 8cf2e7615093e93c489c9db0fe626ab539c7112b Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:05:02 +0100 Subject: [PATCH 10/31] Remove default from ClusterMode enum --- crates/modelardb_server/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/modelardb_server/src/main.rs b/crates/modelardb_server/src/main.rs index df98cad0..784ada52 100644 --- a/crates/modelardb_server/src/main.rs +++ b/crates/modelardb_server/src/main.rs @@ -42,9 +42,8 @@ pub static PORT: LazyLock = /// The different possible modes that a ModelarDB server can be deployed in, assigned when the /// server is started. -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum ClusterMode { - #[default] SingleNode, MultiNode(Manager), } From 262f2500f39ea79963913754358bff01c9882e8b Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:08:08 +0100 Subject: [PATCH 11/31] Move the actual configuration to a separate type to make serialization easier --- crates/modelardb_server/src/configuration.rs | 117 ++++++++++++------- crates/modelardb_server/src/storage/mod.rs | 6 +- 2 files changed, 81 insertions(+), 42 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 8c32cff3..c22dc10e 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -31,13 +31,11 @@ use crate::ClusterMode; use crate::error::{ModelarDbServerError, Result}; use crate::storage::StorageEngine; -/// Manages the system's configuration and provides functionality for updating the configuration. +/// The system's configuration. The configuration can be serialized into a `modelardb.toml` +/// configuration file and deserialized from it. Accessing and modifying the configuration should +/// only be done through the [`ConfigurationManager`]. #[derive(Clone, Serialize, Deserialize)] -pub struct ConfigurationManager { - /// The mode of the cluster used to determine the behaviour when starting the server, - /// creating tables, updating the remote object store, and querying. - #[serde(skip)] - pub(crate) cluster_mode: ClusterMode, +struct Configuration { /// Amount of memory to reserve for storing multivariate time series. multivariate_reserved_memory_in_bytes: u64, /// Amount of memory to reserve for storing uncompressed data buffers. @@ -59,6 +57,19 @@ pub struct ConfigurationManager { pub(crate) writer_threads: u8, } +/// Manages the system's configuration and provides functionality for updating the configuration. +#[derive(Clone)] +pub struct ConfigurationManager { + /// The mode of the cluster used to determine the behaviour when starting the server, + /// creating tables, updating the remote object store, and querying. + pub(crate) cluster_mode: ClusterMode, + /// The local data folder that stores the configuration file at the root. + local_data_folder: DataFolder, + /// The configuration of the system. This is stored in a separate type to allow for easier + /// serialization and deserialization. + configuration: Configuration, +} + impl ConfigurationManager { /// Create a new [`ConfigurationManager`] using the `modelardb.toml` configuration file in the /// local data folder. If the file does not exist, a configuration file is created with the @@ -74,13 +85,13 @@ impl ConfigurationManager { Ok(conf_file) => { // If the configuration file exists, load the configuration from the file. let bytes = conf_file.bytes().await?; - let mut configuration_from_file = toml::from_slice::(&bytes)?; - - // The cluster mode is always ClusterMode::SingleNode when loading the configuration - // from the file. - configuration_from_file.cluster_mode = cluster_mode; + let configuration_from_file = toml::from_slice::(&bytes)?; - Self::validate_configuration(configuration_from_file) + Self::validate_configuration( + local_data_folder, + cluster_mode, + configuration_from_file, + ) } Err(error) => match error { Error::NotFound { .. } => { @@ -105,8 +116,7 @@ impl ConfigurationManager { let transfer_time_in_seconds = env::var("MODELARDBD_TRANSFER_TIME_IN_SECONDS") .map_or(None, |value| Some(value.parse().unwrap())); - let configuration_manager = Self { - cluster_mode, + let configuration = Configuration { multivariate_reserved_memory_in_bytes, uncompressed_reserved_memory_in_bytes, compressed_reserved_memory_in_bytes, @@ -118,12 +128,12 @@ impl ConfigurationManager { }; // Create the TOML file. - let toml = toml::to_string(&configuration_manager)?; + let toml = toml::to_string(&configuration)?; object_store .put(conf_file_path, PutPayload::from(toml.into_bytes())) .await?; - Self::validate_configuration(configuration_manager) + Self::validate_configuration(local_data_folder, cluster_mode, configuration) } _ => Err(ModelarDbServerError::InvalidState(format!( "Configuration file '{conf_file_path}' cannot be read." @@ -134,7 +144,11 @@ impl ConfigurationManager { /// Validate the fields in `configuration` and return the [`ConfigurationManager`] if it is /// valid. If the configuration is invalid, return [`ModelarDbServerError`]. - fn validate_configuration(configuration: Self) -> Result { + fn validate_configuration( + local_data_folder: DataFolder, + cluster_mode: ClusterMode, + configuration: Configuration, + ) -> Result { // TODO: Add support for running multiple threads per component. The individual // components in the storage engine have not been validated with multiple threads, e.g., // UncompressedDataManager may have race conditions finishing buffers if multiple @@ -147,12 +161,16 @@ impl ConfigurationManager { "Only one thread per component is currently supported.".to_string(), )) } else { - Ok(configuration) + Ok(Self { + cluster_mode, + local_data_folder, + configuration, + }) } } pub(crate) fn multivariate_reserved_memory_in_bytes(&self) -> u64 { - self.multivariate_reserved_memory_in_bytes + self.configuration.multivariate_reserved_memory_in_bytes } /// Set the new value and update the amount of memory for multivariate data in the storage engine. @@ -164,7 +182,7 @@ impl ConfigurationManager { // Since the storage engine only keeps track of the remaining reserved memory, calculate // how much the value should change. let value_change = new_multivariate_reserved_memory_in_bytes as i64 - - self.multivariate_reserved_memory_in_bytes as i64; + - self.configuration.multivariate_reserved_memory_in_bytes as i64; storage_engine .write() @@ -172,11 +190,12 @@ impl ConfigurationManager { .adjust_multivariate_remaining_memory_in_bytes(value_change) .await; - self.multivariate_reserved_memory_in_bytes = new_multivariate_reserved_memory_in_bytes; + self.configuration.multivariate_reserved_memory_in_bytes = + new_multivariate_reserved_memory_in_bytes; } pub(crate) fn uncompressed_reserved_memory_in_bytes(&self) -> u64 { - self.uncompressed_reserved_memory_in_bytes + self.configuration.uncompressed_reserved_memory_in_bytes } /// Set the new value and update the amount of memory for uncompressed data in the storage @@ -190,7 +209,7 @@ impl ConfigurationManager { // Since the storage engine only keeps track of the remaining reserved memory, calculate // how much the value should change. let value_change = new_uncompressed_reserved_memory_in_bytes as i64 - - self.uncompressed_reserved_memory_in_bytes as i64; + - self.configuration.uncompressed_reserved_memory_in_bytes as i64; storage_engine .write() @@ -198,13 +217,14 @@ impl ConfigurationManager { .adjust_uncompressed_remaining_memory_in_bytes(value_change) .await?; - self.uncompressed_reserved_memory_in_bytes = new_uncompressed_reserved_memory_in_bytes; + self.configuration.uncompressed_reserved_memory_in_bytes = + new_uncompressed_reserved_memory_in_bytes; Ok(()) } pub(crate) fn compressed_reserved_memory_in_bytes(&self) -> u64 { - self.compressed_reserved_memory_in_bytes + self.configuration.compressed_reserved_memory_in_bytes } /// Set the new value and update the amount of memory for compressed data in the storage engine. @@ -217,7 +237,7 @@ impl ConfigurationManager { // Since the storage engine only keeps track of the remaining reserved memory, calculate // how much the value should change. let value_change = new_compressed_reserved_memory_in_bytes as i64 - - self.compressed_reserved_memory_in_bytes as i64; + - self.configuration.compressed_reserved_memory_in_bytes as i64; storage_engine .write() @@ -225,13 +245,14 @@ impl ConfigurationManager { .adjust_compressed_remaining_memory_in_bytes(value_change) .await?; - self.compressed_reserved_memory_in_bytes = new_compressed_reserved_memory_in_bytes; + self.configuration.compressed_reserved_memory_in_bytes = + new_compressed_reserved_memory_in_bytes; Ok(()) } pub(crate) fn transfer_batch_size_in_bytes(&self) -> Option { - self.transfer_batch_size_in_bytes + self.configuration.transfer_batch_size_in_bytes } /// Set the new value and update the transfer batch size in the storage engine. If the value was @@ -247,13 +268,13 @@ impl ConfigurationManager { .set_transfer_batch_size_in_bytes(new_transfer_batch_size_in_bytes) .await?; - self.transfer_batch_size_in_bytes = new_transfer_batch_size_in_bytes; + self.configuration.transfer_batch_size_in_bytes = new_transfer_batch_size_in_bytes; Ok(()) } pub(crate) fn transfer_time_in_seconds(&self) -> Option { - self.transfer_time_in_seconds + self.configuration.transfer_time_in_seconds } /// Set the new value and update the transfer time in the storage engine. If the value was @@ -269,23 +290,41 @@ impl ConfigurationManager { .set_transfer_time_in_seconds(new_transfer_time_in_seconds) .await?; - self.transfer_time_in_seconds = new_transfer_time_in_seconds; + self.configuration.transfer_time_in_seconds = new_transfer_time_in_seconds; Ok(()) } + pub(crate) fn ingestion_threads(&self) -> u8 { + self.configuration.ingestion_threads + } + + pub(crate) fn compression_threads(&self) -> u8 { + self.configuration.compression_threads + } + + pub(crate) fn writer_threads(&self) -> u8 { + self.configuration.writer_threads + } + /// Encode the current configuration into a [`Configuration`](protocol::Configuration) /// protobuf message and serialize it. pub(crate) fn encode_and_serialize(&self) -> Vec { let configuration = protocol::Configuration { - multivariate_reserved_memory_in_bytes: self.multivariate_reserved_memory_in_bytes, - uncompressed_reserved_memory_in_bytes: self.uncompressed_reserved_memory_in_bytes, - compressed_reserved_memory_in_bytes: self.compressed_reserved_memory_in_bytes, - transfer_batch_size_in_bytes: self.transfer_batch_size_in_bytes, - transfer_time_in_seconds: self.transfer_time_in_seconds, - ingestion_threads: self.ingestion_threads as u32, - compression_threads: self.compression_threads as u32, - writer_threads: self.writer_threads as u32, + multivariate_reserved_memory_in_bytes: self + .configuration + .multivariate_reserved_memory_in_bytes, + uncompressed_reserved_memory_in_bytes: self + .configuration + .uncompressed_reserved_memory_in_bytes, + compressed_reserved_memory_in_bytes: self + .configuration + .compressed_reserved_memory_in_bytes, + transfer_batch_size_in_bytes: self.configuration.transfer_batch_size_in_bytes, + transfer_time_in_seconds: self.configuration.transfer_time_in_seconds, + ingestion_threads: self.configuration.ingestion_threads as u32, + compression_threads: self.configuration.compression_threads as u32, + writer_threads: self.configuration.writer_threads as u32, }; configuration.encode_to_vec() diff --git a/crates/modelardb_server/src/storage/mod.rs b/crates/modelardb_server/src/storage/mod.rs index 65c8e6f0..f7488f10 100644 --- a/crates/modelardb_server/src/storage/mod.rs +++ b/crates/modelardb_server/src/storage/mod.rs @@ -120,7 +120,7 @@ impl StorageEngine { let uncompressed_data_manager = uncompressed_data_manager.clone(); Self::start_threads( - configuration_manager.ingestion_threads, + configuration_manager.ingestion_threads(), "Ingestion", move || { if let Err(error) = @@ -138,7 +138,7 @@ impl StorageEngine { let uncompressed_data_manager = uncompressed_data_manager.clone(); Self::start_threads( - configuration_manager.compression_threads, + configuration_manager.compression_threads(), "Compression", move || { if let Err(error) = @@ -179,7 +179,7 @@ impl StorageEngine { let compressed_data_manager = compressed_data_manager.clone(); Self::start_threads( - configuration_manager.writer_threads, + configuration_manager.writer_threads(), "Writer", move || { if let Err(error) = From 3fe68a6de30c48ef7eff3d166cb155b8bf549bf6 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:28:42 +0100 Subject: [PATCH 12/31] Save the configuration when it is updated --- crates/modelardb_server/src/configuration.rs | 67 ++++++++++++++------ crates/modelardb_server/src/remote.rs | 5 +- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index c22dc10e..6b86c064 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -31,6 +31,8 @@ use crate::ClusterMode; use crate::error::{ModelarDbServerError, Result}; use crate::storage::StorageEngine; +const CONFIGURATION_FILE_NAME: &str = "modelardb.toml"; + /// The system's configuration. The configuration can be serialized into a `modelardb.toml` /// configuration file and deserialized from it. Accessing and modifying the configuration should /// only be done through the [`ConfigurationManager`]. @@ -78,8 +80,7 @@ impl ConfigurationManager { pub async fn try_new(local_data_folder: DataFolder, cluster_mode: ClusterMode) -> Result { // Check if there is a configuration file in the local data folder. let object_store = local_data_folder.object_store(); - let conf_file_path = &Path::from("modelardb.toml"); - let maybe_conf_file = object_store.get(conf_file_path).await; + let maybe_conf_file = object_store.get(&Path::from(CONFIGURATION_FILE_NAME)).await; match maybe_conf_file { Ok(conf_file) => { @@ -127,16 +128,12 @@ impl ConfigurationManager { writer_threads: 1, }; - // Create the TOML file. - let toml = toml::to_string(&configuration)?; - object_store - .put(conf_file_path, PutPayload::from(toml.into_bytes())) - .await?; + save_configuration(&local_data_folder, &configuration).await?; Self::validate_configuration(local_data_folder, cluster_mode, configuration) } _ => Err(ModelarDbServerError::InvalidState(format!( - "Configuration file '{conf_file_path}' cannot be read." + "Configuration file '{CONFIGURATION_FILE_NAME}' cannot be read." ))), }, } @@ -173,12 +170,14 @@ impl ConfigurationManager { self.configuration.multivariate_reserved_memory_in_bytes } - /// Set the new value and update the amount of memory for multivariate data in the storage engine. + /// Set the new value and update the amount of memory for multivariate data in the storage + /// engine. Returns [`ModelarDbServerError`] if the new configuration cannot be saved to the + /// configuration file. pub(crate) async fn set_multivariate_reserved_memory_in_bytes( &mut self, new_multivariate_reserved_memory_in_bytes: u64, storage_engine: Arc>, - ) { + ) -> Result<()> { // Since the storage engine only keeps track of the remaining reserved memory, calculate // how much the value should change. let value_change = new_multivariate_reserved_memory_in_bytes as i64 @@ -192,6 +191,8 @@ impl ConfigurationManager { self.configuration.multivariate_reserved_memory_in_bytes = new_multivariate_reserved_memory_in_bytes; + + save_configuration(&self.local_data_folder, &self.configuration).await } pub(crate) fn uncompressed_reserved_memory_in_bytes(&self) -> u64 { @@ -200,7 +201,7 @@ impl ConfigurationManager { /// Set the new value and update the amount of memory for uncompressed data in the storage /// engine. Returns [`ModelarDbServerError`] if the memory cannot be updated because a buffer - /// cannot be spilled. + /// cannot be spilled or if the new configuration cannot be saved to the configuration file. pub(crate) async fn set_uncompressed_reserved_memory_in_bytes( &mut self, new_uncompressed_reserved_memory_in_bytes: u64, @@ -220,7 +221,7 @@ impl ConfigurationManager { self.configuration.uncompressed_reserved_memory_in_bytes = new_uncompressed_reserved_memory_in_bytes; - Ok(()) + save_configuration(&self.local_data_folder, &self.configuration).await } pub(crate) fn compressed_reserved_memory_in_bytes(&self) -> u64 { @@ -228,7 +229,8 @@ impl ConfigurationManager { } /// Set the new value and update the amount of memory for compressed data in the storage engine. - /// If the value was updated, return [`Ok`], otherwise return [`ModelarDbServerError`]. + /// Returns [`ModelarDbServerError`] if the memory cannot be updated because a buffer cannot be + /// saved to disk or if the new configuration cannot be saved to the configuration file. pub(crate) async fn set_compressed_reserved_memory_in_bytes( &mut self, new_compressed_reserved_memory_in_bytes: u64, @@ -248,15 +250,16 @@ impl ConfigurationManager { self.configuration.compressed_reserved_memory_in_bytes = new_compressed_reserved_memory_in_bytes; - Ok(()) + save_configuration(&self.local_data_folder, &self.configuration).await } pub(crate) fn transfer_batch_size_in_bytes(&self) -> Option { self.configuration.transfer_batch_size_in_bytes } - /// Set the new value and update the transfer batch size in the storage engine. If the value was - /// updated, return [`Ok`], otherwise return [`ModelarDbServerError`]. + /// Set the new value and update the transfer batch size in the storage engine. Returns + /// [`ModelarDbServerError`] if the value cannot be updated or if the new configuration cannot + /// be saved to the configuration file. pub(crate) async fn set_transfer_batch_size_in_bytes( &mut self, new_transfer_batch_size_in_bytes: Option, @@ -270,15 +273,16 @@ impl ConfigurationManager { self.configuration.transfer_batch_size_in_bytes = new_transfer_batch_size_in_bytes; - Ok(()) + save_configuration(&self.local_data_folder, &self.configuration).await } pub(crate) fn transfer_time_in_seconds(&self) -> Option { self.configuration.transfer_time_in_seconds } - /// Set the new value and update the transfer time in the storage engine. If the value was - /// updated, return [`Ok`], otherwise return [`ModelarDbServerError`]. + /// Set the new value and update the transfer time in the storage engine. Returns + /// [`ModelarDbServerError`] if the value cannot be updated or if the new configuration cannot + /// be saved to the configuration file. pub(crate) async fn set_transfer_time_in_seconds( &mut self, new_transfer_time_in_seconds: Option, @@ -292,7 +296,7 @@ impl ConfigurationManager { self.configuration.transfer_time_in_seconds = new_transfer_time_in_seconds; - Ok(()) + save_configuration(&self.local_data_folder, &self.configuration).await } pub(crate) fn ingestion_threads(&self) -> u8 { @@ -331,6 +335,26 @@ impl ConfigurationManager { } } +/// Save `configuration` to the configuration file at the root of `local_data_folder`. If the file +/// does not exist, it is created. If the configuration file could not be updated or created, return +/// [`ModelarDbServerError`]. +async fn save_configuration( + local_data_folder: &DataFolder, + configuration: &Configuration, +) -> Result<()> { + let toml = toml::to_string(configuration)?; + let object_store = local_data_folder.object_store(); + + object_store + .put( + &Path::from(CONFIGURATION_FILE_NAME), + PutPayload::from(toml.into_bytes()), + ) + .await?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -365,7 +389,8 @@ mod tests { .write() .await .set_multivariate_reserved_memory_in_bytes(new_value, storage_engine) - .await; + .await + .unwrap(); assert_eq!( configuration_manager diff --git a/crates/modelardb_server/src/remote.rs b/crates/modelardb_server/src/remote.rs index 993dcf14..932497d4 100644 --- a/crates/modelardb_server/src/remote.rs +++ b/crates/modelardb_server/src/remote.rs @@ -745,9 +745,8 @@ impl FlightService for FlightServiceHandler { configuration_manager .set_multivariate_reserved_memory_in_bytes(new_value, storage_engine) - .await; - - Ok(()) + .await + .map_err(error_to_status_internal) } Ok(protocol::update_configuration::Setting::UncompressedReservedMemoryInBytes) => { let new_value = maybe_new_value.ok_or(invalid_null_error)?; From b47dd8e3e46b2317cae37de43696816566b120cf Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:34:51 +0100 Subject: [PATCH 13/31] Use a consistent structure for the doc comments --- crates/modelardb_server/src/configuration.rs | 25 ++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 6b86c064..3690c33f 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -171,8 +171,8 @@ impl ConfigurationManager { } /// Set the new value and update the amount of memory for multivariate data in the storage - /// engine. Returns [`ModelarDbServerError`] if the new configuration cannot be saved to the - /// configuration file. + /// engine. If the new configuration could not be saved to the configuration file, return + /// [`ModelarDbServerError`]. pub(crate) async fn set_multivariate_reserved_memory_in_bytes( &mut self, new_multivariate_reserved_memory_in_bytes: u64, @@ -200,8 +200,9 @@ impl ConfigurationManager { } /// Set the new value and update the amount of memory for uncompressed data in the storage - /// engine. Returns [`ModelarDbServerError`] if the memory cannot be updated because a buffer - /// cannot be spilled or if the new configuration cannot be saved to the configuration file. + /// engine. If the memory could not be updated because a buffer could not be spilled or if the + /// new configuration could not be saved to the configuration file, return + /// [`ModelarDbServerError`]. pub(crate) async fn set_uncompressed_reserved_memory_in_bytes( &mut self, new_uncompressed_reserved_memory_in_bytes: u64, @@ -229,8 +230,8 @@ impl ConfigurationManager { } /// Set the new value and update the amount of memory for compressed data in the storage engine. - /// Returns [`ModelarDbServerError`] if the memory cannot be updated because a buffer cannot be - /// saved to disk or if the new configuration cannot be saved to the configuration file. + /// If the memory could not be updated because a buffer could not be saved to disk or if the new + /// configuration could not be saved to the configuration file, return [`ModelarDbServerError`]. pub(crate) async fn set_compressed_reserved_memory_in_bytes( &mut self, new_compressed_reserved_memory_in_bytes: u64, @@ -257,9 +258,9 @@ impl ConfigurationManager { self.configuration.transfer_batch_size_in_bytes } - /// Set the new value and update the transfer batch size in the storage engine. Returns - /// [`ModelarDbServerError`] if the value cannot be updated or if the new configuration cannot - /// be saved to the configuration file. + /// Set the new value and update the transfer batch size in the storage engine. If the batch + /// size could not be updated or if the new configuration could not be saved to the + /// configuration file, return [`ModelarDbServerError`]. pub(crate) async fn set_transfer_batch_size_in_bytes( &mut self, new_transfer_batch_size_in_bytes: Option, @@ -280,9 +281,9 @@ impl ConfigurationManager { self.configuration.transfer_time_in_seconds } - /// Set the new value and update the transfer time in the storage engine. Returns - /// [`ModelarDbServerError`] if the value cannot be updated or if the new configuration cannot - /// be saved to the configuration file. + /// Set the new value and update the transfer time in the storage engine. If the transfer time + /// could not be updated or if the new configuration could not be saved to the configuration + /// file, return [`ModelarDbServerError`]. pub(crate) async fn set_transfer_time_in_seconds( &mut self, new_transfer_time_in_seconds: Option, From b79658053d89cc48c8d668a691381cd8f89f729d Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:52:49 +0100 Subject: [PATCH 14/31] Add test to ensure configuration file is created if it does not exist --- crates/modelardb_server/src/configuration.rs | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 3690c33f..43bc0537 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -372,6 +372,35 @@ mod tests { use crate::storage::StorageEngine; // Tests for ConfigurationManager. + #[tokio::test] + async fn test_configuration_file_is_created_if_it_does_not_exist() { + let temp_dir = tempfile::tempdir().unwrap(); + let (_storage_engine, _configuration_manager) = create_components(&temp_dir).await; + + let configuration = configuration_from_file(&temp_dir).await; + + assert_eq!( + configuration.multivariate_reserved_memory_in_bytes, + 512 * 1024 * 1024 + ); + assert_eq!( + configuration.uncompressed_reserved_memory_in_bytes, + 512 * 1024 * 1024 + ); + assert_eq!( + configuration.compressed_reserved_memory_in_bytes, + 512 * 1024 * 1024 + ); + assert_eq!( + configuration.transfer_batch_size_in_bytes, + Some(64 * 1024 * 1024) + ); + assert_eq!(configuration.transfer_time_in_seconds, None); + assert_eq!(configuration.ingestion_threads, 1); + assert_eq!(configuration.compression_threads, 1); + assert_eq!(configuration.writer_threads, 1); + } + #[tokio::test] async fn test_set_multivariate_reserved_memory_in_bytes() { let temp_dir = tempfile::tempdir().unwrap(); @@ -522,6 +551,14 @@ mod tests { ); } + /// Return the configuration from the configuration file at the root of `temp_dir`. + async fn configuration_from_file(temp_dir: &TempDir) -> Configuration { + let configuration_file_path = temp_dir.path().join(CONFIGURATION_FILE_NAME); + let file_content = std::fs::read_to_string(&configuration_file_path).unwrap(); + + toml::from_str::(&file_content).unwrap() + } + /// Create a [`StorageEngine`] and a [`ConfigurationManager`]. async fn create_components( temp_dir: &TempDir, From 85a59ae918927643c02116190f8740e6edd29e2c Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:09:52 +0100 Subject: [PATCH 15/31] Add test to ensure configuration file is read if it exists --- crates/modelardb_server/src/configuration.rs | 58 ++++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 43bc0537..60ed01ce 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -36,7 +36,7 @@ const CONFIGURATION_FILE_NAME: &str = "modelardb.toml"; /// The system's configuration. The configuration can be serialized into a `modelardb.toml` /// configuration file and deserialized from it. Accessing and modifying the configuration should /// only be done through the [`ConfigurationManager`]. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct Configuration { /// Amount of memory to reserve for storing multivariate time series. multivariate_reserved_memory_in_bytes: u64, @@ -375,30 +375,42 @@ mod tests { #[tokio::test] async fn test_configuration_file_is_created_if_it_does_not_exist() { let temp_dir = tempfile::tempdir().unwrap(); - let (_storage_engine, _configuration_manager) = create_components(&temp_dir).await; + let (_storage_engine, configuration_manager) = create_components(&temp_dir).await; - let configuration = configuration_from_file(&temp_dir).await; + let configuration_from_manager = configuration_manager.read().await.configuration.clone(); + let configuration_from_file = configuration_from_file(&temp_dir).await; - assert_eq!( - configuration.multivariate_reserved_memory_in_bytes, - 512 * 1024 * 1024 - ); - assert_eq!( - configuration.uncompressed_reserved_memory_in_bytes, - 512 * 1024 * 1024 - ); - assert_eq!( - configuration.compressed_reserved_memory_in_bytes, - 512 * 1024 * 1024 - ); - assert_eq!( - configuration.transfer_batch_size_in_bytes, - Some(64 * 1024 * 1024) - ); - assert_eq!(configuration.transfer_time_in_seconds, None); - assert_eq!(configuration.ingestion_threads, 1); - assert_eq!(configuration.compression_threads, 1); - assert_eq!(configuration.writer_threads, 1); + assert_eq!(configuration_from_manager, configuration_from_file); + } + + #[tokio::test] + async fn test_configuration_file_is_read_if_it_exists() { + let temp_dir = tempfile::tempdir().unwrap(); + let local_url = temp_dir.path().to_str().unwrap(); + let local_data_folder = DataFolder::open_local_url(local_url).await.unwrap(); + + let existing_configuration = Configuration { + multivariate_reserved_memory_in_bytes: 1, + uncompressed_reserved_memory_in_bytes: 1, + compressed_reserved_memory_in_bytes: 1, + transfer_batch_size_in_bytes: Some(1), + transfer_time_in_seconds: Some(1), + ingestion_threads: 1, + compression_threads: 1, + writer_threads: 1, + }; + + save_configuration(&local_data_folder, &existing_configuration) + .await + .unwrap(); + + let (_storage_engine, configuration_manager) = create_components(&temp_dir).await; + + let configuration_from_manager = configuration_manager.read().await.configuration.clone(); + let configuration_from_file = configuration_from_file(&temp_dir).await; + + assert_eq!(existing_configuration, configuration_from_manager); + assert_eq!(existing_configuration, configuration_from_file); } #[tokio::test] From 6d11839675e20a9595ad31a768f0bb09617bebff Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:16:01 +0100 Subject: [PATCH 16/31] Add check to set_ tests to ensure the file is updated --- crates/modelardb_server/src/configuration.rs | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 60ed01ce..f62fd61f 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -441,6 +441,12 @@ mod tests { .multivariate_reserved_memory_in_bytes(), new_value ); + + let configuration_from_file = configuration_from_file(&temp_dir).await; + assert_eq!( + configuration_from_file.multivariate_reserved_memory_in_bytes, + new_value + ); } #[tokio::test] @@ -471,6 +477,12 @@ mod tests { .uncompressed_reserved_memory_in_bytes(), new_value ); + + let configuration_from_file = configuration_from_file(&temp_dir).await; + assert_eq!( + configuration_from_file.uncompressed_reserved_memory_in_bytes, + new_value + ); } #[tokio::test] @@ -501,6 +513,12 @@ mod tests { .compressed_reserved_memory_in_bytes(), new_value ); + + let configuration_from_file = configuration_from_file(&temp_dir).await; + assert_eq!( + configuration_from_file.compressed_reserved_memory_in_bytes, + new_value + ); } #[tokio::test] @@ -531,6 +549,12 @@ mod tests { .transfer_batch_size_in_bytes(), new_value ); + + let configuration_from_file = configuration_from_file(&temp_dir).await; + assert_eq!( + configuration_from_file.transfer_batch_size_in_bytes, + new_value + ); } #[tokio::test] @@ -561,6 +585,9 @@ mod tests { .transfer_time_in_seconds(), new_value ); + + let configuration_from_file = configuration_from_file(&temp_dir).await; + assert_eq!(configuration_from_file.transfer_time_in_seconds, new_value) } /// Return the configuration from the configuration file at the root of `temp_dir`. From 2f31911fbe13e512aab65a986cacaaa8feaddb96 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:42:21 +0100 Subject: [PATCH 17/31] Fix doc minor issues --- crates/modelardb_server/src/configuration.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index f62fd61f..d34d56f4 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -97,7 +97,7 @@ impl ConfigurationManager { Err(error) => match error { Error::NotFound { .. } => { // If the configuration file does not exist, create one with the configuration - // from the environment variables and default. + // from the environment variables or default values. let multivariate_reserved_memory_in_bytes = env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); @@ -132,14 +132,14 @@ impl ConfigurationManager { Self::validate_configuration(local_data_folder, cluster_mode, configuration) } - _ => Err(ModelarDbServerError::InvalidState(format!( - "Configuration file '{CONFIGURATION_FILE_NAME}' cannot be read." + error => Err(ModelarDbServerError::InvalidState(format!( + "Configuration file '{CONFIGURATION_FILE_NAME}' could not be read: {error}" ))), }, } } - /// Validate the fields in `configuration` and return the [`ConfigurationManager`] if it is + /// Validate the fields in `configuration` and return the [`ConfigurationManager`] if they are /// valid. If the configuration is invalid, return [`ModelarDbServerError`]. fn validate_configuration( local_data_folder: DataFolder, From f6b79ae6ef151021358516500345db6fbe973aad Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:02:41 +0100 Subject: [PATCH 18/31] Add test for ensure invalid configuration files are not accepted --- crates/modelardb_server/src/configuration.rs | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index d34d56f4..c7636d2a 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -413,6 +413,37 @@ mod tests { assert_eq!(existing_configuration, configuration_from_file); } + #[tokio::test] + async fn test_invalid_configuration_file() { + let temp_dir = tempfile::tempdir().unwrap(); + let local_url = temp_dir.path().to_str().unwrap(); + let local_data_folder = DataFolder::open_local_url(local_url).await.unwrap(); + + // Multiple threads per component are not supported. + let invalid_configuration = Configuration { + multivariate_reserved_memory_in_bytes: 1, + uncompressed_reserved_memory_in_bytes: 1, + compressed_reserved_memory_in_bytes: 1, + transfer_batch_size_in_bytes: None, + transfer_time_in_seconds: None, + ingestion_threads: 2, + compression_threads: 2, + writer_threads: 2, + }; + + save_configuration(&local_data_folder, &invalid_configuration) + .await + .unwrap(); + + let result = + ConfigurationManager::try_new(local_data_folder, ClusterMode::SingleNode).await; + + assert_eq!( + result.err().unwrap().to_string(), + "Invalid State Error: Only one thread per component is currently supported.".to_owned() + ); + } + #[tokio::test] async fn test_set_multivariate_reserved_memory_in_bytes() { let temp_dir = tempfile::tempdir().unwrap(); From 3ff5e4f7f8370b8602878c019c60bb49d49c7691 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:08:44 +0100 Subject: [PATCH 19/31] Add test to ensure invalid TOML is not accepted --- crates/modelardb_server/src/configuration.rs | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index c7636d2a..7bbe3f02 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -444,6 +444,34 @@ mod tests { ); } + #[tokio::test] + async fn test_invalid_toml_configuration_file() { + let temp_dir = tempfile::tempdir().unwrap(); + let local_url = temp_dir.path().to_str().unwrap(); + let local_data_folder = DataFolder::open_local_url(local_url).await.unwrap(); + + // Write invalid TOML to the configuration file. + let object_store = local_data_folder.object_store(); + object_store + .put( + &Path::from(CONFIGURATION_FILE_NAME), + PutPayload::from("invalid toml".as_bytes()), + ) + .await + .unwrap(); + + let result = + ConfigurationManager::try_new(local_data_folder, ClusterMode::SingleNode).await; + + assert!( + result + .err() + .unwrap() + .to_string() + .contains("TOML Deserialize Error: TOML parse error at line 1") + ); + } + #[tokio::test] async fn test_set_multivariate_reserved_memory_in_bytes() { let temp_dir = tempfile::tempdir().unwrap(); From cd6125b81cb93484a074b8618cc08c8fcf72465b Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:11:53 +0100 Subject: [PATCH 20/31] Simplify writing to file in test --- crates/modelardb_server/src/configuration.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 7bbe3f02..b82889ca 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -451,14 +451,8 @@ mod tests { let local_data_folder = DataFolder::open_local_url(local_url).await.unwrap(); // Write invalid TOML to the configuration file. - let object_store = local_data_folder.object_store(); - object_store - .put( - &Path::from(CONFIGURATION_FILE_NAME), - PutPayload::from("invalid toml".as_bytes()), - ) - .await - .unwrap(); + let path = temp_dir.path().join(CONFIGURATION_FILE_NAME); + std::fs::write(path, "invalid_toml").unwrap(); let result = ConfigurationManager::try_new(local_data_folder, ClusterMode::SingleNode).await; From 1794fac986a02eb92ad7db804c2c5ded44d8b482 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:13:53 +0100 Subject: [PATCH 21/31] Change test naming for consistency --- crates/modelardb_server/src/configuration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index b82889ca..2ef7a677 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -414,7 +414,7 @@ mod tests { } #[tokio::test] - async fn test_invalid_configuration_file() { + async fn test_invalid_configuration_in_configuration_file() { let temp_dir = tempfile::tempdir().unwrap(); let local_url = temp_dir.path().to_str().unwrap(); let local_data_folder = DataFolder::open_local_url(local_url).await.unwrap(); @@ -445,7 +445,7 @@ mod tests { } #[tokio::test] - async fn test_invalid_toml_configuration_file() { + async fn test_invalid_toml_in_configuration_file() { let temp_dir = tempfile::tempdir().unwrap(); let local_url = temp_dir.path().to_str().unwrap(); let local_data_folder = DataFolder::open_local_url(local_url).await.unwrap(); From e2aa46fed6b61e7da140c5f6341d8c26e6fd717a Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:00:29 +0100 Subject: [PATCH 22/31] Added a default implementation for Configuration struct --- crates/modelardb_server/src/configuration.rs | 27 ++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 2ef7a677..e8ee3bda 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -59,6 +59,21 @@ struct Configuration { pub(crate) writer_threads: u8, } +impl Default for Configuration { + fn default() -> Self { + Self { + multivariate_reserved_memory_in_bytes: 512 * 1024 * 1024, + uncompressed_reserved_memory_in_bytes: 512 * 1024 * 1024, + compressed_reserved_memory_in_bytes: 512 * 1024 * 1024, + transfer_batch_size_in_bytes: Some(64 * 1024 * 1024), + transfer_time_in_seconds: None, + ingestion_threads: 1, + compression_threads: 1, + writer_threads: 1, + } + } +} + /// Manages the system's configuration and provides functionality for updating the configuration. #[derive(Clone)] pub struct ConfigurationManager { @@ -395,9 +410,7 @@ mod tests { compressed_reserved_memory_in_bytes: 1, transfer_batch_size_in_bytes: Some(1), transfer_time_in_seconds: Some(1), - ingestion_threads: 1, - compression_threads: 1, - writer_threads: 1, + ..Default::default() }; save_configuration(&local_data_folder, &existing_configuration) @@ -421,14 +434,8 @@ mod tests { // Multiple threads per component are not supported. let invalid_configuration = Configuration { - multivariate_reserved_memory_in_bytes: 1, - uncompressed_reserved_memory_in_bytes: 1, - compressed_reserved_memory_in_bytes: 1, - transfer_batch_size_in_bytes: None, - transfer_time_in_seconds: None, ingestion_threads: 2, - compression_threads: 2, - writer_threads: 2, + ..Default::default() }; save_configuration(&local_data_folder, &invalid_configuration) From 77924c4f90d76d0db3129e6f018e1c28ac05614b Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:14:00 +0100 Subject: [PATCH 23/31] Update documentation to make it clear env var takes precedence --- crates/modelardb_server/src/configuration.rs | 5 +++-- docs/user/README.md | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index e8ee3bda..c3dd7114 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -90,8 +90,9 @@ pub struct ConfigurationManager { impl ConfigurationManager { /// Create a new [`ConfigurationManager`] using the `modelardb.toml` configuration file in the /// local data folder. If the file does not exist, a configuration file is created with the - /// values from the environment variables or default values. If the configuration file could - /// not be read or created, [`ModelarDbServerError`] is returned. + /// default values. Note that the configuration file and default values are overwritten if the + /// corresponding environment variables are set. If the configuration file could not be read or + /// created, [`ModelarDbServerError`] is returned. pub async fn try_new(local_data_folder: DataFolder, cluster_mode: ClusterMode) -> Result { // Check if there is a configuration file in the local data folder. let object_store = local_data_folder.object_store(); diff --git a/docs/user/README.md b/docs/user/README.md index 84e8e4dc..c32a44e4 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -387,8 +387,13 @@ modelardb_node = modelardb.connect(node) ``` ## ModelarDB configuration +When the server is started for the first time, a configuration file is created in the root of the data folder named +`modelardb.toml`. This file is used to persist updates to the configuration made using the `UpdateConfiguration` +action. If the file is changed manually, the changes are only applied when the server is restarted. + `ModelarDB` can be configured before the server is started using environment variables. A full list of the environment -variables is provided here. If an environment variable is not set, the specified default value will be used. +variables is provided here. If an environment variable is not set, the configuration file will be used. If neither the +environment variable nor the configuration file contains the variable, the specified default value will be used. | **Variable** | **Default** | **Description** | |--------------------------------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -402,10 +407,6 @@ variables is provided here. If an environment variable is not set, the specified | MODELARDBD_TRANSFER_TIME_IN_SECONDS | Disabled | The number of seconds between each transfer of data to the remote object store. | | MODELARDBD_UNCOMPRESSED_DATA_BUFFER_CAPACITY | 65536 | The capacity of each uncompressed data buffer as the number of elements in the buffer where each element is a timestamp and a value. Note that the resulting size of the buffer has to be a multiple of 64 bytes to avoid the actual capacity being larger than the requested due to internal alignment. | -When the server is started for the first time, a configuration file is created in the root of the data folder named -`modelardb.toml`. This file is used to persist updates to the configuration made using the `UpdateConfiguration` -action. If the file is changed manually, the changes are only applied when the server is restarted. - ## Docker Two different [Docker](https://docs.docker.com/) environments are included to make it easy to experiment with the different use cases of ModelarDB. The first environment sets up a single instance of `modelardbd` that only uses local From a93f81c26a9f6d6e1b32f8f32d1da52ae3be7ccf Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:38:34 +0100 Subject: [PATCH 24/31] Update the configuration from env if possible --- crates/modelardb_server/src/configuration.rs | 67 ++++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index c3dd7114..14938dfd 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -59,6 +59,32 @@ struct Configuration { pub(crate) writer_threads: u8, } +impl Configuration { + /// If the corresponding environment variable is set, update the configuration with the value + /// from the environment variable. + fn update_from_env(&mut self) { + if let Ok(value) = env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") { + self.multivariate_reserved_memory_in_bytes = value.parse().unwrap(); + }; + + if let Ok(value) = env::var("MODELARDBD_UNCOMPRESSED_RESERVED_MEMORY_IN_BYTES") { + self.uncompressed_reserved_memory_in_bytes = value.parse().unwrap(); + }; + + if let Ok(value) = env::var("MODELARDBD_COMPRESSED_RESERVED_MEMORY_IN_BYTES") { + self.compressed_reserved_memory_in_bytes = value.parse().unwrap(); + }; + + if let Ok(value) = env::var("MODELARDBD_TRANSFER_BATCH_SIZE_IN_BYTES") { + self.transfer_batch_size_in_bytes = Some(value.parse().unwrap()); + } + + if let Ok(value) = env::var("MODELARDBD_TRANSFER_TIME_IN_SECONDS") { + self.transfer_time_in_seconds = Some(value.parse().unwrap()); + } + } +} + impl Default for Configuration { fn default() -> Self { Self { @@ -102,7 +128,8 @@ impl ConfigurationManager { Ok(conf_file) => { // If the configuration file exists, load the configuration from the file. let bytes = conf_file.bytes().await?; - let configuration_from_file = toml::from_slice::(&bytes)?; + let mut configuration_from_file = toml::from_slice::(&bytes)?; + configuration_from_file.update_from_env(); Self::validate_configuration( local_data_folder, @@ -112,37 +139,9 @@ impl ConfigurationManager { } Err(error) => match error { Error::NotFound { .. } => { - // If the configuration file does not exist, create one with the configuration - // from the environment variables or default values. - let multivariate_reserved_memory_in_bytes = - env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") - .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); - - let uncompressed_reserved_memory_in_bytes = - env::var("MODELARDBD_UNCOMPRESSED_RESERVED_MEMORY_IN_BYTES") - .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); - - let compressed_reserved_memory_in_bytes = - env::var("MODELARDBD_COMPRESSED_RESERVED_MEMORY_IN_BYTES") - .map_or(512 * 1024 * 1024, |value| value.parse().unwrap()); - - let transfer_batch_size_in_bytes = - env::var("MODELARDBD_TRANSFER_BATCH_SIZE_IN_BYTES") - .map_or(Some(64 * 1024 * 1024), |value| Some(value.parse().unwrap())); - - let transfer_time_in_seconds = env::var("MODELARDBD_TRANSFER_TIME_IN_SECONDS") - .map_or(None, |value| Some(value.parse().unwrap())); - - let configuration = Configuration { - multivariate_reserved_memory_in_bytes, - uncompressed_reserved_memory_in_bytes, - compressed_reserved_memory_in_bytes, - transfer_batch_size_in_bytes, - transfer_time_in_seconds, - ingestion_threads: 1, - compression_threads: 1, - writer_threads: 1, - }; + // If the configuration file does not exist, create one with the default values. + let mut configuration = Configuration::default(); + configuration.update_from_env(); save_configuration(&local_data_folder, &configuration).await?; @@ -411,7 +410,7 @@ mod tests { compressed_reserved_memory_in_bytes: 1, transfer_batch_size_in_bytes: Some(1), transfer_time_in_seconds: Some(1), - ..Default::default() + ..Configuration::default() }; save_configuration(&local_data_folder, &existing_configuration) @@ -436,7 +435,7 @@ mod tests { // Multiple threads per component are not supported. let invalid_configuration = Configuration { ingestion_threads: 2, - ..Default::default() + ..Configuration::default() }; save_configuration(&local_data_folder, &invalid_configuration) From 9d50a81dde58814cc108766efe0daeecb22a24f6 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:42:17 +0100 Subject: [PATCH 25/31] Move save_configuration to Configuration struct --- crates/modelardb_server/src/configuration.rs | 65 +++++++++++--------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 14938dfd..135c0658 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -83,6 +83,23 @@ impl Configuration { self.transfer_time_in_seconds = Some(value.parse().unwrap()); } } + + /// Save the configuration to the configuration file at the root of `local_data_folder`. If the + /// file does not exist, it is created. If the configuration file could not be updated or + /// created, return [`ModelarDbServerError`]. + async fn save_to_toml(&self, local_data_folder: &DataFolder) -> Result<()> { + let toml = toml::to_string(self)?; + let object_store = local_data_folder.object_store(); + + object_store + .put( + &Path::from(CONFIGURATION_FILE_NAME), + PutPayload::from(toml.into_bytes()), + ) + .await?; + + Ok(()) + } } impl Default for Configuration { @@ -143,7 +160,7 @@ impl ConfigurationManager { let mut configuration = Configuration::default(); configuration.update_from_env(); - save_configuration(&local_data_folder, &configuration).await?; + configuration.save_to_toml(&local_data_folder).await?; Self::validate_configuration(local_data_folder, cluster_mode, configuration) } @@ -207,7 +224,9 @@ impl ConfigurationManager { self.configuration.multivariate_reserved_memory_in_bytes = new_multivariate_reserved_memory_in_bytes; - save_configuration(&self.local_data_folder, &self.configuration).await + self.configuration + .save_to_toml(&self.local_data_folder) + .await } pub(crate) fn uncompressed_reserved_memory_in_bytes(&self) -> u64 { @@ -237,7 +256,9 @@ impl ConfigurationManager { self.configuration.uncompressed_reserved_memory_in_bytes = new_uncompressed_reserved_memory_in_bytes; - save_configuration(&self.local_data_folder, &self.configuration).await + self.configuration + .save_to_toml(&self.local_data_folder) + .await } pub(crate) fn compressed_reserved_memory_in_bytes(&self) -> u64 { @@ -266,7 +287,9 @@ impl ConfigurationManager { self.configuration.compressed_reserved_memory_in_bytes = new_compressed_reserved_memory_in_bytes; - save_configuration(&self.local_data_folder, &self.configuration).await + self.configuration + .save_to_toml(&self.local_data_folder) + .await } pub(crate) fn transfer_batch_size_in_bytes(&self) -> Option { @@ -289,7 +312,9 @@ impl ConfigurationManager { self.configuration.transfer_batch_size_in_bytes = new_transfer_batch_size_in_bytes; - save_configuration(&self.local_data_folder, &self.configuration).await + self.configuration + .save_to_toml(&self.local_data_folder) + .await } pub(crate) fn transfer_time_in_seconds(&self) -> Option { @@ -312,7 +337,9 @@ impl ConfigurationManager { self.configuration.transfer_time_in_seconds = new_transfer_time_in_seconds; - save_configuration(&self.local_data_folder, &self.configuration).await + self.configuration + .save_to_toml(&self.local_data_folder) + .await } pub(crate) fn ingestion_threads(&self) -> u8 { @@ -351,26 +378,6 @@ impl ConfigurationManager { } } -/// Save `configuration` to the configuration file at the root of `local_data_folder`. If the file -/// does not exist, it is created. If the configuration file could not be updated or created, return -/// [`ModelarDbServerError`]. -async fn save_configuration( - local_data_folder: &DataFolder, - configuration: &Configuration, -) -> Result<()> { - let toml = toml::to_string(configuration)?; - let object_store = local_data_folder.object_store(); - - object_store - .put( - &Path::from(CONFIGURATION_FILE_NAME), - PutPayload::from(toml.into_bytes()), - ) - .await?; - - Ok(()) -} - #[cfg(test)] mod tests { use super::*; @@ -413,7 +420,8 @@ mod tests { ..Configuration::default() }; - save_configuration(&local_data_folder, &existing_configuration) + existing_configuration + .save_to_toml(&local_data_folder) .await .unwrap(); @@ -438,7 +446,8 @@ mod tests { ..Configuration::default() }; - save_configuration(&local_data_folder, &invalid_configuration) + invalid_configuration + .save_to_toml(&local_data_folder) .await .unwrap(); From 210095ad3d6a93b30b20d7cf93441af809963c07 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:50:47 +0100 Subject: [PATCH 26/31] Move validate_configuration to Configuration struct --- crates/modelardb_server/src/configuration.rs | 76 ++++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 135c0658..4611f0e5 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -60,6 +60,23 @@ struct Configuration { } impl Configuration { + /// Validate the fields in the configuration and return [`Ok`] if they are valid. If the + /// configuration is invalid, return [`ModelarDbServerError`]. + fn validate(&self) -> Result<()> { + // TODO: Add support for running multiple threads per component. The individual + // components in the storage engine have not been validated with multiple threads, e.g., + // UncompressedDataManager may have race conditions finishing buffers if multiple + // different data points are added by multiple different clients in parallel. + if self.ingestion_threads != 1 || self.compression_threads != 1 || self.writer_threads != 1 + { + return Err(ModelarDbServerError::InvalidState( + "Only one thread per component is currently supported.".to_string(), + )); + }; + + Ok(()) + } + /// If the corresponding environment variable is set, update the configuration with the value /// from the environment variable. fn update_from_env(&mut self) { @@ -141,61 +158,44 @@ impl ConfigurationManager { let object_store = local_data_folder.object_store(); let maybe_conf_file = object_store.get(&Path::from(CONFIGURATION_FILE_NAME)).await; - match maybe_conf_file { + let configuration = match maybe_conf_file { Ok(conf_file) => { // If the configuration file exists, load the configuration from the file. let bytes = conf_file.bytes().await?; let mut configuration_from_file = toml::from_slice::(&bytes)?; + configuration_from_file.update_from_env(); + configuration_from_file.validate()?; + configuration_from_file + .save_to_toml(&local_data_folder) + .await?; - Self::validate_configuration( - local_data_folder, - cluster_mode, - configuration_from_file, - ) + configuration_from_file } Err(error) => match error { Error::NotFound { .. } => { // If the configuration file does not exist, create one with the default values. let mut configuration = Configuration::default(); - configuration.update_from_env(); + configuration.update_from_env(); + configuration.validate()?; configuration.save_to_toml(&local_data_folder).await?; - Self::validate_configuration(local_data_folder, cluster_mode, configuration) + configuration + } + error => { + return Err(ModelarDbServerError::InvalidState(format!( + "Configuration file '{CONFIGURATION_FILE_NAME}' could not be read: {error}" + ))); } - error => Err(ModelarDbServerError::InvalidState(format!( - "Configuration file '{CONFIGURATION_FILE_NAME}' could not be read: {error}" - ))), }, - } - } + }; - /// Validate the fields in `configuration` and return the [`ConfigurationManager`] if they are - /// valid. If the configuration is invalid, return [`ModelarDbServerError`]. - fn validate_configuration( - local_data_folder: DataFolder, - cluster_mode: ClusterMode, - configuration: Configuration, - ) -> Result { - // TODO: Add support for running multiple threads per component. The individual - // components in the storage engine have not been validated with multiple threads, e.g., - // UncompressedDataManager may have race conditions finishing buffers if multiple - // different data points are added by multiple different clients in parallel. - if configuration.ingestion_threads != 1 - || configuration.compression_threads != 1 - || configuration.writer_threads != 1 - { - Err(ModelarDbServerError::InvalidState( - "Only one thread per component is currently supported.".to_string(), - )) - } else { - Ok(Self { - cluster_mode, - local_data_folder, - configuration, - }) - } + Ok(Self { + cluster_mode, + local_data_folder, + configuration, + }) } pub(crate) fn multivariate_reserved_memory_in_bytes(&self) -> u64 { From 4f09973856329323b5965d0361cbf6478a498438 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:04:22 +0100 Subject: [PATCH 27/31] Always update from env, validate, and save to toml --- crates/modelardb_server/src/configuration.rs | 58 ++++++++------------ 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 4611f0e5..acdbbf57 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -60,23 +60,6 @@ struct Configuration { } impl Configuration { - /// Validate the fields in the configuration and return [`Ok`] if they are valid. If the - /// configuration is invalid, return [`ModelarDbServerError`]. - fn validate(&self) -> Result<()> { - // TODO: Add support for running multiple threads per component. The individual - // components in the storage engine have not been validated with multiple threads, e.g., - // UncompressedDataManager may have race conditions finishing buffers if multiple - // different data points are added by multiple different clients in parallel. - if self.ingestion_threads != 1 || self.compression_threads != 1 || self.writer_threads != 1 - { - return Err(ModelarDbServerError::InvalidState( - "Only one thread per component is currently supported.".to_string(), - )); - }; - - Ok(()) - } - /// If the corresponding environment variable is set, update the configuration with the value /// from the environment variable. fn update_from_env(&mut self) { @@ -101,6 +84,23 @@ impl Configuration { } } + /// Validate the fields in the configuration and return [`Ok`] if they are valid. If the + /// configuration is invalid, return [`ModelarDbServerError`]. + fn validate(&self) -> Result<()> { + // TODO: Add support for running multiple threads per component. The individual + // components in the storage engine have not been validated with multiple threads, e.g., + // UncompressedDataManager may have race conditions finishing buffers if multiple + // different data points are added by multiple different clients in parallel. + if self.ingestion_threads != 1 || self.compression_threads != 1 || self.writer_threads != 1 + { + return Err(ModelarDbServerError::InvalidState( + "Only one thread per component is currently supported.".to_string(), + )); + }; + + Ok(()) + } + /// Save the configuration to the configuration file at the root of `local_data_folder`. If the /// file does not exist, it is created. If the configuration file could not be updated or /// created, return [`ModelarDbServerError`]. @@ -158,30 +158,16 @@ impl ConfigurationManager { let object_store = local_data_folder.object_store(); let maybe_conf_file = object_store.get(&Path::from(CONFIGURATION_FILE_NAME)).await; - let configuration = match maybe_conf_file { + let mut configuration = match maybe_conf_file { Ok(conf_file) => { // If the configuration file exists, load the configuration from the file. let bytes = conf_file.bytes().await?; - let mut configuration_from_file = toml::from_slice::(&bytes)?; - - configuration_from_file.update_from_env(); - configuration_from_file.validate()?; - configuration_from_file - .save_to_toml(&local_data_folder) - .await?; - - configuration_from_file + toml::from_slice::(&bytes)? } Err(error) => match error { Error::NotFound { .. } => { // If the configuration file does not exist, create one with the default values. - let mut configuration = Configuration::default(); - - configuration.update_from_env(); - configuration.validate()?; - configuration.save_to_toml(&local_data_folder).await?; - - configuration + Configuration::default() } error => { return Err(ModelarDbServerError::InvalidState(format!( @@ -191,6 +177,10 @@ impl ConfigurationManager { }, }; + configuration.update_from_env(); + configuration.validate()?; + configuration.save_to_toml(&local_data_folder).await?; + Ok(Self { cluster_mode, local_data_folder, From a03236c11de39124d8563b7d8bd9a54c8c4490f3 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:43:04 +0100 Subject: [PATCH 28/31] Add error handling for when we parse environment variables --- crates/modelardb_server/src/configuration.rs | 19 +++++++++++-------- crates/modelardb_server/src/error.rs | 11 +++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index acdbbf57..14d5aaaf 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -61,27 +61,30 @@ struct Configuration { impl Configuration { /// If the corresponding environment variable is set, update the configuration with the value - /// from the environment variable. - fn update_from_env(&mut self) { + /// from the environment variable. If the value could not be parsed, return + /// [`ModelarDbServerError`]. + fn update_from_env(&mut self) -> Result<()> { if let Ok(value) = env::var("MODELARDBD_MULTIVARIATE_RESERVED_MEMORY_IN_BYTES") { - self.multivariate_reserved_memory_in_bytes = value.parse().unwrap(); + self.multivariate_reserved_memory_in_bytes = value.parse()?; }; if let Ok(value) = env::var("MODELARDBD_UNCOMPRESSED_RESERVED_MEMORY_IN_BYTES") { - self.uncompressed_reserved_memory_in_bytes = value.parse().unwrap(); + self.uncompressed_reserved_memory_in_bytes = value.parse()?; }; if let Ok(value) = env::var("MODELARDBD_COMPRESSED_RESERVED_MEMORY_IN_BYTES") { - self.compressed_reserved_memory_in_bytes = value.parse().unwrap(); + self.compressed_reserved_memory_in_bytes = value.parse()?; }; if let Ok(value) = env::var("MODELARDBD_TRANSFER_BATCH_SIZE_IN_BYTES") { - self.transfer_batch_size_in_bytes = Some(value.parse().unwrap()); + self.transfer_batch_size_in_bytes = Some(value.parse()?); } if let Ok(value) = env::var("MODELARDBD_TRANSFER_TIME_IN_SECONDS") { - self.transfer_time_in_seconds = Some(value.parse().unwrap()); + self.transfer_time_in_seconds = Some(value.parse()?); } + + Ok(()) } /// Validate the fields in the configuration and return [`Ok`] if they are valid. If the @@ -177,7 +180,7 @@ impl ConfigurationManager { }, }; - configuration.update_from_env(); + configuration.update_from_env()?; configuration.validate()?; configuration.save_to_toml(&local_data_folder).await?; diff --git a/crates/modelardb_server/src/error.rs b/crates/modelardb_server/src/error.rs index e0eded77..24ac9366 100644 --- a/crates/modelardb_server/src/error.rs +++ b/crates/modelardb_server/src/error.rs @@ -18,6 +18,7 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use std::io::Error as IoError; +use std::num::ParseIntError; use std::result::Result as StdResult; use crossbeam_channel::RecvError as CrossbeamRecvError; @@ -65,6 +66,8 @@ pub enum ModelarDbServerError { ObjectStore(ObjectStoreError), /// Error returned by ObjectStorePath. ObjectStorePath(ObjectStorePathError), + /// Error returned when failing to parse a string to an integer. + ParseInt(ParseIntError), /// Error returned by Prost when decoding a message that is not valid. ProstDecode(DecodeError), /// Error returned by TOML when deserializing a configuration file. @@ -92,6 +95,7 @@ impl Display for ModelarDbServerError { Self::ModelarDbTypes(reason) => write!(f, "ModelarDB Types Error: {reason}"), Self::ObjectStore(reason) => write!(f, "Object Store Error: {reason}"), Self::ObjectStorePath(reason) => write!(f, "Object Store Path Error: {reason}"), + Self::ParseInt(reason) => write!(f, "Parse Int Error: {reason}"), Self::ProstDecode(reason) => write!(f, "Prost Decode Error: {reason}"), Self::TomlDeserialize(reason) => write!(f, "TOML Deserialize Error: {reason}"), Self::TomlSerialize(reason) => write!(f, "TOML Serialize Error: {reason}"), @@ -117,6 +121,7 @@ impl Error for ModelarDbServerError { Self::ModelarDbTypes(reason) => Some(reason), Self::ObjectStore(reason) => Some(reason), Self::ObjectStorePath(reason) => Some(reason), + Self::ParseInt(reason) => Some(reason), Self::ProstDecode(reason) => Some(reason), Self::TomlDeserialize(reason) => Some(reason), Self::TomlSerialize(reason) => Some(reason), @@ -186,6 +191,12 @@ impl From for ModelarDbServerError { } } +impl From for ModelarDbServerError { + fn from(error: ParseIntError) -> Self { + Self::ParseInt(error) + } +} + impl From for ModelarDbServerError { fn from(error: DecodeError) -> Self { Self::ProstDecode(error) From be10dd1a0d444465efbc4a86495b765159fc4327 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:29:21 +0100 Subject: [PATCH 29/31] Change modelardb.toml to modelardbd.toml --- crates/modelardb_server/src/configuration.rs | 6 +++--- crates/modelardb_server/src/remote.rs | 2 +- docs/user/README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index 14d5aaaf..c01060f6 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -31,9 +31,9 @@ use crate::ClusterMode; use crate::error::{ModelarDbServerError, Result}; use crate::storage::StorageEngine; -const CONFIGURATION_FILE_NAME: &str = "modelardb.toml"; +const CONFIGURATION_FILE_NAME: &str = "modelardbd.toml"; -/// The system's configuration. The configuration can be serialized into a `modelardb.toml` +/// The system's configuration. The configuration can be serialized into a `modelardbd.toml` /// configuration file and deserialized from it. Accessing and modifying the configuration should /// only be done through the [`ConfigurationManager`]. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -151,7 +151,7 @@ pub struct ConfigurationManager { } impl ConfigurationManager { - /// Create a new [`ConfigurationManager`] using the `modelardb.toml` configuration file in the + /// Create a new [`ConfigurationManager`] using the `modelardbd.toml` configuration file in the /// local data folder. If the file does not exist, a configuration file is created with the /// default values. Note that the configuration file and default values are overwritten if the /// corresponding environment variables are set. If the configuration file could not be read or diff --git a/crates/modelardb_server/src/remote.rs b/crates/modelardb_server/src/remote.rs index 932497d4..76a5fa6a 100644 --- a/crates/modelardb_server/src/remote.rs +++ b/crates/modelardb_server/src/remote.rs @@ -641,7 +641,7 @@ impl FlightService for FlightServiceHandler { /// * `UpdateConfiguration`: Update a single setting in the configuration. The setting to update /// and the new value are provided in the [`UpdateConfiguration`](protocol::UpdateConfiguration) /// protobuf message in the action body. The setting is updated in the live server configuration - /// and the change is persisted in the `modelardb.toml` configuration file. + /// and the change is persisted in the `modelardbd.toml` configuration file. /// * `NodeType`: Get the type of the node. The type is always `server`. The type of the node /// is returned as a string. async fn do_action( diff --git a/docs/user/README.md b/docs/user/README.md index c32a44e4..7abf0676 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -388,7 +388,7 @@ modelardb_node = modelardb.connect(node) ## ModelarDB configuration When the server is started for the first time, a configuration file is created in the root of the data folder named -`modelardb.toml`. This file is used to persist updates to the configuration made using the `UpdateConfiguration` +`modelardbd.toml`. This file is used to persist updates to the configuration made using the `UpdateConfiguration` action. If the file is changed manually, the changes are only applied when the server is restarted. `ModelarDB` can be configured before the server is started using environment variables. A full list of the environment From 5d9529ae3cc7226ff107da67701bb862d33df8ae Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:33:23 +0100 Subject: [PATCH 30/31] Add method for getting the cluster mode instead of the field being public --- crates/modelardb_server/src/configuration.rs | 6 +++++- crates/modelardb_server/src/remote.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index c01060f6..e60081a8 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -142,7 +142,7 @@ impl Default for Configuration { pub struct ConfigurationManager { /// The mode of the cluster used to determine the behaviour when starting the server, /// creating tables, updating the remote object store, and querying. - pub(crate) cluster_mode: ClusterMode, + cluster_mode: ClusterMode, /// The local data folder that stores the configuration file at the root. local_data_folder: DataFolder, /// The configuration of the system. This is stored in a separate type to allow for easier @@ -191,6 +191,10 @@ impl ConfigurationManager { }) } + pub(crate) fn cluster_mode(&self) -> &ClusterMode { + &self.cluster_mode + } + pub(crate) fn multivariate_reserved_memory_in_bytes(&self) -> u64 { self.configuration.multivariate_reserved_memory_in_bytes } diff --git a/crates/modelardb_server/src/remote.rs b/crates/modelardb_server/src/remote.rs index 76a5fa6a..24096ec1 100644 --- a/crates/modelardb_server/src/remote.rs +++ b/crates/modelardb_server/src/remote.rs @@ -349,7 +349,7 @@ impl FlightServiceHandler { async fn validate_request(&self, request_metadata: &MetadataMap) -> StdResult<(), Status> { let configuration_manager = self.context.configuration_manager.read().await; - if let ClusterMode::MultiNode(manager) = &configuration_manager.cluster_mode { + if let ClusterMode::MultiNode(manager) = configuration_manager.cluster_mode() { manager .validate_request(request_metadata) .map_err(|error| Status::unauthenticated(error.to_string())) From c49709214d4120a233d1996832c1860c3ce0a6e8 Mon Sep 17 00:00:00 2001 From: CGodiksen <36046286+CGodiksen@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:39:33 +0100 Subject: [PATCH 31/31] Update based on comments from @skejserjensen --- crates/modelardb_server/src/configuration.rs | 12 ++++++------ crates/modelardb_server/src/remote.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/modelardb_server/src/configuration.rs b/crates/modelardb_server/src/configuration.rs index e60081a8..812ec174 100644 --- a/crates/modelardb_server/src/configuration.rs +++ b/crates/modelardb_server/src/configuration.rs @@ -33,7 +33,7 @@ use crate::storage::StorageEngine; const CONFIGURATION_FILE_NAME: &str = "modelardbd.toml"; -/// The system's configuration. The configuration can be serialized into a `modelardbd.toml` +/// The system's configuration. The configuration can be serialized into a [`CONFIGURATION_FILE_NAME`] /// configuration file and deserialized from it. Accessing and modifying the configuration should /// only be done through the [`ConfigurationManager`]. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -151,11 +151,11 @@ pub struct ConfigurationManager { } impl ConfigurationManager { - /// Create a new [`ConfigurationManager`] using the `modelardbd.toml` configuration file in the - /// local data folder. If the file does not exist, a configuration file is created with the - /// default values. Note that the configuration file and default values are overwritten if the - /// corresponding environment variables are set. If the configuration file could not be read or - /// created, [`ModelarDbServerError`] is returned. + /// Create a new [`ConfigurationManager`] using the [`CONFIGURATION_FILE_NAME`] configuration + /// file in the local data folder. If the file does not exist, a configuration file is created + /// with the default values. Note that the configuration file and default values are overwritten + /// if the corresponding environment variables are set. If the configuration file could not be + /// read or created, [`ModelarDbServerError`] is returned. pub async fn try_new(local_data_folder: DataFolder, cluster_mode: ClusterMode) -> Result { // Check if there is a configuration file in the local data folder. let object_store = local_data_folder.object_store(); diff --git a/crates/modelardb_server/src/remote.rs b/crates/modelardb_server/src/remote.rs index 24096ec1..5f344c71 100644 --- a/crates/modelardb_server/src/remote.rs +++ b/crates/modelardb_server/src/remote.rs @@ -641,7 +641,7 @@ impl FlightService for FlightServiceHandler { /// * `UpdateConfiguration`: Update a single setting in the configuration. The setting to update /// and the new value are provided in the [`UpdateConfiguration`](protocol::UpdateConfiguration) /// protobuf message in the action body. The setting is updated in the live server configuration - /// and the change is persisted in the `modelardbd.toml` configuration file. + /// and the change is persisted in the configuration file. /// * `NodeType`: Get the type of the node. The type is always `server`. The type of the node /// is returned as a string. async fn do_action(