1use std::num::NonZeroUsize;
20use std::time::Duration;
21
22use serde::{Deserialize, Serialize};
24
25pub(super) const FLUSH_INTERVAL: Duration = Duration::from_millis(100);
31pub(super) const MAX_BATCH_BYTES: usize = 16 * 1024 * 1024;
32pub(super) const TARGET_BATCH_BYTES: usize = 1024 * 1024;
33pub(super) const OBJECT_PRUNE_ENTRIES: NonZeroUsize = NonZeroUsize::new(256).unwrap();
34pub(super) const SNAPSHOT_BYTES: usize = 1024 * 1024;
35
36pub(super) const IO_TIMEOUT: Duration = Duration::from_secs(30);
38pub(super) const RECOVERY_TIMEOUT: Duration = Duration::from_secs(300);
39pub(super) const MAX_RETRIES: usize = 3;
40pub(super) const RETRY_BASE: Duration = Duration::from_millis(10);
41pub(super) const RETRY_CAP: Duration = Duration::from_secs(1);
42
43pub(super) const MAX_FILES: NonZeroUsize = NonZeroUsize::new(256).unwrap();
45pub(super) const FILE_SESSION_TIMEOUT: Duration = Duration::from_secs(60);
46pub(super) const MAX_MUTATIONS: NonZeroUsize = NonZeroUsize::new(1024).unwrap();
47pub(super) const MUTATION_RETENTION: Duration = Duration::from_secs(300);
48pub(super) const SCAN_ROWS: NonZeroUsize = NonZeroUsize::new(16_384).unwrap();
53pub(super) const SCAN_BYTES: usize = 8 * 1024 * 1024;
54
55#[derive(Clone, Debug, Serialize, Deserialize)]
62#[serde(deny_unknown_fields)]
63pub struct StorageSettings {
64 pub request: StorageRequestSettings,
65 pub response: StorageResponseSettings,
66 pub filesystem: StorageFilesystemSettings,
67}
68
69#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
70#[serde(deny_unknown_fields)]
71pub struct StorageFilesystemSettings {
72 pub poll_interval: Duration,
73 pub max_entries: NonZeroUsize,
74 pub event_capacity: NonZeroUsize,
75}
76
77#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
78#[serde(deny_unknown_fields)]
79pub struct StorageRequestSettings {
80 pub max_pending_queries: NonZeroUsize,
81 pub max_active_queries: NonZeroUsize,
82 pub max_pending_write_batches: NonZeroUsize,
83 pub max_active_file_operations: NonZeroUsize,
84 pub max_pending_write_bytes: NonZeroUsize,
85}
86
87#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
88#[serde(deny_unknown_fields)]
89pub struct StorageResponseSettings {
90 pub batch_rows: NonZeroUsize,
91 pub batch_bytes: NonZeroUsize,
92}
93
94#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
95#[serde(deny_unknown_fields)]
96pub struct StorageAccumulatorSettings {
97 pub memory_bytes: usize,
99}
100
101impl Default for StorageAccumulatorSettings {
108 fn default() -> Self {
109 Self { memory_bytes: 64 * 1024 }
110 }
111}
112
113impl StorageSettings {
114 pub fn validate(&self) -> crate::Result<()> {
115 if self.filesystem.poll_interval.is_zero() || self.filesystem.event_capacity.get() > tokio::sync::Semaphore::MAX_PERMITS {
116 return Err(crate::Error::InvalidInput {
117 field: "storage filesystem",
118 reason: "poll interval must be positive and event capacity bounded",
119 });
120 }
121
122 if self.request.max_active_queries.get() > tokio::sync::Semaphore::MAX_PERMITS
123 || self.request.max_pending_queries.get() > tokio::sync::Semaphore::MAX_PERMITS
124 {
125 return Err(crate::Error::InvalidInput {
126 field: "storage query admission",
127 reason: "query counts exceed semaphore capacity",
128 });
129 }
130 Ok(())
131 }
132
133 pub fn max_batch_bytes(&self) -> NonZeroUsize {
135 self.request.max_pending_write_bytes.min(NonZeroUsize::new(MAX_BATCH_BYTES).unwrap())
136 }
137
138 pub(super) fn target_batch_bytes(&self) -> usize {
139 TARGET_BATCH_BYTES.min(self.max_batch_bytes().get())
140 }
141
142 pub(super) fn scan_bytes(&self) -> NonZeroUsize {
143 NonZeroUsize::new(SCAN_BYTES).unwrap()
144 }
145}
146
147#[cfg(test)]
154mod tests {
155 use super::{StorageRequestSettings, StorageResponseSettings, StorageSettings};
156 use std::num::NonZeroUsize;
157
158 fn settings() -> StorageSettings {
159 StorageSettings {
160 filesystem: crate::config::StorageFilesystemSettings {
161 poll_interval: std::time::Duration::from_millis(100),
162 max_entries: std::num::NonZeroUsize::new(100_000).unwrap(),
163 event_capacity: std::num::NonZeroUsize::new(1024).unwrap(),
164 },
165
166 request: StorageRequestSettings {
167 max_pending_queries: NonZeroUsize::new(64).unwrap(),
168 max_active_queries: NonZeroUsize::new(4).unwrap(),
169 max_pending_write_batches: NonZeroUsize::new(256).unwrap(),
170 max_active_file_operations: NonZeroUsize::MIN,
171 max_pending_write_bytes: NonZeroUsize::new(16 * 1024 * 1024).unwrap(),
172 },
173 response: StorageResponseSettings {
174 batch_rows: NonZeroUsize::new(64).unwrap(),
175 batch_bytes: NonZeroUsize::new(64 * 1024).unwrap(),
176 },
177 }
178 }
179
180 #[test]
181 fn query_counts_bounded() {
182 let mut settings = settings();
183 settings.request.max_active_queries = NonZeroUsize::new(tokio::sync::Semaphore::MAX_PERMITS + 1).unwrap();
184 assert!(settings.validate().is_err());
185
186 settings.request.max_active_queries = NonZeroUsize::MIN;
187 settings.request.max_pending_queries = NonZeroUsize::MIN;
188 settings.validate().unwrap();
189 }
190}