pub fn assign_hierarchical<W, A, V, S>(
workers: &BTreeMap<W, NonZeroUsize>,
actors: &[A],
virtual_nodes: &[V],
salt: S,
actor_capacity_mode: CapacityMode,
balanced_by: BalancedBy,
) -> Result<BTreeMap<W, BTreeMap<A, Vec<V>>>>Expand description
Hierarchically distributes virtual nodes to actors in two weighted stages with deterministic tie-breaking.
This function first assigns each actor to a worker, then distributes all virtual nodes among those active workers, and finally partitions each worker’s vnodes among its actors in a simple round-robin fashion.
§Type Parameters
W: Worker identifier. Must implementOrd + Hash + Eq + Clone + Debug.A: Actor identifier. Must implementOrd + Hash + Eq + Copy + Clone + Debug.V: Virtual node type. Must implementHash + Eq + Copy + Clone + Debug.S: Salt type for deterministic tie-breaking. Must implementHash + Copy.
§Parameters
workers: ABTreeMap<W, NonZeroUsize>mapping each worker to its positive weight.actors: A slice of actors (&[A]) to place on workers.virtual_nodes: A slice of vnodes (&[V]) to distribute across actors.salt: A salt value to break ties in hashing, kept constant per invocation for reproducibility.actor_capacity_mode: ACapacityModedeciding how actors are packed onto workers:Weighted: respectworkersweights when placing actors.Unbounded: ignore capacity limits when placing actors.
balanced_by: ABalancedByenum determining vnode distribution strategy:RawWorkerWeights: prioritize original worker weights (with actor count as lower bound).ActorCounts: prioritize equal vnode counts per actor (actor-oriented).
§Returns
A BTreeMap<W, BTreeMap<A, Vec<V>>> mapping each worker to its map of actors and their assigned vnodes.
- Only workers with at least one actor appear in the result.
- Each actor receives at least one vnode (invariant).
§Errors
- Returns an error if
actorsis empty orvirtual_nodesis empty. - Returns an error if
actors.len() > virtual_nodes.len(), since each actor must receive at least one vnode.
§Complexity
Runs in O((W + A + V) · log W + V · W) time:
- Actor → Worker assignment is O(A · W) via weighted rendezvous + O(W + A) map operations.
- VNode → Worker assignment is O(V · W) plus quota computation O(W log W).
- VNode → Actor partition is O(V).
§Example
// Define two workers with numeric IDs and weights
let mut workers: BTreeMap<u8, NonZeroUsize> = BTreeMap::new();
workers.insert(1, NonZeroUsize::new(2).unwrap());
workers.insert(2, NonZeroUsize::new(3).unwrap());
// Actors also identified by numbers
let actors: Vec<u16> = vec![10, 20, 30];
// Virtual nodes are simple 0–8
let vnodes: Vec<u16> = (0..9).collect();
let assignment = assign_hierarchical(
&workers,
&actors,
&vnodes,
0u8, // salt
CapacityMode::Weighted, // actor -> worker mode
BalancedBy::RawWorkerWeights, // vnode -> worker mode
)
.unwrap();
for (worker_id, actor_map) in assignment {
println!("Worker {}:", worker_id);
for (actor_id, vn_list) in actor_map {
println!(" Actor {} -> {:?}", actor_id, vn_list);
}
}§Algorithm
-
Actors → Workers
- Use weighted or unbounded rendezvous hashing to assign each actor to exactly one worker,
based on
actor_capacity_modeandworkersweights. - Build
actor_to_worker: BTreeMap<W, Vec<A>>.
- Use weighted or unbounded rendezvous hashing to assign each actor to exactly one worker,
based on
-
VNodes→ Workers- If
RawWorkerWeights: compute per-worker quotas withcompute_worker_quotas, ensuring each active worker’s quota ≥ its actor count and quotas sum = total vnodes. - If
ActorCounts: set each worker’s weight = its actor count. - Run
assign_items_weighted_with_scale_fnon vnodes vs. the computed weights, yieldingvnode_to_worker: BTreeMap<W, Vec<V>>.
- If
-
VNodes→ Actors- For each worker, take its vnode list and assign them to actors in simple round-robin:
iterate vnodes in order, dispatching index
% actor_list.len(). - Collect into final
BTreeMap<W, BTreeMap<A, Vec<V>>>.
- For each worker, take its vnode list and assign them to actors in simple round-robin:
iterate vnodes in order, dispatching index