forked from TrueCloudLab/frostfs-sdk-go
netmap: sort buckets by HRW value
If weights for some buckets are equal, result depends on the initial positions of buckets in slice. Thus we sort buckets by value using hash of the first node as a have for the whole bucket. This is ok, because buckets are already sorted by HRW. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
f7582399ed
commit
3edaf9ecb6
3 changed files with 192 additions and 2 deletions
|
@ -3,13 +3,190 @@ package netmap
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/hrw"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
testv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkHRWSort(b *testing.B) {
|
||||
const netmapSize = 1000
|
||||
|
||||
nodes := make([]Nodes, netmapSize)
|
||||
weights := make([]float64, netmapSize)
|
||||
for i := range nodes {
|
||||
nodes[i] = Nodes{{
|
||||
ID: rand.Uint64(),
|
||||
Index: i,
|
||||
Capacity: 100,
|
||||
Price: 1,
|
||||
AttrMap: nil,
|
||||
}}
|
||||
weights[i] = float64(rand.Uint32()%10) / 10.0
|
||||
}
|
||||
|
||||
pivot := rand.Uint64()
|
||||
b.Run("sort by index, no weight", func(b *testing.B) {
|
||||
realNodes := make([]Nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
copy(realNodes, nodes)
|
||||
b.StartTimer()
|
||||
|
||||
hrw.SortSliceByIndex(realNodes, pivot)
|
||||
}
|
||||
})
|
||||
b.Run("sort by value, no weight", func(b *testing.B) {
|
||||
realNodes := make([]Nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
copy(realNodes, nodes)
|
||||
b.StartTimer()
|
||||
|
||||
hrw.SortSliceByValue(realNodes, pivot)
|
||||
}
|
||||
})
|
||||
b.Run("only sort by index", func(b *testing.B) {
|
||||
realNodes := make([]Nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
copy(realNodes, nodes)
|
||||
b.StartTimer()
|
||||
|
||||
hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
|
||||
}
|
||||
})
|
||||
b.Run("sort by value", func(b *testing.B) {
|
||||
realNodes := make([]Nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
copy(realNodes, nodes)
|
||||
b.StartTimer()
|
||||
|
||||
hrw.SortSliceByWeightValue(realNodes, weights, pivot)
|
||||
}
|
||||
})
|
||||
b.Run("sort by ID, then by index (deterministic)", func(b *testing.B) {
|
||||
realNodes := make([]Nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
copy(realNodes, nodes)
|
||||
b.StartTimer()
|
||||
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
return nodes[i][0].ID < nodes[j][0].ID
|
||||
})
|
||||
hrw.SortSliceByWeightIndex(realNodes, weights, pivot)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPolicyHRWType(b *testing.B) {
|
||||
const netmapSize = 100
|
||||
|
||||
p := newPlacementPolicy(1,
|
||||
[]*Replica{
|
||||
newReplica(1, "loc1"),
|
||||
newReplica(1, "loc2")},
|
||||
[]*Selector{
|
||||
newSelector("loc1", "Location", ClauseSame, 1, "loc1"),
|
||||
newSelector("loc2", "Location", ClauseSame, 1, "loc2")},
|
||||
[]*Filter{
|
||||
newFilter("loc1", "Location", "Shanghai", OpEQ),
|
||||
newFilter("loc2", "Location", "Shanghai", OpNE),
|
||||
})
|
||||
|
||||
nodes := make([]NodeInfo, netmapSize)
|
||||
for i := range nodes {
|
||||
var loc string
|
||||
switch i % 20 {
|
||||
case 0:
|
||||
loc = "Shanghai"
|
||||
default:
|
||||
loc = strconv.Itoa(i % 20)
|
||||
}
|
||||
|
||||
// Having the same price and capacity ensures equal weights for all nodes.
|
||||
// This way placement is more dependent on the initial order.
|
||||
nodes[i] = nodeInfoFromAttributes("Location", loc, "Price", "1", "Capacity", "10")
|
||||
pub := make([]byte, 33)
|
||||
pub[0] = byte(i)
|
||||
nodes[i].SetPublicKey(pub)
|
||||
}
|
||||
|
||||
nm, err := NewNetmap(NodesFromInfo(nodes))
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := nm.GetContainerNodes(p, []byte{1})
|
||||
if err != nil {
|
||||
b.Fatal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
||||
const netmapSize = 100
|
||||
|
||||
p := newPlacementPolicy(1,
|
||||
[]*Replica{
|
||||
newReplica(1, "loc1"),
|
||||
newReplica(1, "loc2")},
|
||||
[]*Selector{
|
||||
newSelector("loc1", "Location", ClauseSame, 1, "loc1"),
|
||||
newSelector("loc2", "Location", ClauseSame, 1, "loc2")},
|
||||
[]*Filter{
|
||||
newFilter("loc1", "Location", "Shanghai", OpEQ),
|
||||
newFilter("loc2", "Location", "Shanghai", OpNE),
|
||||
})
|
||||
|
||||
nodes := make([]NodeInfo, netmapSize)
|
||||
for i := range nodes {
|
||||
var loc string
|
||||
switch i % 20 {
|
||||
case 0:
|
||||
loc = "Shanghai"
|
||||
default:
|
||||
loc = strconv.Itoa(i % 20)
|
||||
}
|
||||
|
||||
// Having the same price and capacity ensures equal weights for all nodes.
|
||||
// This way placement is more dependent on the initial order.
|
||||
nodes[i] = nodeInfoFromAttributes("Location", loc, "Price", "1", "Capacity", "10")
|
||||
pub := make([]byte, 33)
|
||||
pub[0] = byte(i)
|
||||
nodes[i].SetPublicKey(pub)
|
||||
}
|
||||
|
||||
nm, err := NewNetmap(NodesFromInfo(nodes))
|
||||
require.NoError(t, err)
|
||||
getIndices := func(t *testing.T) (int, int) {
|
||||
v, err := nm.GetContainerNodes(p, []byte{1})
|
||||
require.NoError(t, err)
|
||||
ns := v.Flatten()
|
||||
require.Equal(t, 2, len(ns))
|
||||
return ns[0].Index, ns[1].Index
|
||||
}
|
||||
|
||||
a, b := getIndices(t)
|
||||
for i := 0; i < 10; i++ {
|
||||
x, y := getIndices(t)
|
||||
require.Equal(t, a, x)
|
||||
require.Equal(t, b, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlacementPolicy_UnspecifiedClause(t *testing.T) {
|
||||
p := newPlacementPolicy(1,
|
||||
[]*Replica{newReplica(1, "X")},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue