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_L2_BYTES_MAX: u64 = 4 * 1024 * 1024 * 1024;
5pub const DEFAULT_BLOB_MAX_NAMESPACES: usize = 256;
6pub const DEFAULT_BLOB_SHARDS: usize = 64;
7pub const DEFAULT_CONTENT_METADATA_KEYS_MAX: usize = 32;
8pub const DEFAULT_CONTENT_METADATA_BYTES_MAX: usize = 4 * 1024;
9pub const METRIC_CACHE_BLOB_L1_BYTES_IN_USE: &str = "cache_blob_l1_bytes_in_use";
10pub const METRIC_CACHE_VERSION_MISMATCH_TOTAL: &str = "cache_version_mismatch_total";
11pub const METRIC_CACHE_BLOB_L2_BYTES_IN_USE: &str = "reddb_cache_blob_l2_bytes_in_use";
12pub const METRIC_CACHE_BLOB_L2_FULL_REJECTIONS_TOTAL: &str =
13 "reddb_cache_blob_l2_full_rejections_total";
14pub const METRIC_CACHE_BLOB_SYNOPSIS_METADATA_READS_TOTAL: &str =
15 "cache_blob_synopsis_metadata_reads_total";
16pub const METRIC_CACHE_BLOB_SYNOPSIS_BYTES: &str = "cache_blob_synopsis_bytes";
17
18pub const DEFAULT_BLOB_SYNOPSIS_CAPACITY: usize = 10_000;
21pub const DEFAULT_BLOB_SYNOPSIS_FPR: f64 = 0.01;
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31pub enum L2Compression {
32 Off,
33 #[default]
34 On,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct BlobCacheConfig {
39 pub(super) l1_bytes_max: usize,
40 pub(super) l2_bytes_max: u64,
41 pub(super) l2_path: Option<PathBuf>,
42 pub(super) max_namespaces: usize,
43 pub(super) shard_count: usize,
44 pub(super) content_metadata_keys_max: usize,
45 pub(super) content_metadata_bytes_max: usize,
46 pub(super) l2_compression: L2Compression,
47}
48
49impl Default for BlobCacheConfig {
50 fn default() -> Self {
51 Self {
52 l1_bytes_max: DEFAULT_BLOB_L1_BYTES_MAX,
53 l2_bytes_max: DEFAULT_BLOB_L2_BYTES_MAX,
54 l2_path: None,
55 max_namespaces: DEFAULT_BLOB_MAX_NAMESPACES,
56 shard_count: DEFAULT_BLOB_SHARDS,
57 content_metadata_keys_max: DEFAULT_CONTENT_METADATA_KEYS_MAX,
58 content_metadata_bytes_max: DEFAULT_CONTENT_METADATA_BYTES_MAX,
59 l2_compression: L2Compression::default(),
60 }
61 }
62}
63
64impl BlobCacheConfig {
65 pub fn builder() -> BlobCacheConfigBuilder {
70 BlobCacheConfigBuilder::new()
71 }
72
73 pub fn with_l1_bytes_max(mut self, l1_bytes_max: usize) -> Self {
74 self.l1_bytes_max = l1_bytes_max;
75 self
76 }
77
78 pub fn with_l2_bytes_max(mut self, l2_bytes_max: u64) -> Self {
79 self.l2_bytes_max = l2_bytes_max;
80 self
81 }
82
83 pub fn with_l2_path(mut self, path: impl Into<PathBuf>) -> Self {
84 self.l2_path = Some(path.into());
85 self
86 }
87
88 pub fn with_max_namespaces(mut self, max_namespaces: usize) -> Self {
89 self.max_namespaces = max_namespaces;
90 self
91 }
92
93 pub fn with_shard_count(mut self, shard_count: usize) -> Self {
94 self.shard_count = shard_count.max(1);
95 self
96 }
97
98 pub fn with_content_metadata_limits(mut self, keys_max: usize, bytes_max: usize) -> Self {
99 self.content_metadata_keys_max = keys_max;
100 self.content_metadata_bytes_max = bytes_max;
101 self
102 }
103
104 pub fn with_l2_compression(mut self, compression: L2Compression) -> Self {
105 self.l2_compression = compression;
106 self
107 }
108
109 pub fn l1_bytes_max(&self) -> usize {
110 self.l1_bytes_max
111 }
112
113 pub fn l2_bytes_max(&self) -> u64 {
114 self.l2_bytes_max
115 }
116
117 pub fn l2_path(&self) -> Option<&Path> {
118 self.l2_path.as_deref()
119 }
120
121 pub fn max_namespaces(&self) -> usize {
122 self.max_namespaces
123 }
124
125 pub fn shard_count(&self) -> usize {
126 self.shard_count
127 }
128
129 pub fn content_metadata_keys_max(&self) -> usize {
130 self.content_metadata_keys_max
131 }
132
133 pub fn content_metadata_bytes_max(&self) -> usize {
134 self.content_metadata_bytes_max
135 }
136
137 pub fn l2_compression(&self) -> L2Compression {
138 self.l2_compression
139 }
140}
141
142#[derive(Debug, Clone)]
147pub struct BlobCacheConfigBuilder {
148 inner: BlobCacheConfig,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum BlobCacheConfigError {
153 ZeroShardCount,
155 ZeroMaxNamespaces,
157}
158
159impl BlobCacheConfigBuilder {
160 fn new() -> Self {
161 Self {
162 inner: BlobCacheConfig::default(),
163 }
164 }
165
166 pub fn l1_bytes_max(mut self, value: usize) -> Self {
167 self.inner.l1_bytes_max = value;
168 self
169 }
170
171 pub fn l2_bytes_max(mut self, value: u64) -> Self {
172 self.inner.l2_bytes_max = value;
173 self
174 }
175
176 pub fn l2_path(mut self, path: impl Into<PathBuf>) -> Self {
177 self.inner.l2_path = Some(path.into());
178 self
179 }
180
181 pub fn max_namespaces(mut self, value: usize) -> Self {
182 self.inner.max_namespaces = value;
183 self
184 }
185
186 pub fn shard_count(mut self, value: usize) -> Self {
187 self.inner.shard_count = value;
188 self
189 }
190
191 pub fn content_metadata_keys_max(mut self, value: usize) -> Self {
192 self.inner.content_metadata_keys_max = value;
193 self
194 }
195
196 pub fn content_metadata_bytes_max(mut self, value: usize) -> Self {
197 self.inner.content_metadata_bytes_max = value;
198 self
199 }
200
201 pub fn l2_compression(mut self, value: L2Compression) -> Self {
202 self.inner.l2_compression = value;
203 self
204 }
205
206 pub fn try_build(self) -> Result<BlobCacheConfig, BlobCacheConfigError> {
207 if self.inner.shard_count == 0 {
208 return Err(BlobCacheConfigError::ZeroShardCount);
209 }
210 if self.inner.max_namespaces == 0 {
211 return Err(BlobCacheConfigError::ZeroMaxNamespaces);
212 }
213 Ok(self.inner)
214 }
215
216 pub fn build(self) -> BlobCacheConfig {
219 self.try_build().expect("blob cache config")
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn blob_cache_config_builder_rejects_zero_shard_count() {
229 let err = BlobCacheConfig::builder()
230 .shard_count(0)
231 .try_build()
232 .expect_err("zero shard count must be rejected");
233 assert_eq!(err, BlobCacheConfigError::ZeroShardCount);
234 }
235
236 #[test]
237 fn blob_cache_config_builder_rejects_zero_max_namespaces() {
238 let err = BlobCacheConfig::builder()
239 .max_namespaces(0)
240 .try_build()
241 .expect_err("zero max_namespaces must be rejected");
242 assert_eq!(err, BlobCacheConfigError::ZeroMaxNamespaces);
243 }
244}