a3s_vec/
storage_ceilings.rs1use crate::error::{Error, Result};
9use serde::{Deserialize, Deserializer, Serialize};
10
11pub const DEFAULT_SNAPSHOT_BYTES: u64 = 8 * 1024 * 1024 * 1024;
13pub const DEFAULT_INDEX_CACHE_BYTES: u64 = 8 * 1024 * 1024 * 1024;
15pub const INDEX_CACHE_FILE_OVERHEAD_BYTES: u64 = 4_096;
17pub const DEFAULT_WAL_REPLAY_BYTES: u64 = 8 * 1024 * 1024 * 1024;
19pub const DEFAULT_DISKANN_FILE_BYTES: u64 = 512 * 1024 * 1024;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
28#[allow(clippy::struct_field_names)]
29pub struct StorageCeilings {
30 #[serde(rename = "max_snapshot_bytes")]
31 snapshot_bytes: u64,
32 #[serde(rename = "max_index_cache_bytes")]
33 index_cache_bytes: u64,
34 #[serde(rename = "max_wal_replay_bytes")]
35 wal_replay_bytes: u64,
36 #[serde(rename = "max_diskann_file_bytes")]
37 diskann_file_bytes: u64,
38}
39
40#[derive(Deserialize, Default)]
41#[serde(default, deny_unknown_fields)]
42#[allow(clippy::struct_field_names)]
43struct StorageCeilingsWire {
44 #[serde(rename = "max_snapshot_bytes")]
45 snapshot_bytes: Option<u64>,
46 #[serde(rename = "max_index_cache_bytes")]
47 index_cache_bytes: Option<u64>,
48 #[serde(rename = "max_wal_replay_bytes")]
49 wal_replay_bytes: Option<u64>,
50 #[serde(rename = "max_diskann_file_bytes")]
51 diskann_file_bytes: Option<u64>,
52}
53
54impl<'de> Deserialize<'de> for StorageCeilings {
55 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
56 where
57 D: Deserializer<'de>,
58 {
59 let wire = StorageCeilingsWire::deserialize(deserializer)?;
60 let mut ceilings = Self::default();
61 if let Some(limit) = wire.snapshot_bytes {
62 ceilings = ceilings
63 .try_with_max_snapshot_bytes(limit)
64 .map_err(serde::de::Error::custom)?;
65 }
66 if let Some(limit) = wire.index_cache_bytes {
67 ceilings = ceilings
68 .try_with_max_index_cache_bytes(limit)
69 .map_err(serde::de::Error::custom)?;
70 }
71 if let Some(limit) = wire.wal_replay_bytes {
72 ceilings = ceilings
73 .try_with_max_wal_replay_bytes(limit)
74 .map_err(serde::de::Error::custom)?;
75 }
76 if let Some(limit) = wire.diskann_file_bytes {
77 ceilings = ceilings
78 .try_with_max_diskann_file_bytes(limit)
79 .map_err(serde::de::Error::custom)?;
80 }
81 Ok(ceilings)
82 }
83}
84
85impl Default for StorageCeilings {
86 fn default() -> Self {
87 Self {
88 snapshot_bytes: DEFAULT_SNAPSHOT_BYTES,
89 index_cache_bytes: DEFAULT_INDEX_CACHE_BYTES,
90 wal_replay_bytes: DEFAULT_WAL_REPLAY_BYTES,
91 diskann_file_bytes: DEFAULT_DISKANN_FILE_BYTES,
92 }
93 }
94}
95
96impl StorageCeilings {
97 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn try_with_max_snapshot_bytes(mut self, limit: u64) -> Result<Self> {
104 self.snapshot_bytes = positive_limit(limit, "max_snapshot_bytes")?;
105 Ok(self)
106 }
107
108 pub fn try_with_max_index_cache_bytes(mut self, limit: u64) -> Result<Self> {
110 self.index_cache_bytes = positive_limit(limit, "max_index_cache_bytes")?;
111 Ok(self)
112 }
113
114 pub fn try_with_max_wal_replay_bytes(mut self, limit: u64) -> Result<Self> {
116 self.wal_replay_bytes = positive_limit(limit, "max_wal_replay_bytes")?;
117 Ok(self)
118 }
119
120 pub fn try_with_max_diskann_file_bytes(mut self, limit: u64) -> Result<Self> {
122 self.diskann_file_bytes = positive_limit(limit, "max_diskann_file_bytes")?;
123 Ok(self)
124 }
125
126 pub fn max_snapshot_bytes(self) -> u64 {
127 self.snapshot_bytes
128 }
129
130 pub fn max_index_cache_bytes(self) -> u64 {
131 self.index_cache_bytes
132 }
133
134 pub fn max_index_cache_file_bytes(self) -> u64 {
136 self.index_cache_bytes
137 .saturating_add(INDEX_CACHE_FILE_OVERHEAD_BYTES)
138 }
139
140 pub fn max_wal_replay_bytes(self) -> u64 {
141 self.wal_replay_bytes
142 }
143
144 pub fn max_diskann_file_bytes(self) -> u64 {
145 self.diskann_file_bytes
146 }
147}
148
149fn positive_limit(limit: u64, name: &str) -> Result<u64> {
150 if limit == 0 {
151 return Err(Error::invalid_argument(format!(
152 "{name} must be positive; omit the field to keep the product default"
153 )));
154 }
155 Ok(limit)
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn defaults_match_published_product_ceilings() {
164 let ceilings = StorageCeilings::default();
165 assert_eq!(ceilings.max_snapshot_bytes(), DEFAULT_SNAPSHOT_BYTES);
166 assert_eq!(ceilings.max_index_cache_bytes(), DEFAULT_INDEX_CACHE_BYTES);
167 assert_eq!(
168 ceilings.max_index_cache_file_bytes(),
169 DEFAULT_INDEX_CACHE_BYTES + INDEX_CACHE_FILE_OVERHEAD_BYTES
170 );
171 assert_eq!(ceilings.max_wal_replay_bytes(), DEFAULT_WAL_REPLAY_BYTES);
172 assert_eq!(
173 ceilings.max_diskann_file_bytes(),
174 DEFAULT_DISKANN_FILE_BYTES
175 );
176 }
177
178 #[test]
179 fn zero_ceilings_are_rejected() {
180 assert!(StorageCeilings::new()
181 .try_with_max_snapshot_bytes(0)
182 .is_err());
183 assert!(StorageCeilings::new()
184 .try_with_max_index_cache_bytes(0)
185 .is_err());
186 assert!(StorageCeilings::new()
187 .try_with_max_wal_replay_bytes(0)
188 .is_err());
189 assert!(StorageCeilings::new()
190 .try_with_max_diskann_file_bytes(0)
191 .is_err());
192 }
193}