-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch sharding struct from array to map
- Loading branch information
1 parent
07366c6
commit f0b6827
Showing
13 changed files
with
656 additions
and
683 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package sharding | ||
|
||
import ( | ||
"hash/fnv" | ||
"math" | ||
) | ||
|
||
type rendezvousShardSelector struct { | ||
keyMap map[uint64]string | ||
weightMap map[uint64]uint32 | ||
} | ||
|
||
func hashServer(key string) uint64 { | ||
h := fnv.New64a() | ||
h.Write([]byte(key)) | ||
return h.Sum64() | ||
} | ||
|
||
func NewRendezvousShardSelector(weights map[string]uint32) *rendezvousShardSelector { | ||
weightMap := make(map[uint64]uint32, len(weights)) | ||
keyMap := make(map[uint64]string, len(weights)) | ||
|
||
for key, weight := range weights { | ||
keyHash := hashServer(key) | ||
keyMap[keyHash] = key | ||
weightMap[keyHash] = weight | ||
} | ||
return &rendezvousShardSelector{ | ||
keyMap: keyMap, | ||
weightMap: weightMap, | ||
} | ||
} | ||
|
||
func score(x uint64) float64 { | ||
// branchless clamp to [1,MAX_UINT64-1] | ||
x = x - ((x|-x)>>63) + (((^x)|-(^x)) >> 63) | ||
frac := float64(x)/float64(^uint64(0)) | ||
return 1.0/-math.Log(frac) | ||
} | ||
|
||
// PRNG without branches or allocations | ||
func splitmix64(x uint64) uint64 { | ||
x ^= x >> 30 | ||
x *= 0xbf58476d1ce4e5b9 | ||
x ^= x >> 27 | ||
x *= 0x94d049bb133111eb | ||
x ^= x >> 31 | ||
return x | ||
} | ||
|
||
func (s *rendezvousShardSelector) GetShard(hash uint64) string { | ||
var best float64 | ||
var bestKey string | ||
|
||
for keyHash, weight := range s.weightMap { | ||
mixed := splitmix64(hash^keyHash) | ||
current := float64(weight) * score(mixed) | ||
if current > best { | ||
best = current | ||
bestKey = s.keyMap[keyHash] | ||
} | ||
} | ||
return bestKey | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package sharding_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/buildbarn/bb-storage/pkg/blobstore/sharding" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestRendezvousShardSelectorDistribution(t *testing.T) { | ||
// Distribution across five backends with a total weight of 15. | ||
weights := map[string]uint32{"a": 1, "b": 4, "c:": 2, "d": 5, "e": 3} | ||
s := sharding.NewRendezvousShardSelector(weights) | ||
|
||
// Request the shard for a very large amount of blobs | ||
occurrences := map[string]uint32{} | ||
for i := 0; i < 1000000; i++ { | ||
hash := uint64(i) | ||
occurrences[s.GetShard(hash)] += 1 | ||
} | ||
|
||
// Requests should be fanned out with a small error margin. | ||
for shard, weight := range weights { | ||
require.InEpsilon(t, weight*1000000/15, occurrences[shard], 0.01) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package sharding | ||
|
||
// ShardSelector is an algorithm that for a hash resolves into a key which | ||
// corresponds to the specific backend for that shard. | ||
// | ||
// The algorithm must be stable, the removal of an unavailable backend should | ||
// not result in the reshuffling of any other blobs. | ||
type ShardSelector interface { | ||
GetShard(hash uint64) string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.