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 = 128 * 1024;
33pub(super) const WRITE_BATCH_ENTRIES: NonZeroUsize = NonZeroUsize::new(4096).unwrap();
34pub(super) const OBJECT_PRUNE_ENTRIES: NonZeroUsize = NonZeroUsize::new(256).unwrap();
35pub(super) const SNAPSHOT_BYTES: usize = 1024 * 1024;
36
37pub(super) const IO_TIMEOUT: Duration = Duration::from_secs(30);
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);
48const MUTATION_BUDGET_SHARE: usize = 8;
50
51pub(super) const SCAN_ROWS: NonZeroUsize = NonZeroUsize::new(1024).unwrap();
53pub(super) const SCAN_BYTES: usize = 1024 * 1024;
54
55#[derive(Clone, Debug, Serialize, Deserialize)]
62#[serde(deny_unknown_fields)]
63pub struct StorageSettings {
64 pub budget: StorageBudget,
65 pub request: StorageRequestSettings,
66 pub response: StorageResponseSettings,
67}
68
69#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
70#[serde(deny_unknown_fields)]
71pub struct StorageBudget {
72 pub read_memory_bytes: NonZeroUsize,
73}
74
75#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
76#[serde(deny_unknown_fields)]
77pub struct StorageRequestSettings {
78 pub max_pending_queries: NonZeroUsize,
79 pub max_active_queries: NonZeroUsize,
80 pub max_pending_write_batches: NonZeroUsize,
81 pub max_pending_write_bytes: NonZeroUsize,
82}
83
84#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct StorageResponseSettings {
87 pub batch_rows: NonZeroUsize,
88 pub batch_bytes: NonZeroUsize,
89}
90
91impl StorageSettings {
98 pub fn validate(&self) -> crate::Result<()> {
99 if self.request.max_active_queries.get() > tokio::sync::Semaphore::MAX_PERMITS
100 || self.request.max_pending_queries.get() > tokio::sync::Semaphore::MAX_PERMITS
101 {
102 return Err(crate::Error::InvalidInput { field: "storage query admission", reason: "query counts exceed semaphore capacity" });
103 }
104 if self.response.batch_bytes.get() > self.budget.read_memory_bytes.get() / 2 {
105 return Err(crate::Error::InvalidInput {
106 field: "storage response",
107 reason: "response batches cannot exceed half the shared read budget",
108 });
109 }
110 Ok(())
111 }
112
113 pub fn max_batch_bytes(&self) -> NonZeroUsize {
115 self.request.max_pending_write_bytes.min(NonZeroUsize::new(MAX_BATCH_BYTES).unwrap())
116 }
117
118 pub(super) fn target_batch_bytes(&self) -> usize {
119 TARGET_BATCH_BYTES.min(self.max_batch_bytes().get())
120 }
121
122 pub(super) fn mutation_bytes(&self) -> NonZeroUsize {
124 NonZeroUsize::new(self.budget.read_memory_bytes.get() / MUTATION_BUDGET_SHARE).unwrap_or(NonZeroUsize::MIN)
125 }
126
127 pub(super) fn scan_bytes(&self) -> NonZeroUsize {
129 let shared = self.budget.read_memory_bytes.get() / self.request.max_active_queries.get().saturating_mul(4).max(1);
130 NonZeroUsize::new(SCAN_BYTES.min(shared).max(1)).unwrap()
131 }
132}
133
134#[cfg(test)]
141mod tests {
142 use super::{StorageBudget, StorageRequestSettings, StorageResponseSettings, StorageSettings};
143 use std::num::NonZeroUsize;
144
145 fn settings() -> StorageSettings {
146 StorageSettings {
147 budget: StorageBudget { read_memory_bytes: NonZeroUsize::new(16 * 1024 * 1024).unwrap() },
148 request: StorageRequestSettings {
149 max_pending_queries: NonZeroUsize::new(64).unwrap(),
150 max_active_queries: NonZeroUsize::new(4).unwrap(),
151 max_pending_write_batches: NonZeroUsize::new(256).unwrap(),
152 max_pending_write_bytes: NonZeroUsize::new(16 * 1024 * 1024).unwrap(),
153 },
154 response: StorageResponseSettings {
155 batch_rows: NonZeroUsize::new(64).unwrap(),
156 batch_bytes: NonZeroUsize::new(64 * 1024).unwrap(),
157 },
158 }
159 }
160
161 #[test]
162 fn response_fits_budget() {
163 let mut settings = settings();
164 settings.validate().unwrap();
165
166 settings.response.batch_bytes = NonZeroUsize::new(settings.budget.read_memory_bytes.get() + 1).unwrap();
167 assert!(settings.validate().is_err());
168 settings.response.batch_bytes = settings.budget.read_memory_bytes;
169 assert!(settings.validate().is_err());
170
171 settings.response.batch_bytes = NonZeroUsize::new(settings.budget.read_memory_bytes.get() / 2).unwrap();
172 settings.validate().unwrap();
173 }
174
175 #[test]
176 fn query_counts_bounded() {
177 let mut settings = settings();
178 settings.request.max_active_queries = NonZeroUsize::new(tokio::sync::Semaphore::MAX_PERMITS + 1).unwrap();
179 assert!(settings.validate().is_err());
180
181 settings.request.max_active_queries = NonZeroUsize::MIN;
182 settings.request.max_pending_queries = NonZeroUsize::MIN;
183 settings.validate().unwrap();
184 }
185}