ec distribution: never move a shard onto a node already holding it

The move picker selected the destination node before the shard, so a node
already holding the chosen shard could be targeted. Pick the shard first
(parity-first) and choose among destination nodes that lack it.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Chris Lu
2026-10-21 14:11:12 -07:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 0c2e8d279d
commit 2b71843a22
@@ -85,26 +85,32 @@ func (r *Rebalancer) PlanRebalance(analysis *TopologyAnalysis) (*RebalancePlan,
}
moveOut := func(level, srcDomain string, srcNodes, dstNodes []*TopologyNode) bool {
var dst *TopologyNode
for _, n := range dstNodes {
if n.FreeSlots > 0 && (dst == nil || nodeLess(n, dst)) {
dst = n
}
}
if dst == nil {
return false
}
var shardIDs []int
for _, n := range srcNodes {
shardIDs = append(shardIDs, n.ShardIDs...)
}
for _, shardID := range r.ecConfig.SortShardsParityFirst(shardIDs) {
for _, src := range srcNodes {
if contains(src.ShardIDs, shardID) {
recordMove(shardID, src, dst, level, srcDomain)
return true
var src *TopologyNode
for _, n := range srcNodes {
if contains(n.ShardIDs, shardID) {
src = n
break
}
}
if src == nil {
continue
}
var dst *TopologyNode
for _, n := range dstNodes {
if n.FreeSlots > 0 && !contains(n.ShardIDs, shardID) && (dst == nil || nodeLess(n, dst)) {
dst = n
}
}
if dst == nil {
continue
}
recordMove(shardID, src, dst, level, srcDomain)
return true
}
return false
}