reddb_server/cluster/
slot.rs1pub const PRODUCTION_HASH_SLOT_COUNT: u16 = 16_384;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct HashSlot(u16);
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct HashSlotError {
17 attempted: u16,
18}
19
20impl HashSlotError {
21 pub fn attempted(self) -> u16 {
22 self.attempted
23 }
24}
25
26impl std::fmt::Display for HashSlotError {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(
29 f,
30 "hash slot {} is outside the valid 0..{} range",
31 self.attempted, PRODUCTION_HASH_SLOT_COUNT
32 )
33 }
34}
35
36impl std::error::Error for HashSlotError {}
37
38impl HashSlot {
39 pub fn new(value: u16) -> Result<Self, HashSlotError> {
41 if value >= PRODUCTION_HASH_SLOT_COUNT {
42 return Err(HashSlotError { attempted: value });
43 }
44 Ok(Self(value))
45 }
46
47 pub fn value(self) -> u16 {
48 self.0
49 }
50
51 pub fn range_key(self) -> [u8; 2] {
59 self.0.to_be_bytes()
60 }
61}
62
63pub fn hash_shard_key_to_slot(shard_key: &[u8]) -> HashSlot {
65 let digest = blake3::hash(shard_key);
66 let mut prefix = [0u8; 8];
67 prefix.copy_from_slice(&digest.as_bytes()[..8]);
68 let slot = (u64::from_be_bytes(prefix) % u64::from(PRODUCTION_HASH_SLOT_COUNT)) as u16;
69 HashSlot(slot)
70}
71
72pub fn hash_shard_key_to_range_key(shard_key: &[u8]) -> [u8; 2] {
74 hash_shard_key_to_slot(shard_key).range_key()
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn hash_slots_are_bounded_by_the_production_slot_count() {
83 assert_eq!(HashSlot::new(0).unwrap().value(), 0);
84 assert_eq!(
85 HashSlot::new(PRODUCTION_HASH_SLOT_COUNT - 1)
86 .unwrap()
87 .value(),
88 PRODUCTION_HASH_SLOT_COUNT - 1
89 );
90 let err = HashSlot::new(PRODUCTION_HASH_SLOT_COUNT).unwrap_err();
91 assert_eq!(err.attempted(), PRODUCTION_HASH_SLOT_COUNT);
92 }
93
94 #[test]
95 fn range_key_preserves_numeric_slot_order() {
96 let before = HashSlot::new(255).unwrap().range_key();
97 let after = HashSlot::new(256).unwrap().range_key();
98 assert!(before < after);
99 }
100
101 #[test]
102 fn shard_key_hashing_is_stable_and_in_range() {
103 let first = hash_shard_key_to_slot(b"tenant:42");
104 let second = hash_shard_key_to_slot(b"tenant:42");
105 assert_eq!(first, second);
106 assert!(first.value() < PRODUCTION_HASH_SLOT_COUNT);
107 }
108}