|
|
@ -42,11 +42,12 @@ func (c *commandVolumeBalance) Help() string { |
|
|
|
idealWritableVolumes = totalWritableVolumes / numVolumeServers |
|
|
|
for hasMovedOneVolume { |
|
|
|
sort all volume servers ordered by the number of local writable volumes |
|
|
|
pick the volume server A with the lowest number of writable volumes x |
|
|
|
pick the volume server B with the highest number of writable volumes y |
|
|
|
if y > idealWritableVolumes and x +1 <= idealWritableVolumes { |
|
|
|
if B has a writable volume id v that A does not have { |
|
|
|
move writable volume v from A to B |
|
|
|
for any the volume server A with the number of writable volumes x +1 <= idealWritableVolume { |
|
|
|
if y > idealWritableVolumes and x +1 <= idealWritableVolumes { |
|
|
|
if B has a writable volume id v that A does not have, and satisfy v replication requirements { |
|
|
|
move writable volume v from A to B |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -185,7 +186,7 @@ func sortReadOnlyVolumes(volumes []*master_pb.VolumeInformationMessage) { |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
func balanceSelectedVolume(commandEnv *CommandEnv, nodes []*Node, sortCandidatesFn func(volumes []*master_pb.VolumeInformationMessage), applyBalancing bool) error { |
|
|
|
func balanceSelectedVolume(commandEnv *CommandEnv, nodes []*Node, sortCandidatesFn func(volumes []*master_pb.VolumeInformationMessage), applyBalancing bool) (err error) { |
|
|
|
selectedVolumeCount := 0 |
|
|
|
for _, dn := range nodes { |
|
|
|
selectedVolumeCount += len(dn.selectedVolumes) |
|
|
@ -193,48 +194,65 @@ func balanceSelectedVolume(commandEnv *CommandEnv, nodes []*Node, sortCandidates |
|
|
|
|
|
|
|
idealSelectedVolumes := ceilDivide(selectedVolumeCount, len(nodes)) |
|
|
|
|
|
|
|
hasMove := true |
|
|
|
hasMoved := true |
|
|
|
|
|
|
|
for hasMove { |
|
|
|
hasMove = false |
|
|
|
for hasMoved { |
|
|
|
hasMoved = false |
|
|
|
sort.Slice(nodes, func(i, j int) bool { |
|
|
|
// TODO sort by free volume slots???
|
|
|
|
return len(nodes[i].selectedVolumes) < len(nodes[j].selectedVolumes) |
|
|
|
}) |
|
|
|
emptyNode, fullNode := nodes[0], nodes[len(nodes)-1] |
|
|
|
if len(fullNode.selectedVolumes) > idealSelectedVolumes && len(emptyNode.selectedVolumes)+1 <= idealSelectedVolumes { |
|
|
|
|
|
|
|
// sort the volumes to move
|
|
|
|
var candidateVolumes []*master_pb.VolumeInformationMessage |
|
|
|
for _, v := range fullNode.selectedVolumes { |
|
|
|
candidateVolumes = append(candidateVolumes, v) |
|
|
|
fullNode := nodes[len(nodes)-1] |
|
|
|
var candidateVolumes []*master_pb.VolumeInformationMessage |
|
|
|
for _, v := range fullNode.selectedVolumes { |
|
|
|
candidateVolumes = append(candidateVolumes, v) |
|
|
|
} |
|
|
|
sortCandidatesFn(candidateVolumes) |
|
|
|
|
|
|
|
for i := 0; i < len(nodes)-1; i++ { |
|
|
|
emptyNode := nodes[i] |
|
|
|
if !(len(fullNode.selectedVolumes) > idealSelectedVolumes && len(emptyNode.selectedVolumes)+1 <= idealSelectedVolumes) { |
|
|
|
// no more volume servers with empty slots
|
|
|
|
break |
|
|
|
} |
|
|
|
sortCandidatesFn(candidateVolumes) |
|
|
|
|
|
|
|
for _, v := range candidateVolumes { |
|
|
|
if v.ReplicaPlacement > 0 { |
|
|
|
if fullNode.dc != emptyNode.dc && fullNode.rack != emptyNode.rack { |
|
|
|
// TODO this logic is too simple, but should work most of the time
|
|
|
|
// Need a correct algorithm to handle all different cases
|
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
if _, found := emptyNode.selectedVolumes[v.Id]; !found { |
|
|
|
if err := moveVolume(commandEnv, v, fullNode, emptyNode, applyBalancing); err == nil { |
|
|
|
delete(fullNode.selectedVolumes, v.Id) |
|
|
|
emptyNode.selectedVolumes[v.Id] = v |
|
|
|
hasMove = true |
|
|
|
break |
|
|
|
} else { |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
hasMoved, err = attemptToMoveOneVolume(commandEnv, fullNode, candidateVolumes, emptyNode, applyBalancing) |
|
|
|
if err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
if hasMoved { |
|
|
|
// moved one volume
|
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
func attemptToMoveOneVolume(commandEnv *CommandEnv, fullNode *Node, candidateVolumes []*master_pb.VolumeInformationMessage, emptyNode *Node, applyBalancing bool) (hasMoved bool, err error) { |
|
|
|
|
|
|
|
for _, v := range candidateVolumes { |
|
|
|
if v.ReplicaPlacement > 0 { |
|
|
|
if fullNode.dc != emptyNode.dc && fullNode.rack != emptyNode.rack { |
|
|
|
// TODO this logic is too simple, but should work most of the time
|
|
|
|
// Need a correct algorithm to handle all different cases
|
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
if _, found := emptyNode.selectedVolumes[v.Id]; !found { |
|
|
|
if err = moveVolume(commandEnv, v, fullNode, emptyNode, applyBalancing); err == nil { |
|
|
|
delete(fullNode.selectedVolumes, v.Id) |
|
|
|
emptyNode.selectedVolumes[v.Id] = v |
|
|
|
hasMoved = true |
|
|
|
break |
|
|
|
} else { |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
func moveVolume(commandEnv *CommandEnv, v *master_pb.VolumeInformationMessage, fullNode *Node, emptyNode *Node, applyBalancing bool) error { |
|
|
|
collectionPrefix := v.Collection + "_" |
|
|
|
if v.Collection == "" { |
|
|
|