metrale_storage/
layout.rs1use anyhow::{Context, Result};
15use std::fs::{File, OpenOptions};
16#[cfg(unix)]
17use std::os::fd::{AsRawFd, RawFd};
18use std::path::{Path, PathBuf};
19
20use crate::group::{GroupKey, GroupLayout};
21
22pub struct Layout {
23 pub dir: PathBuf,
24 pub spec: GroupLayout,
25 files: Vec<File>,
28}
29
30impl Layout {
31 pub fn create(dir: &Path, spec: GroupLayout) -> Result<Self> {
32 std::fs::create_dir_all(dir).with_context(|| format!("mkdir {}", dir.display()))?;
33 let mut files = Vec::with_capacity(spec.num_layers as usize);
34 for layer in 0..spec.num_layers {
35 let p = dir.join(format!("layer_{layer:05}.kv"));
36 let mut opts = OpenOptions::new();
37 opts.read(true).write(true).create(true).truncate(false);
38 set_direct_flag(&mut opts);
39 let f = opts
40 .open(&p)
41 .with_context(|| format!("open {}", p.display()))?;
42 preallocate(&f, spec.bytes_per_layer())
43 .with_context(|| format!("preallocate {}", p.display()))?;
44 files.push(f);
45 }
46 Ok(Self {
47 dir: dir.to_path_buf(),
48 spec,
49 files,
50 })
51 }
52
53 pub fn open(dir: &Path, spec: GroupLayout) -> Result<Self> {
56 let mut files = Vec::with_capacity(spec.num_layers as usize);
57 for layer in 0..spec.num_layers {
58 let p = dir.join(format!("layer_{layer:05}.kv"));
59 let mut opts = OpenOptions::new();
60 opts.read(true).write(true);
61 set_direct_flag(&mut opts);
62 let f = opts
63 .open(&p)
64 .with_context(|| format!("open {}", p.display()))?;
65 let len = f.metadata()?.len();
66 if len < spec.bytes_per_layer() {
67 anyhow::bail!(
68 "layer file {} is undersized: {} < {}",
69 p.display(),
70 len,
71 spec.bytes_per_layer()
72 );
73 }
74 files.push(f);
75 }
76 Ok(Self {
77 dir: dir.to_path_buf(),
78 spec,
79 files,
80 })
81 }
82
83 #[cfg(unix)]
86 pub fn fd(&self, layer: u32) -> RawFd {
87 self.files[layer as usize].as_raw_fd()
88 }
89
90 pub fn file(&self, layer: u32) -> &File {
93 &self.files[layer as usize]
94 }
95
96 pub fn offset(&self, key: GroupKey) -> u64 {
97 self.spec.file_offset(key)
98 }
99
100 pub fn group_bytes(&self) -> u64 {
101 self.spec.group_bytes()
102 }
103}
104
105#[cfg(target_os = "linux")]
106fn set_direct_flag(opts: &mut OpenOptions) {
107 use std::os::unix::fs::OpenOptionsExt;
108 opts.custom_flags(libc::O_DIRECT);
109}
110
111#[cfg(not(target_os = "linux"))]
112fn set_direct_flag(_opts: &mut OpenOptions) {}
113
114#[cfg(unix)]
115fn preallocate(file: &File, size: u64) -> Result<()> {
116 let fd = file.as_raw_fd();
119 let res = unsafe { libc::posix_fallocate(fd, 0, size as libc::off_t) };
120 if res != 0 {
121 anyhow::bail!("posix_fallocate({size}) failed: {res}");
122 }
123 Ok(())
124}
125
126#[cfg(windows)]
127fn preallocate(file: &File, size: u64) -> Result<()> {
128 file.set_len(size)
129 .with_context(|| format!("set_len({size})"))?;
130 Ok(())
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::group::{GroupKey, KvKind};
137
138 #[test]
139 fn create_open_round_trip() {
140 let tmp = tempdir();
141 let spec = GroupLayout::new(2, 4, 2, 16, 128, 2, 4096);
142 {
143 let l = Layout::create(&tmp, spec).unwrap();
144 assert_eq!(l.spec.num_layers, 2);
145 let p = tmp.join("layer_00000.kv");
146 let len = std::fs::metadata(&p).unwrap().len();
147 assert_eq!(len, spec.bytes_per_layer());
148 }
149 {
150 let l = Layout::open(&tmp, spec).unwrap();
151 let off = l.offset(GroupKey::new(0, 1, 1, KvKind::V));
152 assert_eq!(off, spec.file_offset(GroupKey::new(0, 1, 1, KvKind::V)));
153 }
154 std::fs::remove_dir_all(&tmp).ok();
155 }
156
157 fn tempdir() -> PathBuf {
158 let p = std::env::temp_dir().join(format!("metrale-storage-test-{}", std::process::id()));
159 std::fs::create_dir_all(&p).unwrap();
160 p
161 }
162}