#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Partitioner {
#[default]
Crc32,
Murmur2,
}
impl Partitioner {
#[must_use]
pub fn partition_for(
self,
key: Option<&[u8]>,
partition_count: i32,
next: &mut u32,
) -> Option<i32> {
if partition_count <= 0 {
return None;
}
let count = u32::try_from(partition_count).ok()?;
let Some(key) = key else {
let chosen = *next % count;
*next = next.wrapping_add(1);
return i32::try_from(chosen).ok();
};
let slot = match self {
Self::Crc32 => crc32(key) % count,
Self::Murmur2 => {
let h = murmur2(key) & 0x7fff_ffff;
u32::try_from(h).ok()? % count
}
};
i32::try_from(slot).ok()
}
}
#[must_use]
pub fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for &byte in data {
crc ^= u32::from(byte);
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
}
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn murmur2(data: &[u8]) -> i32 {
const SEED: u32 = 0x9747_b28c;
const M: u32 = 0x5bd1_e995;
const R: u32 = 24;
let length = data.len();
let mut h: u32 = SEED ^ (length as u32);
let blocks = length / 4;
for i in 0..blocks {
let i4 = i * 4;
let mut k = u32::from(data[i4])
| (u32::from(data[i4 + 1]) << 8)
| (u32::from(data[i4 + 2]) << 16)
| (u32::from(data[i4 + 3]) << 24);
k = k.wrapping_mul(M);
k ^= k >> R;
k = k.wrapping_mul(M);
h = h.wrapping_mul(M);
h ^= k;
}
let tail = length & !3;
match length % 4 {
3 => {
h ^= u32::from(data[tail + 2]) << 16;
h ^= u32::from(data[tail + 1]) << 8;
h ^= u32::from(data[tail]);
h = h.wrapping_mul(M);
}
2 => {
h ^= u32::from(data[tail + 1]) << 8;
h ^= u32::from(data[tail]);
h = h.wrapping_mul(M);
}
1 => {
h ^= u32::from(data[tail]);
h = h.wrapping_mul(M);
}
_ => {}
}
h ^= h >> 13;
h = h.wrapping_mul(M);
h ^= h >> 15;
h as i32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc32_matches_the_standard_check_value() {
assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
}
#[test]
fn crc32_of_empty_is_zero() {
assert_eq!(crc32(b""), 0);
}
#[test]
fn murmur2_is_stable_across_tail_lengths() {
let hashes: Vec<i32> = ["", "a", "ab", "abc", "abcd", "abcde"]
.iter()
.map(|s| murmur2(s.as_bytes()))
.collect();
for i in 0..hashes.len() {
for j in (i + 1)..hashes.len() {
assert_ne!(hashes[i], hashes[j], "murmur2 collided on short keys");
}
}
assert_eq!(murmur2(b"abc"), hashes[3]);
}
#[test]
fn a_key_is_stable() {
for p in [Partitioner::Crc32, Partitioner::Murmur2] {
let mut counter = 0;
let first = p.partition_for(Some(b"user-42"), 12, &mut counter);
assert!(first.is_some());
for _ in 0..10 {
assert_eq!(p.partition_for(Some(b"user-42"), 12, &mut counter), first);
}
}
}
#[test]
fn partitions_are_in_range_and_spread() {
for p in [Partitioner::Crc32, Partitioner::Murmur2] {
let mut counter = 0;
let mut seen = std::collections::HashSet::new();
for i in 0..2000 {
let key = format!("key-{i}");
let part = p
.partition_for(Some(key.as_bytes()), 8, &mut counter)
.expect("a positive partition count");
assert!((0..8).contains(&part), "{p:?} produced partition {part}");
seen.insert(part);
}
assert_eq!(seen.len(), 8, "{p:?} never used some partitions");
}
}
#[test]
fn the_two_partitioners_disagree() {
let mut c1 = 0;
let mut c2 = 0;
let differing = (0..100).filter(|i| {
let key = format!("key-{i}");
Partitioner::Crc32.partition_for(Some(key.as_bytes()), 16, &mut c1)
!= Partitioner::Murmur2.partition_for(Some(key.as_bytes()), 16, &mut c2)
});
assert!(
differing.count() > 50,
"the CRC-32 and murmur2 partitioners agreed suspiciously often"
);
}
#[test]
fn null_keys_are_spread() {
let mut counter = 0;
let picks: Vec<i32> = (0..6)
.map(|_| {
Partitioner::Crc32
.partition_for(None, 3, &mut counter)
.expect("a positive partition count")
})
.collect();
assert_eq!(picks, vec![0, 1, 2, 0, 1, 2]);
}
#[test]
fn a_keyed_record_does_not_advance_the_round_robin() {
let mut counter = 0;
let _ = Partitioner::Crc32.partition_for(Some(b"k"), 4, &mut counter);
assert_eq!(counter, 0);
let _ = Partitioner::Crc32.partition_for(None, 4, &mut counter);
assert_eq!(counter, 1);
}
#[test]
fn a_topic_with_no_partitions_yields_no_partition() {
let mut counter = 0;
assert_eq!(
Partitioner::Crc32.partition_for(Some(b"k"), 0, &mut counter),
None
);
assert_eq!(
Partitioner::Crc32.partition_for(None, -1, &mut counter),
None
);
}
}