1use std::fs;
8use std::io;
9use std::path::{Path, PathBuf};
10
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "kebab-case")]
16pub enum StorageLayout {
17 Minimal,
19 Standard,
21 Performance,
23 Max,
25}
26
27impl Default for StorageLayout {
28 fn default() -> Self {
29 Self::Standard
30 }
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(default)]
36pub struct LayoutOverrides {
37 pub dedicated_wal_dir: Option<bool>,
38 pub dedicated_index_dir: Option<bool>,
39 pub dedicated_cache_dir: Option<bool>,
40 pub dedicated_snapshot_dir: Option<bool>,
41 pub dedicated_blob_dir: Option<bool>,
42 pub dedicated_temp_dir: Option<bool>,
43 pub dedicated_metrics_dir: Option<bool>,
44 #[serde(default)]
46 pub logs: LogRoutingOverrides,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(rename_all = "kebab-case", tag = "kind", content = "path")]
59pub enum LogDestination {
60 Stderr,
61 File(PathBuf),
62 Syslog,
63}
64
65impl LogDestination {
66 pub fn describe(&self) -> String {
68 match self {
69 Self::Stderr => "stderr".to_string(),
70 Self::Syslog => "syslog".to_string(),
71 Self::File(path) => format!("file:{}", path.display()),
72 }
73 }
74
75 pub fn file_path(&self) -> Option<&Path> {
77 match self {
78 Self::File(path) => Some(path.as_path()),
79 _ => None,
80 }
81 }
82}
83
84#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
86#[serde(default)]
87pub struct LogRoutingOverrides {
88 pub audit_log: Option<LogDestination>,
89 pub slow_log: Option<LogDestination>,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94pub struct LayoutToggles {
95 pub dedicated_wal_dir: bool,
96 pub dedicated_index_dir: bool,
97 pub dedicated_cache_dir: bool,
98 pub dedicated_snapshot_dir: bool,
99 pub dedicated_blob_dir: bool,
100 pub dedicated_temp_dir: bool,
101 pub dedicated_metrics_dir: bool,
102}
103
104impl StorageLayout {
105 pub fn default_audit_log_in(self, support_dir: &Path) -> LogDestination {
110 match self {
111 Self::Performance | Self::Max => {
112 LogDestination::File(support_dir.join("logs").join("audit.log"))
113 }
114 Self::Minimal | Self::Standard => LogDestination::Stderr,
115 }
116 }
117
118 pub fn default_slow_log_in(self, support_dir: &Path) -> LogDestination {
121 match self {
122 Self::Performance | Self::Max => {
123 LogDestination::File(support_dir.join("logs").join("slow.log"))
124 }
125 Self::Minimal | Self::Standard => LogDestination::Stderr,
126 }
127 }
128
129 pub fn expand(self, overrides: &LayoutOverrides) -> LayoutToggles {
130 let mut toggles = match self {
131 Self::Minimal => LayoutToggles {
132 dedicated_wal_dir: false,
133 dedicated_index_dir: false,
134 dedicated_cache_dir: false,
135 dedicated_snapshot_dir: false,
136 dedicated_blob_dir: false,
137 dedicated_temp_dir: false,
138 dedicated_metrics_dir: false,
139 },
140 Self::Standard => LayoutToggles {
141 dedicated_wal_dir: false,
142 dedicated_index_dir: true,
143 dedicated_cache_dir: false,
144 dedicated_snapshot_dir: true,
145 dedicated_blob_dir: false,
146 dedicated_temp_dir: false,
147 dedicated_metrics_dir: false,
148 },
149 Self::Performance => LayoutToggles {
150 dedicated_wal_dir: true,
151 dedicated_index_dir: true,
152 dedicated_cache_dir: true,
153 dedicated_snapshot_dir: true,
154 dedicated_blob_dir: true,
155 dedicated_temp_dir: false,
156 dedicated_metrics_dir: false,
157 },
158 Self::Max => LayoutToggles {
159 dedicated_wal_dir: true,
160 dedicated_index_dir: true,
161 dedicated_cache_dir: true,
162 dedicated_snapshot_dir: true,
163 dedicated_blob_dir: true,
164 dedicated_temp_dir: true,
165 dedicated_metrics_dir: true,
166 },
167 };
168
169 if let Some(value) = overrides.dedicated_wal_dir {
170 toggles.dedicated_wal_dir = value;
171 }
172 if let Some(value) = overrides.dedicated_index_dir {
173 toggles.dedicated_index_dir = value;
174 }
175 if let Some(value) = overrides.dedicated_cache_dir {
176 toggles.dedicated_cache_dir = value;
177 }
178 if let Some(value) = overrides.dedicated_snapshot_dir {
179 toggles.dedicated_snapshot_dir = value;
180 }
181 if let Some(value) = overrides.dedicated_blob_dir {
182 toggles.dedicated_blob_dir = value;
183 }
184 if let Some(value) = overrides.dedicated_temp_dir {
185 toggles.dedicated_temp_dir = value;
186 }
187 if let Some(value) = overrides.dedicated_metrics_dir {
188 toggles.dedicated_metrics_dir = value;
189 }
190
191 toggles
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct TieredLayoutPaths {
198 pub data_file: PathBuf,
199 pub support_dir: PathBuf,
200 pub wal_file: PathBuf,
201 pub logical_wal_file: PathBuf,
202 pub temp_file: PathBuf,
203 pub snapshot_dir: Option<PathBuf>,
204 pub index_dir: Option<PathBuf>,
205 pub cache_dir: Option<PathBuf>,
206 pub blob_dir: Option<PathBuf>,
207 pub metrics_dir: Option<PathBuf>,
208 pub logs_dir: Option<PathBuf>,
212 pub audit_log_destination: LogDestination,
213 pub slow_log_destination: LogDestination,
214 pub toggles: LayoutToggles,
215}
216
217impl TieredLayoutPaths {
218 pub fn new(
219 data_path: &Path,
220 layout: StorageLayout,
221 overrides: LayoutOverrides,
222 ) -> TieredLayoutPaths {
223 let toggles = layout.expand(&overrides);
224 let data_file = data_path.to_path_buf();
225 let support_dir = sibling_path(data_path, &format!("{}.red", file_name(data_path)));
226
227 let wal_file = if toggles.dedicated_wal_dir {
228 support_dir
229 .join("wal")
230 .join(sidecar_file_name(data_path, "rdb-uwal"))
231 } else {
232 data_path.with_extension("rdb-uwal")
233 };
234 let logical_wal_file = if toggles.dedicated_wal_dir {
235 support_dir
236 .join("wal")
237 .join(format!("{}.logical.wal", file_name(data_path)))
238 } else {
239 sibling_path(data_path, &format!("{}.logical.wal", file_name(data_path)))
240 };
241 let temp_file = if toggles.dedicated_temp_dir {
242 support_dir
243 .join("tmp")
244 .join(sidecar_file_name(data_path, "rdb-tmp"))
245 } else {
246 data_path.with_extension("rdb-tmp")
247 };
248
249 let audit_log_destination = overrides
250 .logs
251 .audit_log
252 .clone()
253 .unwrap_or_else(|| layout.default_audit_log_in(&support_dir));
254 let slow_log_destination = overrides
255 .logs
256 .slow_log
257 .clone()
258 .unwrap_or_else(|| layout.default_slow_log_in(&support_dir));
259 let logs_dir = match (
260 audit_log_destination.file_path(),
261 slow_log_destination.file_path(),
262 ) {
263 (None, None) => None,
264 _ => Some(support_dir.join("logs")),
265 };
266
267 TieredLayoutPaths {
268 data_file,
269 support_dir: support_dir.clone(),
270 wal_file,
271 logical_wal_file,
272 temp_file,
273 snapshot_dir: toggles
274 .dedicated_snapshot_dir
275 .then(|| support_dir.join("snapshots")),
276 index_dir: toggles
277 .dedicated_index_dir
278 .then(|| support_dir.join("indexes")),
279 cache_dir: toggles
280 .dedicated_cache_dir
281 .then(|| support_dir.join("cache")),
282 blob_dir: toggles
283 .dedicated_blob_dir
284 .then(|| support_dir.join("blobs")),
285 metrics_dir: toggles
286 .dedicated_metrics_dir
287 .then(|| support_dir.join("metrics")),
288 logs_dir,
289 audit_log_destination,
290 slow_log_destination,
291 toggles,
292 }
293 }
294
295 pub fn dirs_to_create(&self) -> Vec<PathBuf> {
296 let mut dirs = Vec::new();
297 push_parent(&mut dirs, &self.data_file);
298 push_parent(&mut dirs, &self.wal_file);
299 push_parent(&mut dirs, &self.logical_wal_file);
300 push_parent(&mut dirs, &self.temp_file);
301 push_optional(&mut dirs, self.snapshot_dir.as_ref());
302 push_optional(&mut dirs, self.index_dir.as_ref());
303 push_optional(&mut dirs, self.cache_dir.as_ref());
304 push_optional(&mut dirs, self.blob_dir.as_ref());
305 push_optional(&mut dirs, self.metrics_dir.as_ref());
306 push_optional(&mut dirs, self.logs_dir.as_ref());
307 if let Some(path) = self.audit_log_destination.file_path() {
308 push_parent(&mut dirs, path);
309 }
310 if let Some(path) = self.slow_log_destination.file_path() {
311 push_parent(&mut dirs, path);
312 }
313 dirs.sort();
314 dirs.dedup();
315 dirs
316 }
317
318 pub fn ensure_dirs(&self) -> io::Result<()> {
319 for dir in self.dirs_to_create() {
320 fs::create_dir_all(dir)?;
321 }
322 Ok(())
323 }
324}
325
326fn file_name(path: &Path) -> String {
327 path.file_name()
328 .and_then(|name| name.to_str())
329 .unwrap_or("data.rdb")
330 .to_string()
331}
332
333fn sibling_path(path: &Path, file_name: &str) -> PathBuf {
334 match path.parent() {
335 Some(parent) if !parent.as_os_str().is_empty() => parent.join(file_name),
336 _ => PathBuf::from(file_name),
337 }
338}
339
340fn sidecar_file_name(path: &Path, extension: &str) -> String {
341 path.with_extension(extension)
342 .file_name()
343 .and_then(|name| name.to_str())
344 .unwrap_or("data.rdb")
345 .to_string()
346}
347
348fn push_parent(dirs: &mut Vec<PathBuf>, path: &Path) {
349 if let Some(parent) = path.parent() {
350 if !parent.as_os_str().is_empty() {
351 dirs.push(parent.to_path_buf());
352 }
353 }
354}
355
356fn push_optional(dirs: &mut Vec<PathBuf>, path: Option<&PathBuf>) {
357 if let Some(path) = path {
358 dirs.push(path.clone());
359 }
360}