reddb_server/storage/primitives/
split_block_bloom.rs1const SALTS: [u32; 8] = [
27 0x47b6137b, 0x44974d91, 0x8824ad5b, 0xa2b7289d, 0x705495c7, 0x2df1424b, 0x9efc4947, 0x5c6bfb31,
28];
29
30#[repr(align(32))]
32#[derive(Clone, Default, PartialEq, Eq)]
33pub struct Block {
34 words: [u32; 8],
35}
36
37impl std::fmt::Debug for Block {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "Block({:08x?})", &self.words)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct SplitBlockBloom {
47 blocks: Vec<Block>,
48 mask: usize,
50}
51
52impl SplitBlockBloom {
53 pub fn with_capacity(n: usize) -> Self {
55 let bits_needed = (n * 10).max(256);
58 let blocks_needed = bits_needed.div_ceil(256);
59 let num_blocks = blocks_needed.next_power_of_two();
60 Self {
61 blocks: vec![Block::default(); num_blocks],
62 mask: num_blocks - 1,
63 }
64 }
65
66 #[inline]
68 pub fn insert(&mut self, key: u32) {
69 let block_idx = (key as usize) & self.mask;
70 let block = &mut self.blocks[block_idx];
71 for (i, &salt) in SALTS.iter().enumerate() {
72 let bit = key.wrapping_mul(salt) >> 27; block.words[i] |= 1u32 << bit;
74 }
75 }
76
77 #[inline]
79 pub fn insert_bytes(&mut self, key: &[u8]) {
80 let h = stable_hash64(key);
81 let block_idx = ((h >> 32) as usize) & self.mask;
82 let probe_word = h as u32;
83 let block = &mut self.blocks[block_idx];
84 for (i, &salt) in SALTS.iter().enumerate() {
85 let bit = probe_word.wrapping_mul(salt) >> 27;
86 block.words[i] |= 1u32 << bit;
87 }
88 }
89
90 #[inline]
93 pub fn probe(&self, key: u32) -> bool {
94 let block_idx = (key as usize) & self.mask;
95 let block = &self.blocks[block_idx];
96 for (i, &salt) in SALTS.iter().enumerate() {
97 let bit = key.wrapping_mul(salt) >> 27;
98 if block.words[i] & (1u32 << bit) == 0 {
99 return false;
100 }
101 }
102 true
103 }
104
105 #[inline]
108 pub fn probe_bytes(&self, key: &[u8]) -> bool {
109 let h = stable_hash64(key);
110 let block_idx = ((h >> 32) as usize) & self.mask;
111 let probe_word = h as u32;
112 let block = &self.blocks[block_idx];
113 for (i, &salt) in SALTS.iter().enumerate() {
114 let bit = probe_word.wrapping_mul(salt) >> 27;
115 if block.words[i] & (1u32 << bit) == 0 {
116 return false;
117 }
118 }
119 true
120 }
121
122 pub fn num_blocks(&self) -> usize {
124 self.blocks.len()
125 }
126
127 pub fn byte_size(&self) -> usize {
130 self.blocks.len() * 32
131 }
132
133 pub fn union_inplace(&mut self, other: &SplitBlockBloom) -> bool {
137 if self.blocks.len() != other.blocks.len() {
138 return false;
139 }
140 for (left, right) in self.blocks.iter_mut().zip(&other.blocks) {
141 for (left_word, right_word) in left.words.iter_mut().zip(right.words) {
142 *left_word |= right_word;
143 }
144 }
145 true
146 }
147
148 pub fn fill_ratio(&self) -> f64 {
150 let total_words = self.blocks.len() * 8;
151 if total_words == 0 {
152 return 0.0;
153 }
154 let filled_words = self
155 .blocks
156 .iter()
157 .flat_map(|block| block.words)
158 .filter(|word| *word != 0)
159 .count();
160 filled_words as f64 / total_words as f64
161 }
162
163 pub fn to_bytes(&self) -> Vec<u8> {
169 let mut out = Vec::with_capacity(4 + self.blocks.len() * 32);
170 out.extend_from_slice(&(self.blocks.len() as u32).to_le_bytes());
171 for block in &self.blocks {
172 for &w in &block.words {
173 out.extend_from_slice(&w.to_le_bytes());
174 }
175 }
176 out
177 }
178
179 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
184 if bytes.len() < 4 {
185 return None;
186 }
187 let num_blocks = u32::from_le_bytes(bytes[0..4].try_into().ok()?) as usize;
188 if num_blocks == 0 || !num_blocks.is_power_of_two() {
189 return None;
190 }
191 if bytes.len() < 4 + num_blocks * 32 {
192 return None;
193 }
194 let mut blocks = Vec::with_capacity(num_blocks);
195 let mut cur = 4;
196 for _ in 0..num_blocks {
197 let mut words = [0u32; 8];
198 for w in &mut words {
199 *w = u32::from_le_bytes(bytes[cur..cur + 4].try_into().ok()?);
200 cur += 4;
201 }
202 blocks.push(Block { words });
203 }
204 Some(Self {
205 blocks,
206 mask: num_blocks - 1,
207 })
208 }
209}
210
211pub fn stable_hash64(bytes: &[u8]) -> u64 {
218 let mut hash = 0xcbf2_9ce4_8422_2325u64;
219 for &byte in bytes {
220 hash ^= u64::from(byte);
221 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
222 }
223 hash ^= hash >> 33;
224 hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
225 hash ^= hash >> 33;
226 hash = hash.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
227 hash ^ (hash >> 33)
228}
229
230pub fn hash_bytes_u32(bytes: &[u8]) -> u32 {
235 use std::hash::{Hash, Hasher};
236 let mut h = std::collections::hash_map::DefaultHasher::new();
237 bytes.hash(&mut h);
238 let bits = h.finish();
239 (bits ^ (bits >> 32)) as u32
240}
241
242pub fn hash_value_u32(v: &crate::storage::schema::Value) -> u32 {
246 use std::hash::{Hash, Hasher};
247 let mut h = std::collections::hash_map::DefaultHasher::new();
248 v.hash(&mut h);
249 let bits = h.finish();
250 (bits ^ (bits >> 32)) as u32
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_insert_then_probe() {
259 let mut bloom = SplitBlockBloom::with_capacity(100);
260 for i in 0u32..100 {
261 bloom.insert(i);
262 }
263 for i in 0u32..100 {
264 assert!(bloom.probe(i), "false negative for key {i}");
265 }
266 }
267
268 #[test]
269 fn test_absent_key_may_return_false() {
270 let mut bloom = SplitBlockBloom::with_capacity(1000);
271 for i in 0u32..1000 {
272 bloom.insert(i * 2); }
274 for i in 0u32..1000 {
277 assert!(bloom.probe(i * 2), "false negative for key {}", i * 2);
278 }
279 }
280
281 #[test]
282 fn to_bytes_from_bytes_round_trips_and_keeps_no_false_negatives() {
283 let mut bloom = SplitBlockBloom::with_capacity(500);
284 for i in 0u32..500 {
285 bloom.insert(i.wrapping_mul(2_654_435_761));
286 }
287 let blob = bloom.to_bytes();
288 let restored = SplitBlockBloom::from_bytes(&blob).expect("round-trips");
289 assert_eq!(restored, bloom);
290 for i in 0u32..500 {
292 assert!(restored.probe(i.wrapping_mul(2_654_435_761)));
293 }
294 }
295
296 #[test]
297 fn from_bytes_rejects_truncated_or_non_power_of_two() {
298 assert!(SplitBlockBloom::from_bytes(&[]).is_none());
299 assert!(SplitBlockBloom::from_bytes(&[1, 2, 3]).is_none());
300 assert!(SplitBlockBloom::from_bytes(&3u32.to_le_bytes()).is_none());
302 assert!(SplitBlockBloom::from_bytes(&2u32.to_le_bytes()).is_none());
304 }
305
306 #[test]
307 fn hash_bytes_u32_is_stable_across_calls() {
308 let a = hash_bytes_u32(&7u64.to_le_bytes());
309 let b = hash_bytes_u32(&7u64.to_le_bytes());
310 assert_eq!(a, b);
311 }
312
313 #[test]
314 fn test_false_positive_rate_approximately_one_percent() {
315 const N: usize = 10_000;
316 let mut bloom = SplitBlockBloom::with_capacity(N);
317 for i in 0u32..N as u32 {
318 bloom.insert(i);
319 }
320 let mut fp = 0usize;
321 let probes = 10_000usize;
322 for i in N as u32..(N as u32 + probes as u32) {
323 if bloom.probe(i) {
324 fp += 1;
325 }
326 }
327 let fpr = fp as f64 / probes as f64;
328 assert!(fpr < 0.05, "FPR too high: {fpr:.3}");
330 }
331
332 #[test]
333 fn insert_bytes_has_no_false_negatives() {
334 const N: usize = 10_000;
335 let mut bloom = SplitBlockBloom::with_capacity(N);
336 for i in 0..N as u64 {
337 bloom.insert_bytes(&byte_key(i));
338 }
339 for i in 0..N as u64 {
340 assert!(
341 bloom.probe_bytes(&byte_key(i)),
342 "false negative for key {i}"
343 );
344 }
345 }
346
347 #[test]
348 fn insert_bytes_false_positive_rate_stays_below_two_percent() {
349 const N: usize = 10_000;
350 let mut bloom = SplitBlockBloom::with_capacity(N);
351 for i in 0..N as u64 {
352 bloom.insert_bytes(&byte_key(i));
353 }
354 let mut fp = 0usize;
355 let probes = 10_000usize;
356 for i in N as u64..(N + probes) as u64 {
357 if bloom.probe_bytes(&byte_key(i)) {
358 fp += 1;
359 }
360 }
361 let fpr = fp as f64 / probes as f64;
362 assert!(fpr < 0.02, "FPR too high: {fpr:.3}");
363 }
364
365 #[test]
366 fn bytes_round_trip_keeps_no_false_negatives() {
367 const N: usize = 10_000;
368 let mut bloom = SplitBlockBloom::with_capacity(N);
369 for i in 0..N as u64 {
370 bloom.insert_bytes(&byte_key(i));
371 }
372 let blob = bloom.to_bytes();
373 let restored = SplitBlockBloom::from_bytes(&blob).expect("round-trips");
374 assert_eq!(restored, bloom);
375 for i in 0..N as u64 {
376 assert!(
377 restored.probe_bytes(&byte_key(i)),
378 "false negative for key {i}"
379 );
380 }
381 }
382
383 #[test]
384 fn bytes_hashing_agrees_across_filters() {
385 const N: usize = 10_000;
386 let mut a = SplitBlockBloom::with_capacity(N);
387 let mut b = SplitBlockBloom::with_capacity(N);
388 for i in 0..N as u64 {
389 let key = byte_key(i);
390 a.insert_bytes(&key);
391 b.insert_bytes(&key);
392 }
393 assert_eq!(a, b);
394 for i in 0..N as u64 {
395 let key = byte_key(i);
396 assert_eq!(a.probe_bytes(&key), b.probe_bytes(&key));
397 assert!(a.probe_bytes(&key), "false negative for key {i}");
398 }
399 }
400
401 #[test]
402 fn union_inplace_merges_matching_block_counts() {
403 let mut a = SplitBlockBloom::with_capacity(1024);
404 let mut b = SplitBlockBloom::with_capacity(1024);
405 a.insert_bytes(b"one");
406 b.insert_bytes(b"two");
407
408 assert!(a.union_inplace(&b));
409 assert!(a.probe_bytes(b"one"));
410 assert!(a.probe_bytes(b"two"));
411 }
412
413 #[test]
414 fn union_inplace_rejects_mismatched_block_counts() {
415 let mut a = SplitBlockBloom::with_capacity(1024);
416 let b = SplitBlockBloom::with_capacity(4096);
417
418 assert!(!a.union_inplace(&b));
419 }
420
421 fn byte_key(i: u64) -> [u8; 16] {
422 let mut key = [0u8; 16];
423 key[..8].copy_from_slice(&i.to_le_bytes());
424 key[8..].copy_from_slice(&i.wrapping_mul(0x9e37_79b9_7f4a_7c15).to_le_bytes());
425 key
426 }
427}