use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FragmentId(pub u32);
impl fmt::Display for FragmentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "frag-{}", self.0)
}
}
pub fn hash_key(key: &[u8]) -> u64 {
seahash::hash(key)
}
pub fn fragment_of(key: &str, frag_count: usize) -> FragmentId {
assert!(frag_count > 0, "fragment count must be non-zero");
FragmentId((hash_key(key.as_bytes()) % frag_count as u64) as u32)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn fragment_ids_are_in_range() {
for i in 0..1000 {
let key = format!("key-{i}");
let frag = fragment_of(&key, 16);
assert!(frag.0 < 16, "out of range: {frag}");
}
}
#[test]
fn mapping_is_deterministic() {
for _ in 0..100 {
assert_eq!(fragment_of("hello", 8), fragment_of("hello", 8));
}
}
#[test]
fn distribution_is_roughly_uniform() {
let mut counts = [0usize; 16];
for i in 0..10_000 {
counts[fragment_of(&format!("key-{i}"), 16).0 as usize] += 1;
}
for (i, c) in counts.iter().enumerate() {
assert!(
(400..=900).contains(c),
"fragment {i} got {c}/10000 keys, expected ~625"
);
}
}
#[test]
fn all_fragments_reachable() {
let mut seen = HashSet::new();
for i in 0..10_000 {
seen.insert(fragment_of(&format!("k{i}"), 8));
}
assert_eq!(seen.len(), 8, "some fragments never hit");
}
}