///|
fn node_index(loads : Array[NodeLoad], id : String) -> Int {
for index = 0; index < loads.length(); index = index + 1 {
if loads[index].node_id == id {
return index
}
}
-1
}
///|
fn placement_zone_violations(
nodes : Array[ShardNode],
owners : Array[String],
) -> Int {
let zones : Array[String] = []
let mut violations = 0
for owner in owners {
match find_node(nodes, owner) {
Some(node) =>
if zones.contains(node.zone) {
violations = violations + 1
} else {
zones.push(node.zone)
}
None => ()
}
}
violations
}
///|
fn placement_rack_violations(
nodes : Array[ShardNode],
owners : Array[String],
) -> Int {
let locations : Array[String] = []
let mut violations = 0
for owner in owners {
match find_node(nodes, owner) {
Some(node) => {
let location = "\{node.zone}/\{node.rack}"
if locations.contains(location) {
violations = violations + 1
} else {
locations.push(location)
}
}
None => ()
}
}
violations
}
///|
/// Measures placement balance and topology constraint outcomes.
pub fn analyze_distribution(
keys : Array[String],
nodes : Array[ShardNode],
replicas? : Int = 1,
salt? : String = "moonshardkit",
) -> DistributionReport {
let loads : Array[NodeLoad] = []
for node in nodes {
if node.is_eligible() {
loads.push({ node_id: node.id, primary_keys: 0, replica_keys: 0 })
}
}
let mut incomplete = 0
let mut zone_violations = 0
let mut rack_violations = 0
for key in keys {
let placement = place_replicas(nodes, key, replicas, salt~)
if !placement.complete {
incomplete = incomplete + 1
}
zone_violations = zone_violations +
placement_zone_violations(nodes, placement.owners)
rack_violations = rack_violations +
placement_rack_violations(nodes, placement.owners)
for owner_index = 0
owner_index < placement.owners.length()
owner_index = owner_index + 1 {
let index = node_index(loads, placement.owners[owner_index])
if index >= 0 {
let current = loads[index]
loads[index] = if owner_index == 0 {
{ ..current, primary_keys: current.primary_keys + 1 }
} else {
{ ..current, replica_keys: current.replica_keys + 1 }
}
}
}
}
let mut min_primary = 0
let mut max_primary = 0
if loads.length() > 0 {
min_primary = loads[0].primary_keys
max_primary = loads[0].primary_keys
for load in loads {
if load.primary_keys < min_primary {
min_primary = load.primary_keys
}
if load.primary_keys > max_primary {
max_primary = load.primary_keys
}
}
}
{
keys: keys.length(),
replicas,
nodes: loads,
min_primary,
max_primary,
spread: max_primary - min_primary,
incomplete_placements: incomplete,
zone_violations,
rack_violations,
}
}