use std::collections::BTreeSet;
use ahash::AHashMap as HashMap;
use super::{MemberSubscription, collect};
use crate::PartitionId;
use crate::consumer::group::MemberAssignment;
type TopicPartition = (String, PartitionId);
pub(super) fn resolve_claims(members: &[MemberSubscription]) -> HashMap<TopicPartition, usize> {
let mut claims: HashMap<TopicPartition, (usize, i32)> = HashMap::new();
for (index, member) in members.iter().enumerate() {
for tp in &member.owned {
match claims.get(tp) {
Some(&(_, generation)) if generation >= member.generation => {}
_ => {
claims.insert(tp.clone(), (index, member.generation));
}
}
}
}
claims
.into_iter()
.map(|(tp, (index, _))| (tp, index))
.collect()
}
pub(super) fn assign(
members: &[MemberSubscription],
topics: &[(String, Vec<PartitionId>)],
) -> HashMap<String, MemberAssignment> {
if members.is_empty() {
return collect(members, Vec::new());
}
let all: Vec<TopicPartition> = topics
.iter()
.flat_map(|(topic, ps)| ps.iter().map(move |&p| (topic.clone(), p)))
.collect();
let exists: std::collections::HashSet<&TopicPartition> = all.iter().collect();
let mut assigned: Vec<BTreeSet<TopicPartition>> = vec![BTreeSet::new(); members.len()];
let mut owner: HashMap<TopicPartition, usize> = HashMap::new();
for (tp, index) in resolve_claims(members) {
if exists.contains(&tp) && members[index].subscribes(&tp.0) {
assigned[index].insert(tp.clone());
owner.insert(tp, index);
}
}
let kept: Vec<BTreeSet<TopicPartition>> = assigned.clone();
for tp in &all {
if owner.contains_key(tp) {
continue;
}
let target = (0..members.len())
.filter(|&i| members[i].subscribes(&tp.0))
.min_by_key(|&i| (assigned[i].len(), i));
if let Some(index) = target {
assigned[index].insert(tp.clone());
owner.insert(tp.clone(), index);
}
}
loop {
let mut by_count: Vec<usize> = (0..members.len()).collect();
by_count.sort_by_key(|&i| (assigned[i].len(), i));
let mut moved = false;
'search: for &to in &by_count {
for &from in by_count.iter().rev() {
if assigned[from].len() < assigned[to].len() + 2 {
break;
}
let candidate = assigned[from]
.iter()
.rev()
.filter(|tp| members[to].subscribes(&tp.0))
.max_by_key(|tp| !kept[from].contains(*tp))
.cloned();
if let Some(tp) = candidate {
assigned[from].remove(&tp);
assigned[to].insert(tp.clone());
owner.insert(tp, to);
moved = true;
break 'search;
}
}
}
if !moved {
break;
}
}
collect(members, owner)
}