1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(tag = "type")]
14pub enum StorageConfig {
15 InMemory,
16 SlateDb(SlateDbStorageConfig),
17}
18
19impl Default for StorageConfig {
20 fn default() -> Self {
21 StorageConfig::SlateDb(SlateDbStorageConfig {
22 path: "data".to_string(),
23 object_store: ObjectStoreConfig::Local(LocalObjectStoreConfig {
24 path: ".data".to_string(),
25 }),
26 settings_path: None,
27 block_cache: None,
28 })
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct SlateDbStorageConfig {
35 pub path: String,
37
38 pub object_store: ObjectStoreConfig,
40
41 #[serde(skip_serializing_if = "Option::is_none")]
47 pub settings_path: Option<String>,
48
49 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub block_cache: Option<BlockCacheConfig>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
59#[serde(tag = "type")]
60pub enum BlockCacheConfig {
61 FoyerHybrid(FoyerHybridCacheConfig),
63}
64
65#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
69pub enum FoyerWritePolicy {
70 #[default]
73 WriteOnInsertion,
74 WriteOnEviction,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub struct FoyerHybridCacheConfig {
82 pub memory_capacity: u64,
84 pub disk_capacity: u64,
86 pub disk_path: String,
88 #[serde(default)]
90 pub write_policy: FoyerWritePolicy,
91 #[serde(default = "default_flushers")]
93 pub flushers: usize,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub buffer_pool_size: Option<u64>,
99 #[serde(default = "default_submit_queue_size_threshold")]
102 pub submit_queue_size_threshold: u64,
103}
104
105fn default_flushers() -> usize {
106 4
107}
108
109fn default_submit_queue_size_threshold() -> u64 {
110 1024 * 1024 * 1024 }
112
113impl FoyerHybridCacheConfig {
114 pub fn effective_buffer_pool_size(&self) -> u64 {
117 self.buffer_pool_size.unwrap_or(self.memory_capacity / 32)
118 }
119}
120
121impl Default for SlateDbStorageConfig {
122 fn default() -> Self {
123 Self {
124 path: "data".to_string(),
125 object_store: ObjectStoreConfig::default(),
126 settings_path: None,
127 block_cache: None,
128 }
129 }
130}
131
132impl StorageConfig {
133 pub fn with_path_suffix(&self, suffix: &str) -> Self {
138 match self {
139 StorageConfig::InMemory => StorageConfig::InMemory,
140 StorageConfig::SlateDb(config) => StorageConfig::SlateDb(SlateDbStorageConfig {
141 path: format!("{}/{}", config.path, suffix),
142 object_store: config.object_store.clone(),
143 settings_path: config.settings_path.clone(),
144 block_cache: config.block_cache.clone(),
145 }),
146 }
147 }
148}
149
150#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
152#[serde(tag = "type")]
153pub enum ObjectStoreConfig {
154 #[default]
156 InMemory,
157
158 Aws(AwsObjectStoreConfig),
160
161 Local(LocalObjectStoreConfig),
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
167pub struct AwsObjectStoreConfig {
168 pub region: String,
170
171 pub bucket: String,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177pub struct LocalObjectStoreConfig {
178 pub path: String,
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn should_default_to_slatedb_with_local_data_dir() {
188 let config = StorageConfig::default();
190
191 match config {
193 StorageConfig::SlateDb(slate_config) => {
194 assert_eq!(slate_config.path, "data");
195 assert_eq!(
196 slate_config.object_store,
197 ObjectStoreConfig::Local(LocalObjectStoreConfig {
198 path: ".data".to_string()
199 })
200 );
201 }
202 _ => panic!("Expected SlateDb config as default"),
203 }
204 }
205
206 #[test]
207 fn should_deserialize_in_memory_config() {
208 let yaml = r#"type: InMemory"#;
210
211 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
213
214 assert_eq!(config, StorageConfig::InMemory);
216 }
217
218 #[test]
219 fn should_deserialize_slatedb_config_with_local_object_store() {
220 let yaml = r#"
222type: SlateDb
223path: my-data
224object_store:
225 type: Local
226 path: /tmp/slatedb
227"#;
228
229 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
231
232 match config {
234 StorageConfig::SlateDb(slate_config) => {
235 assert_eq!(slate_config.path, "my-data");
236 assert_eq!(
237 slate_config.object_store,
238 ObjectStoreConfig::Local(LocalObjectStoreConfig {
239 path: "/tmp/slatedb".to_string()
240 })
241 );
242 assert!(slate_config.settings_path.is_none());
243 }
244 _ => panic!("Expected SlateDb config"),
245 }
246 }
247
248 #[test]
249 fn should_deserialize_slatedb_config_with_aws_object_store() {
250 let yaml = r#"
252type: SlateDb
253path: my-data
254object_store:
255 type: Aws
256 region: us-west-2
257 bucket: my-bucket
258settings_path: slatedb.toml
259"#;
260
261 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
263
264 match config {
266 StorageConfig::SlateDb(slate_config) => {
267 assert_eq!(slate_config.path, "my-data");
268 assert_eq!(
269 slate_config.object_store,
270 ObjectStoreConfig::Aws(AwsObjectStoreConfig {
271 region: "us-west-2".to_string(),
272 bucket: "my-bucket".to_string()
273 })
274 );
275 assert_eq!(slate_config.settings_path, Some("slatedb.toml".to_string()));
276 }
277 _ => panic!("Expected SlateDb config"),
278 }
279 }
280
281 #[test]
282 fn should_deserialize_slatedb_config_with_in_memory_object_store() {
283 let yaml = r#"
285type: SlateDb
286path: test-data
287object_store:
288 type: InMemory
289"#;
290
291 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
293
294 match config {
296 StorageConfig::SlateDb(slate_config) => {
297 assert_eq!(slate_config.path, "test-data");
298 assert_eq!(slate_config.object_store, ObjectStoreConfig::InMemory);
299 }
300 _ => panic!("Expected SlateDb config"),
301 }
302 }
303
304 #[test]
305 fn should_serialize_slatedb_config() {
306 let config = StorageConfig::SlateDb(SlateDbStorageConfig {
308 path: "my-data".to_string(),
309 object_store: ObjectStoreConfig::Local(LocalObjectStoreConfig {
310 path: "/tmp/slatedb".to_string(),
311 }),
312 settings_path: None,
313 block_cache: None,
314 });
315
316 let yaml = serde_yaml::to_string(&config).unwrap();
318
319 assert!(yaml.contains("type: SlateDb"));
321 assert!(yaml.contains("path: my-data"));
322 assert!(yaml.contains("type: Local"));
323 assert!(!yaml.contains("settings_path"));
325 assert!(!yaml.contains("block_cache"));
326 }
327
328 #[test]
329 fn should_deserialize_block_cache_config() {
330 let yaml = r#"
331type: SlateDb
332path: data
333object_store:
334 type: InMemory
335block_cache:
336 type: FoyerHybrid
337 memory_capacity: 8589934592
338 disk_capacity: 150323855360
339 disk_path: /mnt/nvme/block-cache
340"#;
341 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
342 match config {
343 StorageConfig::SlateDb(slate_config) => {
344 let cache = slate_config.block_cache.expect("block_cache should be set");
345 match cache {
346 BlockCacheConfig::FoyerHybrid(foyer) => {
347 assert_eq!(foyer.memory_capacity, 8589934592);
348 assert_eq!(foyer.disk_capacity, 150323855360);
349 assert_eq!(foyer.disk_path, "/mnt/nvme/block-cache");
350 assert_eq!(foyer.write_policy, FoyerWritePolicy::WriteOnInsertion);
352 assert_eq!(foyer.flushers, 4);
353 assert!(foyer.buffer_pool_size.is_none());
354 assert_eq!(foyer.submit_queue_size_threshold, 1024 * 1024 * 1024);
355 assert_eq!(foyer.effective_buffer_pool_size(), 8589934592 / 32);
357 }
358 }
359 }
360 _ => panic!("Expected SlateDb config"),
361 }
362 }
363
364 #[test]
365 fn should_deserialize_block_cache_with_explicit_engine_options() {
366 let yaml = r#"
368type: SlateDb
369path: data
370object_store:
371 type: InMemory
372block_cache:
373 type: FoyerHybrid
374 memory_capacity: 4294967296
375 disk_capacity: 10737418240
376 disk_path: /mnt/nvme/cache
377 write_policy: WriteOnEviction
378 flushers: 2
379 buffer_pool_size: 134217728
380 submit_queue_size_threshold: 536870912
381"#;
382
383 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
385
386 match config {
388 StorageConfig::SlateDb(slate_config) => {
389 let cache = slate_config.block_cache.expect("block_cache should be set");
390 match cache {
391 BlockCacheConfig::FoyerHybrid(foyer) => {
392 assert_eq!(foyer.write_policy, FoyerWritePolicy::WriteOnEviction);
393 assert_eq!(foyer.flushers, 2);
394 assert_eq!(foyer.buffer_pool_size, Some(134217728));
395 assert_eq!(foyer.submit_queue_size_threshold, 536870912);
396 assert_eq!(foyer.effective_buffer_pool_size(), 134217728);
398 }
399 }
400 }
401 _ => panic!("Expected SlateDb config"),
402 }
403 }
404
405 #[test]
406 fn should_derive_buffer_pool_size_from_memory_capacity() {
407 let config = FoyerHybridCacheConfig {
409 memory_capacity: 8 * 1024 * 1024 * 1024, disk_capacity: 100 * 1024 * 1024 * 1024,
411 disk_path: "/tmp/cache".to_string(),
412 write_policy: FoyerWritePolicy::default(),
413 flushers: 4,
414 buffer_pool_size: None,
415 submit_queue_size_threshold: 1024 * 1024 * 1024,
416 };
417
418 assert_eq!(
420 config.effective_buffer_pool_size(),
421 256 * 1024 * 1024 );
423 }
424
425 #[test]
426 fn should_default_block_cache_to_none() {
427 let yaml = r#"
428type: SlateDb
429path: data
430object_store:
431 type: InMemory
432"#;
433 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
434 match config {
435 StorageConfig::SlateDb(slate_config) => {
436 assert!(slate_config.block_cache.is_none());
437 }
438 _ => panic!("Expected SlateDb config"),
439 }
440 }
441}