reddb_server/storage/cache/blob/
config.rs1use std::path::{Path, PathBuf};
2
3pub const DEFAULT_BLOB_L1_BYTES_MAX: usize = 256 * 1024 * 1024;
4pub const DEFAULT_BLOB_NAMESPACE_BYTES_MAX: usize = DEFAULT_BLOB_L1_BYTES_MAX;
5pub const DEFAULT_BLOB_L2_BYTES_MAX: u64 = 4 * 1024 * 1024 * 1024;
6pub const DEFAULT_BLOB_MAX_NAMESPACES: usize = 256;
7pub const DEFAULT_BLOB_SHARDS: usize = 64;
8pub const DEFAULT_CONTENT_METADATA_KEYS_MAX: usize = 32;
9pub const DEFAULT_CONTENT_METADATA_BYTES_MAX: usize = 4 * 1024;
10pub const METRIC_CACHE_BLOB_L1_BYTES_IN_USE: &str = "cache_blob_l1_bytes_in_use";
11pub const METRIC_CACHE_VERSION_MISMATCH_TOTAL: &str = "cache_version_mismatch_total";
12pub const METRIC_CACHE_BLOB_L2_BYTES_IN_USE: &str = "reddb_cache_blob_l2_bytes_in_use";
13pub const METRIC_CACHE_BLOB_L2_FULL_REJECTIONS_TOTAL: &str =
14 "reddb_cache_blob_l2_full_rejections_total";
15pub const METRIC_CACHE_BLOB_SYNOPSIS_METADATA_READS_TOTAL: &str =
16 "cache_blob_synopsis_metadata_reads_total";
17pub const METRIC_CACHE_BLOB_SYNOPSIS_BYTES: &str = "cache_blob_synopsis_bytes";
18
19pub const DEFAULT_BLOB_SYNOPSIS_CAPACITY: usize = 10_000;
23
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
26pub enum L2PromotionPolicy {
27 Immediate,
28 #[default]
29 OnSecondHit,
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub enum L2Compression {
41 Off,
42 #[default]
43 On,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct BlobCacheConfig {
48 pub(super) l1_bytes_max: usize,
49 pub(super) namespace_bytes_max: usize,
50 pub(super) l2_bytes_max: u64,
51 pub(super) l2_path: Option<PathBuf>,
52 pub(super) max_namespaces: usize,
53 pub(super) shard_count: usize,
54 pub(super) content_metadata_keys_max: usize,
55 pub(super) content_metadata_bytes_max: usize,
56 pub(super) l2_compression: L2Compression,
57 pub(super) l2_promotion_policy: L2PromotionPolicy,
58}
59
60impl Default for BlobCacheConfig {
61 fn default() -> Self {
62 Self {
63 l1_bytes_max: DEFAULT_BLOB_L1_BYTES_MAX,
64 namespace_bytes_max: DEFAULT_BLOB_NAMESPACE_BYTES_MAX,
65 l2_bytes_max: DEFAULT_BLOB_L2_BYTES_MAX,
66 l2_path: None,
67 max_namespaces: DEFAULT_BLOB_MAX_NAMESPACES,
68 shard_count: DEFAULT_BLOB_SHARDS,
69 content_metadata_keys_max: DEFAULT_CONTENT_METADATA_KEYS_MAX,
70 content_metadata_bytes_max: DEFAULT_CONTENT_METADATA_BYTES_MAX,
71 l2_compression: L2Compression::default(),
72 l2_promotion_policy: L2PromotionPolicy::default(),
73 }
74 }
75}
76
77impl BlobCacheConfig {
78 pub fn builder() -> BlobCacheConfigBuilder {
83 BlobCacheConfigBuilder::new()
84 }
85
86 pub fn with_l1_bytes_max(mut self, l1_bytes_max: usize) -> Self {
87 self.l1_bytes_max = l1_bytes_max;
88 self
89 }
90
91 pub fn with_l2_bytes_max(mut self, l2_bytes_max: u64) -> Self {
92 self.l2_bytes_max = l2_bytes_max;
93 self
94 }
95
96 pub fn with_namespace_bytes_max(mut self, namespace_bytes_max: usize) -> Self {
97 self.namespace_bytes_max = namespace_bytes_max;
98 self
99 }
100
101 pub fn with_l2_path(mut self, path: impl Into<PathBuf>) -> Self {
102 self.l2_path = Some(path.into());
103 self
104 }
105
106 pub fn with_max_namespaces(mut self, max_namespaces: usize) -> Self {
107 self.max_namespaces = max_namespaces;
108 self
109 }
110
111 pub fn with_shard_count(mut self, shard_count: usize) -> Self {
112 self.shard_count = shard_count.max(1);
113 self
114 }
115
116 pub fn with_content_metadata_limits(mut self, keys_max: usize, bytes_max: usize) -> Self {
117 self.content_metadata_keys_max = keys_max;
118 self.content_metadata_bytes_max = bytes_max;
119 self
120 }
121
122 pub fn with_l2_compression(mut self, compression: L2Compression) -> Self {
123 self.l2_compression = compression;
124 self
125 }
126
127 pub fn with_l2_promotion_policy(mut self, policy: L2PromotionPolicy) -> Self {
128 self.l2_promotion_policy = policy;
129 self
130 }
131
132 pub fn l1_bytes_max(&self) -> usize {
133 self.l1_bytes_max
134 }
135
136 pub fn l2_bytes_max(&self) -> u64 {
137 self.l2_bytes_max
138 }
139
140 pub fn namespace_bytes_max(&self) -> usize {
141 self.namespace_bytes_max
142 }
143
144 pub fn l2_path(&self) -> Option<&Path> {
145 self.l2_path.as_deref()
146 }
147
148 pub fn max_namespaces(&self) -> usize {
149 self.max_namespaces
150 }
151
152 pub fn shard_count(&self) -> usize {
153 self.shard_count
154 }
155
156 pub fn content_metadata_keys_max(&self) -> usize {
157 self.content_metadata_keys_max
158 }
159
160 pub fn content_metadata_bytes_max(&self) -> usize {
161 self.content_metadata_bytes_max
162 }
163
164 pub fn l2_compression(&self) -> L2Compression {
165 self.l2_compression
166 }
167
168 pub fn l2_promotion_policy(&self) -> L2PromotionPolicy {
169 self.l2_promotion_policy
170 }
171}
172
173#[derive(Debug, Clone)]
178pub struct BlobCacheConfigBuilder {
179 inner: BlobCacheConfig,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub enum BlobCacheConfigError {
184 ZeroShardCount,
186 ZeroMaxNamespaces,
188 ZeroNamespaceBytesMax,
190}
191
192impl BlobCacheConfigBuilder {
193 fn new() -> Self {
194 Self {
195 inner: BlobCacheConfig::default(),
196 }
197 }
198
199 pub fn l1_bytes_max(mut self, value: usize) -> Self {
200 self.inner.l1_bytes_max = value;
201 self
202 }
203
204 pub fn l2_bytes_max(mut self, value: u64) -> Self {
205 self.inner.l2_bytes_max = value;
206 self
207 }
208
209 pub fn namespace_bytes_max(mut self, value: usize) -> Self {
210 self.inner.namespace_bytes_max = value;
211 self
212 }
213
214 pub fn l2_path(mut self, path: impl Into<PathBuf>) -> Self {
215 self.inner.l2_path = Some(path.into());
216 self
217 }
218
219 pub fn max_namespaces(mut self, value: usize) -> Self {
220 self.inner.max_namespaces = value;
221 self
222 }
223
224 pub fn shard_count(mut self, value: usize) -> Self {
225 self.inner.shard_count = value;
226 self
227 }
228
229 pub fn content_metadata_keys_max(mut self, value: usize) -> Self {
230 self.inner.content_metadata_keys_max = value;
231 self
232 }
233
234 pub fn content_metadata_bytes_max(mut self, value: usize) -> Self {
235 self.inner.content_metadata_bytes_max = value;
236 self
237 }
238
239 pub fn l2_compression(mut self, value: L2Compression) -> Self {
240 self.inner.l2_compression = value;
241 self
242 }
243
244 pub fn l2_promotion_policy(mut self, value: L2PromotionPolicy) -> Self {
245 self.inner.l2_promotion_policy = value;
246 self
247 }
248
249 pub fn try_build(self) -> Result<BlobCacheConfig, BlobCacheConfigError> {
250 if self.inner.shard_count == 0 {
251 return Err(BlobCacheConfigError::ZeroShardCount);
252 }
253 if self.inner.max_namespaces == 0 {
254 return Err(BlobCacheConfigError::ZeroMaxNamespaces);
255 }
256 if self.inner.namespace_bytes_max == 0 {
257 return Err(BlobCacheConfigError::ZeroNamespaceBytesMax);
258 }
259 Ok(self.inner)
260 }
261
262 pub fn build(self) -> BlobCacheConfig {
265 self.try_build().expect("blob cache config")
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn blob_cache_config_builder_rejects_zero_shard_count() {
275 let err = BlobCacheConfig::builder()
276 .shard_count(0)
277 .try_build()
278 .expect_err("zero shard count must be rejected");
279 assert_eq!(err, BlobCacheConfigError::ZeroShardCount);
280 }
281
282 #[test]
283 fn blob_cache_config_builder_rejects_zero_max_namespaces() {
284 let err = BlobCacheConfig::builder()
285 .max_namespaces(0)
286 .try_build()
287 .expect_err("zero max_namespaces must be rejected");
288 assert_eq!(err, BlobCacheConfigError::ZeroMaxNamespaces);
289 }
290
291 #[test]
292 fn blob_cache_config_builder_rejects_zero_namespace_budget() {
293 let err = BlobCacheConfig::builder()
294 .namespace_bytes_max(0)
295 .try_build()
296 .expect_err("zero namespace_bytes_max must be rejected");
297 assert_eq!(err, BlobCacheConfigError::ZeroNamespaceBytesMax);
298 }
299}