
Smooth Weighted Round Robin
The Smooth Weighted Round Robin (SWRR) is a more advanced version of the Weighted Round Robin (WRR), that distributes output more evenly. The name “Smooth Weighted Round Robin” was popularized by the NGINX implementation of the Weighted Round Robin and is actually not a well known name outside that field.
Functionality
For example, lets imagine we have:
Greg (weight 3)
Trevor (weight 2)
Mondy (weight 1)
and we let the algorithm choose for 6 rounds.
The WRR would output it like this:
Greg, Greg, Greg, Trevor, Trevor, Mondy
The SWRR however, would have the following output:
Greg, Trevor, Greg, Mondy, Trevor, Greg
Go implementation
To implement this, we need one simple struct, that has a weight and a currentWeight. The name only exists to distinguish between the objects.
type WeightedObject struct {
name string
weight float32
currentWeight float32
}
The weight stays constant and only currentWeight changes frequently because currentWeight keeps track of which element should be chosen next.
Now let's define how many rounds we want and also create our main function, where we initialize our slice with our WeightedObjects in it.
const rounds = 6
func main() {
weights := []WeightedObject{
{name: "Greg", weight: 3},
{name: "Trevor", weight: 2},
{name: "Mondy", weight: 1},
}
// We start with our weights also being
// the currentWeights
for i := range weights {
weights[i].currentWeight = weights[i].weight
}
}
We need to get our total weight amount, which is later used to change currentWeight. So let's create a function for it.
func getTotalWeightAmount(weights []WeightedObject) float32 {
var total float32
for _, weight := range weights {
total += weight.weight
}
return total
}
While we're at it, we can also create another function we will need, that gets us our the element with the biggest currentWeight.
func getBiggestCurrentWeight(weights []WeightedObject) *WeightedObject {
biggestWeightIndex := 0
for i := range weights {
if weights[i].currentWeight > weights[biggestWeightIndex].currentWeight {
biggestWeightIndex = i
}
}
return &weights[biggestWeightIndex]
}
We return here a pointer because we need to alter currentWeight of the struct later.
Now we have all it takes to finish the algorithm. We can finally finish our main function.
func main() {
...
totalWeights := getTotalWeightAmount(weights)
var selectedWeights []string
// Here we simulate the selection
for range rounds {
biggestWeight := getBiggestCurrentWeight(weights)
// We remove the totalWeights
biggestWeight.currentWeight -= totalWeights
// and add the weights of the objects again
for i := range weights {
weights[i].currentWeight += weights[i].weight
}
selectedWeights = append(selectedWeights, biggestWeight.name)
}
// To see our output, we print it here
for _, weight := range selectedWeights {
fmt.Println(weight)
}
}
You can see it’s a really simple algorithm. By subtracting the total weight of the objects and readding the initial weight to everyone, the biggest weight slowly loses his weight until a smaller weight catches up and becomes the biggest one.
Thus, this is happening each round:
TotalWeight = Greg (3) + Trevor (2) + Mondy (1) = 6
We choose the biggest weight, which is Greg.
Greg currently has the weight 3, but now loses 6, so he now has -3
Now everyone gets their original weight added again, so Greg now has 0, because his original weight was 3
So Greg = 0, Trevor = 4 and Mondy = 2
Now the cycle starts all over again and we choose Trevor because he has the biggest weight.
We substract him the TotalWeight so now he has -2
Everyone gets their weight added again: Greg = 3, Trevor = 0 and Mondy = 3
.
.
.
Sources
This post is mainly inspired by the commit by mdounin where Smooth Weighted Round Robin is explained with an example.
You can find the full implementation of the Smooth Weighted Round Robin and other algorithms here.