1use std::path::{Path, PathBuf};
8
9use serde::{Deserialize, Serialize};
10
11pub const SUPPORT_DIR_SUFFIX: &str = "red";
12pub const UNIFIED_WAL_EXTENSION: &str = "rdb-uwal";
13pub const LOGICAL_WAL_SUFFIX: &str = "logical.wal";
14pub const TEMP_EXTENSION: &str = "rdb-tmp";
15pub const ATOMIC_TEMP_EXTENSION: &str = "tmp";
16pub const PRIMARY_WAL_EXTENSION: &str = "redwal";
17pub const PAGER_LEGACY_WAL_EXTENSION: &str = "wal";
18pub const ENGINE_WAL_EXTENSION: &str = "rdb-wal";
19pub const PAGER_DWB_EXTENSION: &str = "rdb-dwb";
20pub const PAGER_DWB_SHADOW_SUFFIX: &str = "dwb";
21pub const SHM_FILE_SUFFIX: &str = "shm";
22pub const PHYSICAL_METADATA_JSON_SUFFIX: &str = "meta.json";
23pub const PHYSICAL_METADATA_BINARY_EXTENSION: &str = "meta.rdbx";
24pub const REBOOTSTRAP_STAGING_EXTENSION: &str = "rebootstrap.redbase";
25pub const REBOOTSTRAP_PENDING_EXTENSION: &str = "rebootstrap.pending.rdb";
26pub const REBOOTSTRAP_READY_EXTENSION: &str = "rebootstrap.ready";
27pub const REBOOTSTRAP_INTENT_LOG_EXTENSION: &str = "rebootstrap.intent.jsonl";
28pub const REBOOTSTRAP_PREVIOUS_EXTENSION: &str = "rebootstrap.previous.rdb";
29pub const PRIMARY_REPLICA_ROOT_EXTENSION: &str = "primary-replica";
30pub const LEGACY_LOGICAL_SLOTS_SUFFIX: &str = "logical.slots.json";
31pub const LEGACY_LOGICAL_SLOTS_TEMP_EXTENSION: &str = "logical.slots.tmp";
32pub const LEGACY_AUDIT_LOG_FILE_NAME: &str = ".audit.log";
33pub const AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION: &str = "zst";
34pub const LEGACY_SLOW_QUERY_LOG_FILE_NAME: &str = "red-slow.log";
35pub const SERVERLESS_ROOT_EXTENSION: &str = "serverless";
36pub const SERVERLESS_CACHE_DIR: &str = "cache";
37pub const RESULT_CACHE_L2_EXTENSION: &str = "result-cache.l2";
38pub const LOCAL_CAS_LOCK_SUFFIX: &str = "cas.lock";
39pub const EMBEDDED_WRITER_LOCK_SUFFIX: &str = "writer.lock";
40pub const LOCAL_UPLOAD_TEMP_TAG: &str = "tmp";
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
44#[serde(rename_all = "kebab-case")]
45#[derive(Default)]
46pub enum StorageLayout {
47 Minimal,
49 #[default]
51 Standard,
52 Performance,
54 Max,
56}
57
58#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(default)]
61pub struct LayoutOverrides {
62 pub dedicated_wal_dir: Option<bool>,
63 pub dedicated_index_dir: Option<bool>,
64 pub dedicated_cache_dir: Option<bool>,
65 pub dedicated_snapshot_dir: Option<bool>,
66 pub dedicated_blob_dir: Option<bool>,
67 pub dedicated_temp_dir: Option<bool>,
68 pub dedicated_metrics_dir: Option<bool>,
69 #[serde(default)]
71 pub logs: LogRoutingOverrides,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "kebab-case", tag = "kind", content = "path")]
77pub enum LogDestination {
78 Stderr,
79 File(PathBuf),
80 Syslog,
81}
82
83impl LogDestination {
84 pub fn describe(&self) -> String {
86 match self {
87 Self::Stderr => "stderr".to_string(),
88 Self::Syslog => "syslog".to_string(),
89 Self::File(path) => format!("file:{}", path.display()),
90 }
91 }
92
93 pub fn file_path(&self) -> Option<&Path> {
95 match self {
96 Self::File(path) => Some(path.as_path()),
97 _ => None,
98 }
99 }
100}
101
102#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(default)]
105pub struct LogRoutingOverrides {
106 pub audit_log: Option<LogDestination>,
107 pub slow_log: Option<LogDestination>,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112pub struct LayoutToggles {
113 pub dedicated_wal_dir: bool,
114 pub dedicated_index_dir: bool,
115 pub dedicated_cache_dir: bool,
116 pub dedicated_snapshot_dir: bool,
117 pub dedicated_blob_dir: bool,
118 pub dedicated_temp_dir: bool,
119 pub dedicated_metrics_dir: bool,
120}
121
122impl StorageLayout {
123 pub fn default_audit_log_in(self, support_dir: &Path) -> LogDestination {
125 match self {
126 Self::Performance | Self::Max => {
127 LogDestination::File(support_dir.join("logs").join("audit.log"))
128 }
129 Self::Minimal | Self::Standard => LogDestination::Stderr,
130 }
131 }
132
133 pub fn default_slow_log_in(self, support_dir: &Path) -> LogDestination {
135 match self {
136 Self::Performance | Self::Max => {
137 LogDestination::File(support_dir.join("logs").join("slow.log"))
138 }
139 Self::Minimal | Self::Standard => LogDestination::Stderr,
140 }
141 }
142
143 pub fn expand(self, overrides: &LayoutOverrides) -> LayoutToggles {
144 let mut toggles = match self {
145 Self::Minimal => LayoutToggles {
146 dedicated_wal_dir: false,
147 dedicated_index_dir: false,
148 dedicated_cache_dir: false,
149 dedicated_snapshot_dir: false,
150 dedicated_blob_dir: false,
151 dedicated_temp_dir: false,
152 dedicated_metrics_dir: false,
153 },
154 Self::Standard => LayoutToggles {
155 dedicated_wal_dir: false,
156 dedicated_index_dir: true,
157 dedicated_cache_dir: false,
158 dedicated_snapshot_dir: true,
159 dedicated_blob_dir: false,
160 dedicated_temp_dir: false,
161 dedicated_metrics_dir: false,
162 },
163 Self::Performance => LayoutToggles {
164 dedicated_wal_dir: true,
165 dedicated_index_dir: true,
166 dedicated_cache_dir: true,
167 dedicated_snapshot_dir: true,
168 dedicated_blob_dir: true,
169 dedicated_temp_dir: false,
170 dedicated_metrics_dir: false,
171 },
172 Self::Max => LayoutToggles {
173 dedicated_wal_dir: true,
174 dedicated_index_dir: true,
175 dedicated_cache_dir: true,
176 dedicated_snapshot_dir: true,
177 dedicated_blob_dir: true,
178 dedicated_temp_dir: true,
179 dedicated_metrics_dir: true,
180 },
181 };
182
183 if let Some(value) = overrides.dedicated_wal_dir {
184 toggles.dedicated_wal_dir = value;
185 }
186 if let Some(value) = overrides.dedicated_index_dir {
187 toggles.dedicated_index_dir = value;
188 }
189 if let Some(value) = overrides.dedicated_cache_dir {
190 toggles.dedicated_cache_dir = value;
191 }
192 if let Some(value) = overrides.dedicated_snapshot_dir {
193 toggles.dedicated_snapshot_dir = value;
194 }
195 if let Some(value) = overrides.dedicated_blob_dir {
196 toggles.dedicated_blob_dir = value;
197 }
198 if let Some(value) = overrides.dedicated_temp_dir {
199 toggles.dedicated_temp_dir = value;
200 }
201 if let Some(value) = overrides.dedicated_metrics_dir {
202 toggles.dedicated_metrics_dir = value;
203 }
204
205 toggles
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct TieredLayoutPaths {
212 pub data_file: PathBuf,
213 pub support_dir: PathBuf,
214 pub wal_file: PathBuf,
215 pub logical_wal_file: PathBuf,
216 pub temp_file: PathBuf,
217 pub snapshot_dir: Option<PathBuf>,
218 pub index_dir: Option<PathBuf>,
219 pub cache_dir: Option<PathBuf>,
220 pub blob_dir: Option<PathBuf>,
221 pub metrics_dir: Option<PathBuf>,
222 pub logs_dir: Option<PathBuf>,
223 pub audit_log_destination: LogDestination,
224 pub slow_log_destination: LogDestination,
225 pub toggles: LayoutToggles,
226}
227
228impl TieredLayoutPaths {
229 pub fn new(
230 data_path: &Path,
231 layout: StorageLayout,
232 overrides: LayoutOverrides,
233 ) -> TieredLayoutPaths {
234 let toggles = layout.expand(&overrides);
235 let data_file = data_path.to_path_buf();
236 let support_dir = support_dir_for(data_path);
237
238 let wal_file = if toggles.dedicated_wal_dir {
239 unified_wal_path_in(&support_dir, data_path)
240 } else {
241 unified_wal_path(data_path)
242 };
243 let logical_wal_file = if toggles.dedicated_wal_dir {
244 logical_wal_path_in(&support_dir, data_path)
245 } else {
246 logical_wal_path(data_path)
247 };
248 let temp_file = if toggles.dedicated_temp_dir {
249 temp_path_in(&support_dir, data_path)
250 } else {
251 temp_path(data_path)
252 };
253
254 let audit_log_destination = overrides
255 .logs
256 .audit_log
257 .clone()
258 .unwrap_or_else(|| layout.default_audit_log_in(&support_dir));
259 let slow_log_destination = overrides
260 .logs
261 .slow_log
262 .clone()
263 .unwrap_or_else(|| layout.default_slow_log_in(&support_dir));
264 let logs_dir = match (
265 audit_log_destination.file_path(),
266 slow_log_destination.file_path(),
267 ) {
268 (None, None) => None,
269 _ => Some(support_dir.join("logs")),
270 };
271
272 TieredLayoutPaths {
273 data_file,
274 support_dir: support_dir.clone(),
275 wal_file,
276 logical_wal_file,
277 temp_file,
278 snapshot_dir: toggles
279 .dedicated_snapshot_dir
280 .then(|| support_dir.join("snapshots")),
281 index_dir: toggles
282 .dedicated_index_dir
283 .then(|| support_dir.join("indexes")),
284 cache_dir: toggles
285 .dedicated_cache_dir
286 .then(|| support_dir.join("cache")),
287 blob_dir: toggles
288 .dedicated_blob_dir
289 .then(|| support_dir.join("blobs")),
290 metrics_dir: toggles
291 .dedicated_metrics_dir
292 .then(|| support_dir.join("metrics")),
293 logs_dir,
294 audit_log_destination,
295 slow_log_destination,
296 toggles,
297 }
298 }
299
300 pub fn dirs_to_create(&self) -> Vec<PathBuf> {
301 let mut dirs = Vec::new();
302 push_parent(&mut dirs, &self.data_file);
303 push_parent(&mut dirs, &self.wal_file);
304 push_parent(&mut dirs, &self.logical_wal_file);
305 push_parent(&mut dirs, &self.temp_file);
306 push_optional(&mut dirs, self.snapshot_dir.as_ref());
307 push_optional(&mut dirs, self.index_dir.as_ref());
308 push_optional(&mut dirs, self.cache_dir.as_ref());
309 push_optional(&mut dirs, self.blob_dir.as_ref());
310 push_optional(&mut dirs, self.metrics_dir.as_ref());
311 push_optional(&mut dirs, self.logs_dir.as_ref());
312 if let Some(path) = self.audit_log_destination.file_path() {
313 push_parent(&mut dirs, path);
314 }
315 if let Some(path) = self.slow_log_destination.file_path() {
316 push_parent(&mut dirs, path);
317 }
318 dirs.sort();
319 dirs.dedup();
320 dirs
321 }
322
323 pub fn ensure_dirs(&self) -> std::io::Result<()> {
324 for dir in self.dirs_to_create() {
325 std::fs::create_dir_all(dir)?;
326 }
327 Ok(())
328 }
329
330 pub fn turbo_snapshot_path(&self, collection: &str) -> Option<PathBuf> {
332 if !self.toggles.dedicated_snapshot_dir {
333 return None;
334 }
335 if let Some(dir) = &self.snapshot_dir {
336 return Some(dir.join(format!("{collection}.tv")));
337 }
338 let stem = data_file_name(&self.data_file);
339 Some(sibling_path(
340 &self.data_file,
341 &format!("{stem}.{collection}.tv"),
342 ))
343 }
344}
345
346pub fn data_file_name(path: &Path) -> String {
347 path.file_name()
348 .and_then(|name| name.to_str())
349 .unwrap_or(DEFAULT_DATABASE_FILE_NAME)
350 .to_string()
351}
352
353pub const DEFAULT_DATABASE_FILE_NAME: &str = "data.rdb";
354pub const DEFAULT_SERVICE_DATABASE_PATH: &str = "/var/lib/reddb/data.rdb";
355pub const TRANSACTION_WAL_FILE_NAME: &str = "wal.log";
356
357pub fn default_database_path() -> PathBuf {
358 PathBuf::from(DEFAULT_DATABASE_FILE_NAME)
359}
360
361pub fn default_service_database_path() -> PathBuf {
362 PathBuf::from(DEFAULT_SERVICE_DATABASE_PATH)
363}
364
365pub fn default_transaction_wal_path() -> PathBuf {
366 PathBuf::from(TRANSACTION_WAL_FILE_NAME)
367}
368
369pub fn sibling_path(path: &Path, file_name: &str) -> PathBuf {
370 match path.parent() {
371 Some(parent) if !parent.as_os_str().is_empty() => parent.join(file_name),
372 _ => PathBuf::from(file_name),
373 }
374}
375
376pub fn sidecar_file_name(path: &Path, extension: &str) -> String {
377 path.with_extension(extension)
378 .file_name()
379 .and_then(|name| name.to_str())
380 .unwrap_or(DEFAULT_DATABASE_FILE_NAME)
381 .to_string()
382}
383
384pub fn support_dir_for(data_path: &Path) -> PathBuf {
385 let file_name = data_file_name(data_path);
386 sibling_path(data_path, &format!("{file_name}.{SUPPORT_DIR_SUFFIX}"))
387}
388
389pub fn unified_wal_path(data_path: &Path) -> PathBuf {
390 data_path.with_extension(UNIFIED_WAL_EXTENSION)
391}
392
393pub fn unified_wal_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
394 support_dir
395 .join("wal")
396 .join(sidecar_file_name(data_path, UNIFIED_WAL_EXTENSION))
397}
398
399pub fn store_commit_coord_temp_wal_path(
400 temp_dir: &Path,
401 name: &str,
402 process_id: u32,
403 nanos: u128,
404) -> PathBuf {
405 temp_dir.join(store_commit_coord_temp_wal_file_name(
406 name, process_id, nanos,
407 ))
408}
409
410pub fn store_commit_coord_temp_wal_file_name(name: &str, process_id: u32, nanos: u128) -> String {
411 format!("rb_commit_coord_{name}_{process_id}_{nanos}.wal")
412}
413
414pub fn group_commit_temp_wal_path(
415 temp_dir: &Path,
416 name: &str,
417 process_id: u32,
418 nanos: u128,
419) -> PathBuf {
420 temp_dir.join(group_commit_temp_wal_file_name(name, process_id, nanos))
421}
422
423pub fn group_commit_temp_wal_file_name(name: &str, process_id: u32, nanos: u128) -> String {
424 format!("rb_group_commit_{name}_{process_id}_{nanos}.wal")
425}
426
427pub fn wal_component_temp_path(
428 temp_dir: &Path,
429 component: &str,
430 name: &str,
431 process_id: u32,
432) -> PathBuf {
433 temp_dir.join(wal_component_temp_file_name(component, name, process_id))
434}
435
436pub fn wal_component_temp_file_name(component: &str, name: &str, process_id: u32) -> String {
437 format!("rb_wal_{component}_{name}_{process_id}.wal")
438}
439
440pub fn wal_component_unique_temp_path(
441 temp_dir: &Path,
442 component: &str,
443 name: &str,
444 process_id: u32,
445 nanos: u128,
446) -> PathBuf {
447 temp_dir.join(wal_component_unique_temp_file_name(
448 component, name, process_id, nanos,
449 ))
450}
451
452pub fn wal_component_unique_temp_file_name(
453 component: &str,
454 name: &str,
455 process_id: u32,
456 nanos: u128,
457) -> String {
458 format!("rb_wal_{component}_{name}_{process_id}_{nanos}.wal")
459}
460
461pub fn backup_temp_json_path(
462 temp_dir: &Path,
463 prefix: &str,
464 process_id: u32,
465 nanos: u128,
466 unique: u64,
467 start_lsn: Option<u64>,
468 end_lsn: Option<u64>,
469) -> PathBuf {
470 temp_dir.join(backup_temp_json_file_name(
471 prefix, process_id, nanos, unique, start_lsn, end_lsn,
472 ))
473}
474
475pub fn backup_temp_json_file_name(
476 prefix: &str,
477 process_id: u32,
478 nanos: u128,
479 unique: u64,
480 start_lsn: Option<u64>,
481 end_lsn: Option<u64>,
482) -> String {
483 match (start_lsn, end_lsn) {
487 (Some(start_lsn), Some(end_lsn)) => {
488 format!("{prefix}-{process_id}-{start_lsn}-{end_lsn}-{nanos}-{unique}.json")
489 }
490 _ => format!("{prefix}-{process_id}-{nanos}-{unique}.json"),
491 }
492}
493
494pub fn logical_wal_path(data_path: &Path) -> PathBuf {
495 sibling_path(
496 data_path,
497 &format!("{}.{}", data_file_name(data_path), LOGICAL_WAL_SUFFIX),
498 )
499}
500
501pub fn logical_wal_temp_path(logical_wal_path: &Path) -> PathBuf {
502 logical_wal_path.with_extension("logical.wal.tmp")
503}
504
505pub fn logical_wal_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
506 support_dir.join("wal").join(format!(
507 "{}.{}",
508 data_file_name(data_path),
509 LOGICAL_WAL_SUFFIX
510 ))
511}
512
513pub fn temp_path(data_path: &Path) -> PathBuf {
514 data_path.with_extension(TEMP_EXTENSION)
515}
516
517pub fn atomic_temp_path(path: &Path) -> PathBuf {
518 path.with_extension(ATOMIC_TEMP_EXTENSION)
519}
520
521pub fn result_cache_l2_path(data_path: &Path) -> PathBuf {
522 data_path.with_extension(RESULT_CACHE_L2_EXTENSION)
523}
524
525pub fn temp_path_in(support_dir: &Path, data_path: &Path) -> PathBuf {
526 support_dir
527 .join("tmp")
528 .join(sidecar_file_name(data_path, TEMP_EXTENSION))
529}
530
531pub fn primary_wal_segment_file_name(segment_index: u64) -> String {
532 format!("{segment_index:020}.{PRIMARY_WAL_EXTENSION}")
533}
534
535pub fn relay_segment_relative_path(start_lsn: u64, end_lsn: u64) -> PathBuf {
536 PathBuf::from(format!(
537 "relay-{start_lsn:020}-{end_lsn:020}.{PRIMARY_WAL_EXTENSION}"
538 ))
539}
540
541pub fn pager_legacy_wal_path(data_path: &Path) -> PathBuf {
542 data_path.with_extension(PAGER_LEGACY_WAL_EXTENSION)
543}
544
545pub fn engine_wal_path(data_path: &Path) -> PathBuf {
546 data_path.with_extension(ENGINE_WAL_EXTENSION)
547}
548
549pub fn pager_dwb_path(data_path: &Path) -> PathBuf {
550 data_path.with_extension(PAGER_DWB_EXTENSION)
551}
552
553fn path_with_dash_suffix(data_path: &Path, suffix: &str) -> PathBuf {
554 let mut path = data_path.to_path_buf().into_os_string();
555 path.push("-");
556 path.push(suffix);
557 PathBuf::from(path)
558}
559
560pub fn pager_dwb_shadow_path(data_path: &Path) -> PathBuf {
561 path_with_dash_suffix(data_path, PAGER_DWB_SHADOW_SUFFIX)
562}
563
564pub fn pager_shadow_sidecar_paths(_data_path: &Path) -> [PathBuf; 0] {
565 []
566}
567
568pub mod retired {
578 use super::path_with_dash_suffix;
579 use std::path::{Path, PathBuf};
580
581 pub const PAGER_HEADER_EXTENSION_V0: &str = "rdb-hdr";
583 pub const PAGER_META_EXTENSION_V0: &str = "rdb-meta";
585 pub const PAGER_DWB_EXTENSION_V0: &str = "rdb-dwb";
587
588 pub fn pager_header_path_v0(data_path: &Path) -> PathBuf {
589 data_path.with_extension(PAGER_HEADER_EXTENSION_V0)
590 }
591
592 pub fn pager_meta_path_v0(data_path: &Path) -> PathBuf {
593 data_path.with_extension(PAGER_META_EXTENSION_V0)
594 }
595
596 pub fn pager_dwb_path_v0(data_path: &Path) -> PathBuf {
597 data_path.with_extension(PAGER_DWB_EXTENSION_V0)
598 }
599
600 pub fn pager_header_shadow_path_v0(data_path: &Path) -> PathBuf {
601 path_with_dash_suffix(data_path, "hdr")
602 }
603
604 pub fn pager_meta_shadow_path_v0(data_path: &Path) -> PathBuf {
605 path_with_dash_suffix(data_path, "meta")
606 }
607
608 pub fn pager_dwb_shadow_path_v0(data_path: &Path) -> PathBuf {
609 path_with_dash_suffix(data_path, "dwb")
610 }
611
612 pub fn phase1_sidecar_paths(data_path: &Path) -> [PathBuf; 4] {
616 [
617 pager_header_path_v0(data_path),
618 pager_meta_path_v0(data_path),
619 pager_header_shadow_path_v0(data_path),
620 pager_meta_shadow_path_v0(data_path),
621 ]
622 }
623
624 pub fn first_present_phase1_sidecar(data_path: &Path) -> Option<PathBuf> {
627 phase1_sidecar_paths(data_path)
628 .into_iter()
629 .find(|path| path.exists())
630 }
631
632 pub fn phase3_dwb_sidecar_paths(data_path: &Path) -> [PathBuf; 2] {
634 [
635 pager_dwb_path_v0(data_path),
636 pager_dwb_shadow_path_v0(data_path),
637 ]
638 }
639
640 pub fn first_present_phase3_dwb_sidecar(data_path: &Path) -> Option<PathBuf> {
641 phase3_dwb_sidecar_paths(data_path)
642 .into_iter()
643 .find(|path| path.exists())
644 }
645}
646
647pub fn shm_path(data_path: &Path) -> PathBuf {
648 sibling_path(
649 data_path,
650 &format!("{}-{SHM_FILE_SUFFIX}", data_file_name(data_path)),
651 )
652}
653
654pub fn physical_metadata_json_path(data_path: &Path) -> PathBuf {
655 sibling_path(
656 data_path,
657 &format!(
658 "{}.{PHYSICAL_METADATA_JSON_SUFFIX}",
659 data_file_name(data_path)
660 ),
661 )
662}
663
664pub fn physical_metadata_binary_path(data_path: &Path) -> PathBuf {
665 sibling_path(
666 data_path,
667 &format!(
668 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}",
669 data_file_name(data_path)
670 ),
671 )
672}
673
674pub fn physical_metadata_journal_path(data_path: &Path, sequence: u64) -> PathBuf {
675 sibling_path(
676 data_path,
677 &format!(
678 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-{sequence:020}",
679 data_file_name(data_path)
680 ),
681 )
682}
683
684pub fn physical_metadata_journal_prefix(data_path: &Path) -> String {
685 format!(
686 "{}.{PHYSICAL_METADATA_BINARY_EXTENSION}.seq-",
687 data_file_name(data_path)
688 )
689}
690
691pub fn physical_export_data_path(data_path: &Path, name: &str) -> PathBuf {
692 let file_name = data_file_name(data_path);
693 let stem = file_name.strip_suffix(".rdb").unwrap_or(&file_name);
694 sibling_path(
695 data_path,
696 &format!("{stem}.export.{}.rdb", sanitize_export_name(name)),
697 )
698}
699
700pub fn local_cas_lock_path(dest: &Path) -> PathBuf {
701 let file_name = data_file_name(dest);
702 dest.with_file_name(format!(".{file_name}.{LOCAL_CAS_LOCK_SUFFIX}"))
703}
704
705pub fn embedded_writer_lock_path(data_path: &Path) -> PathBuf {
708 let file_name = data_file_name(data_path);
709 data_path.with_file_name(format!(".{file_name}.{EMBEDDED_WRITER_LOCK_SUFFIX}"))
710}
711
712pub fn local_upload_temp_path(dest: &Path, pid: u32, unique: u64) -> PathBuf {
713 let file_name = data_file_name(dest);
714 dest.with_file_name(format!(
715 ".{file_name}.{LOCAL_UPLOAD_TEMP_TAG}-{pid}-{unique}"
716 ))
717}
718
719pub fn rebootstrap_staging_root(data_path: &Path) -> PathBuf {
720 data_path.with_extension(REBOOTSTRAP_STAGING_EXTENSION)
721}
722
723pub fn rebootstrap_pending_path(data_path: &Path) -> PathBuf {
724 data_path.with_extension(REBOOTSTRAP_PENDING_EXTENSION)
725}
726
727pub fn rebootstrap_ready_marker_path(data_path: &Path) -> PathBuf {
728 data_path.with_extension(REBOOTSTRAP_READY_EXTENSION)
729}
730
731pub fn rebootstrap_intent_log_path(data_path: &Path) -> PathBuf {
732 data_path.with_extension(REBOOTSTRAP_INTENT_LOG_EXTENSION)
733}
734
735pub fn rebootstrap_previous_path(data_path: &Path) -> PathBuf {
736 data_path.with_extension(REBOOTSTRAP_PREVIOUS_EXTENSION)
737}
738
739pub fn primary_replica_root(data_path: &Path) -> PathBuf {
740 data_path.with_extension(PRIMARY_REPLICA_ROOT_EXTENSION)
741}
742
743pub fn legacy_logical_slots_path(data_path: &Path) -> PathBuf {
744 let file_name = data_path
745 .file_name()
746 .and_then(|name| name.to_str())
747 .unwrap_or("reddb.rdb");
748 sibling_path(
749 data_path,
750 &format!("{file_name}.{LEGACY_LOGICAL_SLOTS_SUFFIX}"),
751 )
752}
753
754pub fn legacy_logical_slots_temp_path(path: &Path) -> PathBuf {
755 path.with_extension(LEGACY_LOGICAL_SLOTS_TEMP_EXTENSION)
756}
757
758pub fn legacy_audit_log_path(data_path: &Path) -> PathBuf {
759 sibling_path(data_path, LEGACY_AUDIT_LOG_FILE_NAME)
760}
761
762pub fn audit_log_rotated_plain_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
763 sibling_path(
764 active_path,
765 &format!("{}.{timestamp_nanos}", audit_log_file_name(active_path)),
766 )
767}
768
769pub fn audit_log_rotated_compressed_path(active_path: &Path, timestamp_nanos: u128) -> PathBuf {
770 sibling_path(
771 active_path,
772 &format!(
773 "{}.{timestamp_nanos}.{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}",
774 audit_log_file_name(active_path)
775 ),
776 )
777}
778
779pub fn parse_audit_log_rotated_timestamp(
780 active_path: &Path,
781 candidate_file_name: &str,
782) -> Option<u128> {
783 let active_name = audit_log_file_name(active_path);
784 let rotated = candidate_file_name.strip_prefix(&format!("{active_name}."))?;
785 let timestamp = rotated
786 .strip_suffix(&format!(".{AUDIT_LOG_ROTATED_COMPRESSED_EXTENSION}"))
787 .unwrap_or(rotated);
788 timestamp.parse::<u128>().ok()
789}
790
791pub fn legacy_slow_query_log_path(log_dir: &Path) -> PathBuf {
792 log_dir.join(LEGACY_SLOW_QUERY_LOG_FILE_NAME)
793}
794
795pub fn serverless_root(data_path: &Path) -> PathBuf {
796 data_path.with_extension(SERVERLESS_ROOT_EXTENSION)
797}
798
799pub fn serverless_namespace(data_path: &Path) -> String {
800 data_path
801 .file_stem()
802 .and_then(|stem| stem.to_str())
803 .filter(|stem| !stem.is_empty())
804 .unwrap_or("default")
805 .to_string()
806}
807
808pub fn serverless_cache_root(root: &Path, namespace: &str) -> PathBuf {
809 root.join(namespace).join(SERVERLESS_CACHE_DIR)
810}
811
812fn audit_log_file_name(path: &Path) -> String {
813 path.file_name()
814 .and_then(|name| name.to_str())
815 .unwrap_or(LEGACY_AUDIT_LOG_FILE_NAME)
816 .to_string()
817}
818
819fn push_parent(dirs: &mut Vec<PathBuf>, path: &Path) {
820 if let Some(parent) = path.parent() {
821 if !parent.as_os_str().is_empty() {
822 dirs.push(parent.to_path_buf());
823 }
824 }
825}
826
827fn push_optional(dirs: &mut Vec<PathBuf>, path: Option<&PathBuf>) {
828 if let Some(path) = path {
829 dirs.push(path.clone());
830 }
831}
832
833fn sanitize_export_name(name: &str) -> String {
834 let mut out = String::new();
835 for ch in name.chars() {
836 if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
837 out.push(ch);
838 } else {
839 out.push('_');
840 }
841 }
842 if out.is_empty() {
843 "export".to_string()
844 } else {
845 out
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[test]
854 fn derives_standard_sidecars_next_to_database() {
855 let path = Path::new("/var/lib/reddb/main.rdb");
856
857 assert_eq!(
858 support_dir_for(path),
859 PathBuf::from("/var/lib/reddb/main.rdb.red")
860 );
861 assert_eq!(
862 unified_wal_path(path),
863 PathBuf::from("/var/lib/reddb/main.rdb-uwal")
864 );
865 assert_eq!(
866 store_commit_coord_temp_wal_path(Path::new("/tmp"), "burst", 7, 99),
867 PathBuf::from("/tmp/rb_commit_coord_burst_7_99.wal")
868 );
869 assert_eq!(
870 group_commit_temp_wal_path(Path::new("/tmp"), "batch", 7, 99),
871 PathBuf::from("/tmp/rb_group_commit_batch_7_99.wal")
872 );
873 assert_eq!(
874 wal_component_temp_path(Path::new("/tmp"), "writer", "create", 7),
875 PathBuf::from("/tmp/rb_wal_writer_create_7.wal")
876 );
877 assert_eq!(
878 wal_component_temp_path(Path::new("/tmp"), "reader", "empty", 7),
879 PathBuf::from("/tmp/rb_wal_reader_empty_7.wal")
880 );
881 assert_eq!(
882 wal_component_unique_temp_path(Path::new("/tmp"), "coord", "single", 7, 99),
883 PathBuf::from("/tmp/rb_wal_coord_single_7_99.wal")
884 );
885 assert_eq!(
886 backup_temp_json_path(
887 Path::new("/tmp"),
888 "reddb-archived-change-records",
889 7,
890 99,
891 3,
892 Some(10),
893 Some(20)
894 ),
895 PathBuf::from("/tmp/reddb-archived-change-records-7-10-20-99-3.json")
896 );
897 assert_eq!(
898 backup_temp_json_path(Path::new("/tmp"), "reddb-json-object", 7, 99, 3, None, None),
899 PathBuf::from("/tmp/reddb-json-object-7-99-3.json")
900 );
901 assert_eq!(
902 logical_wal_path(path),
903 PathBuf::from("/var/lib/reddb/main.rdb.logical.wal")
904 );
905 assert_eq!(
906 logical_wal_temp_path(&logical_wal_path(path)),
907 PathBuf::from("/var/lib/reddb/main.rdb.logical.logical.wal.tmp")
908 );
909 assert_eq!(
910 temp_path(path),
911 PathBuf::from("/var/lib/reddb/main.rdb-tmp")
912 );
913 assert_eq!(
914 atomic_temp_path(&pager_dwb_path(path)),
915 PathBuf::from("/var/lib/reddb/main.tmp")
916 );
917 assert_eq!(
918 result_cache_l2_path(path),
919 PathBuf::from("/var/lib/reddb/main.result-cache.l2")
920 );
921 assert_eq!(
922 engine_wal_path(path),
923 PathBuf::from("/var/lib/reddb/main.rdb-wal")
924 );
925 assert_eq!(
926 pager_legacy_wal_path(path),
927 PathBuf::from("/var/lib/reddb/main.wal")
928 );
929 assert_eq!(
930 pager_dwb_shadow_path(path),
931 PathBuf::from("/var/lib/reddb/main.rdb-dwb")
932 );
933 assert_eq!(shm_path(path), PathBuf::from("/var/lib/reddb/main.rdb-shm"));
934 assert_eq!(
935 physical_metadata_json_path(path),
936 PathBuf::from("/var/lib/reddb/main.rdb.meta.json")
937 );
938 assert_eq!(
939 physical_metadata_binary_path(path),
940 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx")
941 );
942 assert_eq!(
943 physical_metadata_journal_path(path, 7),
944 PathBuf::from("/var/lib/reddb/main.rdb.meta.rdbx.seq-00000000000000000007")
945 );
946 assert_eq!(
947 physical_metadata_journal_prefix(path),
948 "main.rdb.meta.rdbx.seq-"
949 );
950 assert_eq!(
951 physical_export_data_path(path, "nightly backup"),
952 PathBuf::from("/var/lib/reddb/main.export.nightly_backup.rdb")
953 );
954 assert_eq!(
955 local_cas_lock_path(path),
956 PathBuf::from("/var/lib/reddb/.main.rdb.cas.lock")
957 );
958 assert_eq!(
959 embedded_writer_lock_path(path),
960 PathBuf::from("/var/lib/reddb/.main.rdb.writer.lock")
961 );
962 assert_eq!(
963 local_upload_temp_path(path, 123, 7),
964 PathBuf::from("/var/lib/reddb/.main.rdb.tmp-123-7")
965 );
966 assert_eq!(
967 rebootstrap_staging_root(path),
968 PathBuf::from("/var/lib/reddb/main.rebootstrap.redbase")
969 );
970 assert_eq!(
971 rebootstrap_pending_path(path),
972 PathBuf::from("/var/lib/reddb/main.rebootstrap.pending.rdb")
973 );
974 assert_eq!(
975 rebootstrap_ready_marker_path(path),
976 PathBuf::from("/var/lib/reddb/main.rebootstrap.ready")
977 );
978 assert_eq!(
979 rebootstrap_intent_log_path(path),
980 PathBuf::from("/var/lib/reddb/main.rebootstrap.intent.jsonl")
981 );
982 assert_eq!(
983 rebootstrap_previous_path(path),
984 PathBuf::from("/var/lib/reddb/main.rebootstrap.previous.rdb")
985 );
986 assert_eq!(
987 primary_replica_root(path),
988 PathBuf::from("/var/lib/reddb/main.primary-replica")
989 );
990 assert_eq!(
991 legacy_logical_slots_path(path),
992 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.json")
993 );
994 assert_eq!(
995 legacy_logical_slots_temp_path(&legacy_logical_slots_path(path)),
996 PathBuf::from("/var/lib/reddb/main.rdb.logical.slots.logical.slots.tmp")
997 );
998 assert_eq!(
999 legacy_audit_log_path(path),
1000 PathBuf::from("/var/lib/reddb/.audit.log")
1001 );
1002 assert_eq!(
1003 audit_log_rotated_plain_path(&legacy_audit_log_path(path), 42),
1004 PathBuf::from("/var/lib/reddb/.audit.log.42")
1005 );
1006 assert_eq!(
1007 audit_log_rotated_compressed_path(&legacy_audit_log_path(path), 42),
1008 PathBuf::from("/var/lib/reddb/.audit.log.42.zst")
1009 );
1010 assert_eq!(
1011 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42"),
1012 Some(42)
1013 );
1014 assert_eq!(
1015 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), ".audit.log.42.zst"),
1016 Some(42)
1017 );
1018 assert_eq!(
1019 parse_audit_log_rotated_timestamp(&legacy_audit_log_path(path), "other.42.zst"),
1020 None
1021 );
1022 assert_eq!(
1023 legacy_slow_query_log_path(Path::new("/var/log/reddb")),
1024 PathBuf::from("/var/log/reddb/red-slow.log")
1025 );
1026 assert_eq!(
1027 serverless_root(path),
1028 PathBuf::from("/var/lib/reddb/main.serverless")
1029 );
1030 assert_eq!(serverless_namespace(path), "main");
1031 assert_eq!(
1032 serverless_cache_root(&serverless_root(path), &serverless_namespace(path)),
1033 PathBuf::from("/var/lib/reddb/main.serverless/main/cache")
1034 );
1035 }
1036
1037 #[test]
1038 fn derives_dedicated_support_sidecars() {
1039 let path = Path::new("/var/lib/reddb/main.rdb");
1040 let support = support_dir_for(path);
1041
1042 assert_eq!(
1043 unified_wal_path_in(&support, path),
1044 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb-uwal")
1045 );
1046 assert_eq!(
1047 logical_wal_path_in(&support, path),
1048 PathBuf::from("/var/lib/reddb/main.rdb.red/wal/main.rdb.logical.wal")
1049 );
1050 assert_eq!(
1051 temp_path_in(&support, path),
1052 PathBuf::from("/var/lib/reddb/main.rdb.red/tmp/main.rdb-tmp")
1053 );
1054 }
1055
1056 #[test]
1057 fn derives_primary_replica_segment_names() {
1058 assert_eq!(
1059 primary_wal_segment_file_name(2),
1060 "00000000000000000002.redwal"
1061 );
1062 assert_eq!(
1063 relay_segment_relative_path(10, 20),
1064 PathBuf::from("relay-00000000000000000010-00000000000000000020.redwal")
1065 );
1066 }
1067
1068 #[test]
1069 fn the_live_pager_sidecar_set_is_empty_after_phase3() {
1070 let path = Path::new("/var/lib/reddb/main.rdb");
1071 let expected: [PathBuf; 0] = [];
1072 assert_eq!(pager_shadow_sidecar_paths(path), expected);
1073 }
1074
1075 #[test]
1076 fn retired_phase1_and_phase3_names_stay_reachable_only_for_migration_and_refusal() {
1077 let path = Path::new("/var/lib/reddb/main.rdb");
1078 assert_eq!(
1081 retired::phase1_sidecar_paths(path),
1082 [
1083 PathBuf::from("/var/lib/reddb/main.rdb-hdr"),
1084 PathBuf::from("/var/lib/reddb/main.rdb-meta"),
1085 PathBuf::from("/var/lib/reddb/main.rdb-hdr"),
1086 PathBuf::from("/var/lib/reddb/main.rdb-meta"),
1087 ]
1088 );
1089 assert_eq!(
1090 retired::phase3_dwb_sidecar_paths(path),
1091 [
1092 PathBuf::from("/var/lib/reddb/main.rdb-dwb"),
1093 PathBuf::from("/var/lib/reddb/main.rdb-dwb"),
1094 ]
1095 );
1096 let l2 = Path::new("/var/lib/reddb/main.result-cache.l2");
1099 assert_eq!(
1100 retired::pager_header_path_v0(l2),
1101 PathBuf::from("/var/lib/reddb/main.result-cache.rdb-hdr")
1102 );
1103 assert_eq!(
1104 retired::pager_header_shadow_path_v0(l2),
1105 PathBuf::from("/var/lib/reddb/main.result-cache.l2-hdr")
1106 );
1107 }
1108}