use crate::hash::murmur3_x64_128;
#[derive(Debug)]
pub(crate) struct MaglevTable {
table: Box<[u16]>,
}
impl MaglevTable {
#[allow(clippy::indexing_slicing)]
pub(crate) fn build(entries: &[(&str, u32)], m: usize) -> Self {
let n = entries.len();
if n == 0 || entries.iter().all(|(_, w)| *w == 0) || m < 2 {
return Self {
table: Box::default(),
};
}
let mut offset = vec![0usize; n];
let mut skip = vec![0usize; n];
for (i, (name, _)) in entries.iter().enumerate() {
let (h1, h2) = murmur3_x64_128(name.as_bytes(), 0);
offset[i] = (h1 % m as u64) as usize;
skip[i] = (h2 % (m as u64 - 1)) as usize + 1;
}
let weights: Vec<u64> = entries.iter().map(|(_, w)| *w as u64).collect();
let max_w = weights.iter().copied().max().unwrap_or(1).max(1);
debug_assert_eq!(offset.len(), n);
debug_assert_eq!(skip.len(), n);
debug_assert_eq!(weights.len(), n);
let mut target = weights.clone();
let mut next = vec![0usize; n];
let mut entry = vec![-1i64; m];
let mut filled = 0usize;
let mut round = 0u64;
debug_assert_eq!(target.len(), n);
debug_assert_eq!(next.len(), n);
debug_assert_eq!(entry.len(), m);
'fill: while filled < m {
round += 1;
for i in 0..n {
if weights[i] == 0 {
continue; }
if round * weights[i] < target[i] {
continue; }
target[i] += max_w;
let mut c = (offset[i] + next[i].wrapping_mul(skip[i])) % m;
while entry[c] >= 0 {
next[i] += 1;
c = (offset[i] + next[i].wrapping_mul(skip[i])) % m;
}
entry[c] = i as i64;
next[i] += 1;
filled += 1;
if filled == m {
break 'fill;
}
}
}
let table: Vec<u16> = entry.iter().map(|&e| e as u16).collect();
Self {
table: table.into_boxed_slice(),
}
}
#[allow(clippy::indexing_slicing)]
pub(crate) fn lookup(&self, key_hash: u64) -> Option<usize> {
let m = self.table.len();
if m == 0 {
return None;
}
let slot = (key_hash % m as u64) as usize;
debug_assert!(slot < self.table.len());
Some(self.table[slot] as usize)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::hash64;
use std::collections::HashMap;
fn lk(t: &MaglevTable, key: &str) -> usize {
t.lookup(hash64(key.as_bytes())).unwrap()
}
fn counts(entries: &[(&str, u32)], m: usize, draws: usize) -> HashMap<usize, usize> {
let t = MaglevTable::build(entries, m);
let mut c = HashMap::new();
for i in 0..draws {
*c.entry(lk(&t, &format!("key-{i}"))).or_insert(0) += 1;
}
c
}
#[test]
fn empty_or_zero_weight_entries_yield_empty_table_without_hanging() {
let empty = MaglevTable::build(&[], 97);
assert!(empty.lookup(0).is_none());
let zeros = MaglevTable::build(&[("a:1", 0), ("b:2", 0)], 97);
assert!(zeros.lookup(0).is_none());
}
#[test]
fn fills_every_slot_unweighted() {
let entries = [("a:1", 1), ("b:2", 1), ("c:3", 1)];
let m = 97;
let t = MaglevTable::build(&entries, m);
let mut per = [0usize; 3];
for &e in t.table.iter() {
per[e as usize] += 1;
}
assert_eq!(per.iter().sum::<usize>(), m, "table fully filled");
let (lo, hi) = (per.iter().min().unwrap(), per.iter().max().unwrap());
assert!(hi - lo <= 1, "balanced to within one entry: {per:?}");
}
#[test]
fn same_key_maps_to_same_instance() {
let entries = [("a:1", 1), ("b:2", 1), ("c:3", 1)];
let t = MaglevTable::build(&entries, 97);
assert_eq!(lk(&t, "session-xyz"), lk(&t, "session-xyz"));
}
#[test]
fn keys_spread_across_instances() {
let c = counts(&[("a:1", 1), ("b:2", 1), ("c:3", 1)], 97, 3000);
assert_eq!(c.len(), 3, "all three backends receive traffic");
}
#[test]
fn weight_biases_table_share() {
let entries = [("big", 3), ("small", 1)];
let m = 1009;
let t = MaglevTable::build(&entries, m);
let big = t.table.iter().filter(|&&e| e == 0).count();
let small = t.table.iter().filter(|&&e| e == 1).count();
assert_eq!(big + small, m);
let ratio = big as f64 / small as f64;
assert!(
(2.5..3.5).contains(&ratio),
"weight-3 backend should get ~3× share, got ratio {ratio:.2} ({big} vs {small})"
);
}
#[test]
fn tiny_weight_still_gets_an_entry() {
let entries = [("huge", 1000), ("tiny", 1)];
let t = MaglevTable::build(&entries, 1009);
let tiny = t.table.iter().filter(|&&e| e == 1).count();
assert!(
tiny >= 1,
"a positive-weight backend always gets >= 1 entry"
);
}
#[test]
fn single_instance_takes_the_whole_table() {
let t = MaglevTable::build(&[("only", 1)], 17);
assert!(t.table.iter().all(|&e| e == 0));
assert_eq!(lk(&t, "anything"), 0);
}
#[test]
fn minimal_disruption_on_add() {
let three = MaglevTable::build(&[("a", 1), ("b", 1), ("c", 1)], 1009);
let four = MaglevTable::build(&[("a", 1), ("b", 1), ("c", 1), ("d", 1)], 1009);
let moved = (0..5000)
.filter(|i| lk(&three, &format!("key-{i}")) != lk(&four, &format!("key-{i}")))
.count();
assert!(
moved < 5000 / 2,
"adding one of four backends moved {moved}/5000 keys — expected well under half"
);
}
}