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, Copy, 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}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub struct LayoutToggles {
49 pub dedicated_wal_dir: bool,
50 pub dedicated_index_dir: bool,
51 pub dedicated_cache_dir: bool,
52 pub dedicated_snapshot_dir: bool,
53 pub dedicated_blob_dir: bool,
54 pub dedicated_temp_dir: bool,
55 pub dedicated_metrics_dir: bool,
56}
57
58impl StorageLayout {
59 pub fn expand(self, overrides: LayoutOverrides) -> LayoutToggles {
60 let mut toggles = match self {
61 Self::Minimal => LayoutToggles {
62 dedicated_wal_dir: false,
63 dedicated_index_dir: false,
64 dedicated_cache_dir: false,
65 dedicated_snapshot_dir: false,
66 dedicated_blob_dir: false,
67 dedicated_temp_dir: false,
68 dedicated_metrics_dir: false,
69 },
70 Self::Standard => LayoutToggles {
71 dedicated_wal_dir: false,
72 dedicated_index_dir: true,
73 dedicated_cache_dir: false,
74 dedicated_snapshot_dir: true,
75 dedicated_blob_dir: false,
76 dedicated_temp_dir: false,
77 dedicated_metrics_dir: false,
78 },
79 Self::Performance => LayoutToggles {
80 dedicated_wal_dir: true,
81 dedicated_index_dir: true,
82 dedicated_cache_dir: true,
83 dedicated_snapshot_dir: true,
84 dedicated_blob_dir: true,
85 dedicated_temp_dir: false,
86 dedicated_metrics_dir: false,
87 },
88 Self::Max => LayoutToggles {
89 dedicated_wal_dir: true,
90 dedicated_index_dir: true,
91 dedicated_cache_dir: true,
92 dedicated_snapshot_dir: true,
93 dedicated_blob_dir: true,
94 dedicated_temp_dir: true,
95 dedicated_metrics_dir: true,
96 },
97 };
98
99 if let Some(value) = overrides.dedicated_wal_dir {
100 toggles.dedicated_wal_dir = value;
101 }
102 if let Some(value) = overrides.dedicated_index_dir {
103 toggles.dedicated_index_dir = value;
104 }
105 if let Some(value) = overrides.dedicated_cache_dir {
106 toggles.dedicated_cache_dir = value;
107 }
108 if let Some(value) = overrides.dedicated_snapshot_dir {
109 toggles.dedicated_snapshot_dir = value;
110 }
111 if let Some(value) = overrides.dedicated_blob_dir {
112 toggles.dedicated_blob_dir = value;
113 }
114 if let Some(value) = overrides.dedicated_temp_dir {
115 toggles.dedicated_temp_dir = value;
116 }
117 if let Some(value) = overrides.dedicated_metrics_dir {
118 toggles.dedicated_metrics_dir = value;
119 }
120
121 toggles
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct TieredLayoutPaths {
128 pub data_file: PathBuf,
129 pub support_dir: PathBuf,
130 pub wal_file: PathBuf,
131 pub logical_wal_file: PathBuf,
132 pub temp_file: PathBuf,
133 pub snapshot_dir: Option<PathBuf>,
134 pub index_dir: Option<PathBuf>,
135 pub cache_dir: Option<PathBuf>,
136 pub blob_dir: Option<PathBuf>,
137 pub metrics_dir: Option<PathBuf>,
138 pub toggles: LayoutToggles,
139}
140
141impl TieredLayoutPaths {
142 pub fn new(
143 data_path: &Path,
144 layout: StorageLayout,
145 overrides: LayoutOverrides,
146 ) -> TieredLayoutPaths {
147 let toggles = layout.expand(overrides);
148 let data_file = data_path.to_path_buf();
149 let support_dir = sibling_path(data_path, &format!("{}.red", file_name(data_path)));
150
151 let wal_file = if toggles.dedicated_wal_dir {
152 support_dir
153 .join("wal")
154 .join(sidecar_file_name(data_path, "rdb-uwal"))
155 } else {
156 data_path.with_extension("rdb-uwal")
157 };
158 let logical_wal_file = if toggles.dedicated_wal_dir {
159 support_dir
160 .join("wal")
161 .join(format!("{}.logical.wal", file_name(data_path)))
162 } else {
163 sibling_path(data_path, &format!("{}.logical.wal", file_name(data_path)))
164 };
165 let temp_file = if toggles.dedicated_temp_dir {
166 support_dir
167 .join("tmp")
168 .join(sidecar_file_name(data_path, "rdb-tmp"))
169 } else {
170 data_path.with_extension("rdb-tmp")
171 };
172
173 TieredLayoutPaths {
174 data_file,
175 support_dir: support_dir.clone(),
176 wal_file,
177 logical_wal_file,
178 temp_file,
179 snapshot_dir: toggles
180 .dedicated_snapshot_dir
181 .then(|| support_dir.join("snapshots")),
182 index_dir: toggles
183 .dedicated_index_dir
184 .then(|| support_dir.join("indexes")),
185 cache_dir: toggles
186 .dedicated_cache_dir
187 .then(|| support_dir.join("cache")),
188 blob_dir: toggles
189 .dedicated_blob_dir
190 .then(|| support_dir.join("blobs")),
191 metrics_dir: toggles
192 .dedicated_metrics_dir
193 .then(|| support_dir.join("metrics")),
194 toggles,
195 }
196 }
197
198 pub fn dirs_to_create(&self) -> Vec<PathBuf> {
199 let mut dirs = Vec::new();
200 push_parent(&mut dirs, &self.data_file);
201 push_parent(&mut dirs, &self.wal_file);
202 push_parent(&mut dirs, &self.logical_wal_file);
203 push_parent(&mut dirs, &self.temp_file);
204 push_optional(&mut dirs, self.snapshot_dir.as_ref());
205 push_optional(&mut dirs, self.index_dir.as_ref());
206 push_optional(&mut dirs, self.cache_dir.as_ref());
207 push_optional(&mut dirs, self.blob_dir.as_ref());
208 push_optional(&mut dirs, self.metrics_dir.as_ref());
209 dirs.sort();
210 dirs.dedup();
211 dirs
212 }
213
214 pub fn ensure_dirs(&self) -> io::Result<()> {
215 for dir in self.dirs_to_create() {
216 fs::create_dir_all(dir)?;
217 }
218 Ok(())
219 }
220}
221
222fn file_name(path: &Path) -> String {
223 path.file_name()
224 .and_then(|name| name.to_str())
225 .unwrap_or("data.rdb")
226 .to_string()
227}
228
229fn sibling_path(path: &Path, file_name: &str) -> PathBuf {
230 match path.parent() {
231 Some(parent) if !parent.as_os_str().is_empty() => parent.join(file_name),
232 _ => PathBuf::from(file_name),
233 }
234}
235
236fn sidecar_file_name(path: &Path, extension: &str) -> String {
237 path.with_extension(extension)
238 .file_name()
239 .and_then(|name| name.to_str())
240 .unwrap_or("data.rdb")
241 .to_string()
242}
243
244fn push_parent(dirs: &mut Vec<PathBuf>, path: &Path) {
245 if let Some(parent) = path.parent() {
246 if !parent.as_os_str().is_empty() {
247 dirs.push(parent.to_path_buf());
248 }
249 }
250}
251
252fn push_optional(dirs: &mut Vec<PathBuf>, path: Option<&PathBuf>) {
253 if let Some(path) = path {
254 dirs.push(path.clone());
255 }
256}