-
Notifications
You must be signed in to change notification settings - Fork 0
/
gbucket.go
148 lines (114 loc) · 3.04 KB
/
gbucket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package gbucket
import (
"errors"
"fmt"
"github.com/spaolacci/murmur3"
)
const (
seed = 1000
slots = 1000
)
type Bucket struct {
Percentage float64
Bucketname string
}
type Allocations struct {
Mappings []Allocation
}
type Allocation struct {
Name string
Percentage float64
MinRange int
MaxRange int
}
// Generates hash Value between 0-10000
func generteHash(Id string) int {
hasher := murmur3.New32WithSeed(seed)
hasher.Write([]byte(Id))
// generates int hash value in range of 0-10000
hashval := int(hasher.Sum32() % slots)
return hashval
}
// function to validate if total percentage is less than or equal to 100
func validatePercentageSplit(buckets []Bucket) bool {
totalPercentage := 0.00
for _, bucket := range buckets {
totalPercentage = totalPercentage + bucket.Percentage
}
return totalPercentage <= 100
}
// Creates allocation mappings for bucket allocation
// returns allocation and err if any
func CreateAllocations(buckets []Bucket) (*Allocations, error) {
if !validatePercentageSplit(buckets) {
return nil, errors.New("total percentage should be <= 100")
}
mappings := make([]Allocation, len(buckets))
currentmin := 0
for index, bucket := range buckets {
maxLimit := bucket.Percentage / 100 * 10000
maxrange := currentmin + int(maxLimit)
mappings[index] = Allocation{
MinRange: currentmin,
MaxRange: maxrange,
Percentage: bucket.Percentage,
Name: bucket.Bucketname,
}
currentmin = maxrange + 1
}
return &Allocations{Mappings: mappings}, nil
}
// function to get bucket allocation for an id
// returns name of the bucket if allocated or else empty string
func (all *Allocations) GetBucketAllocation(Id string) string {
hashval := generteHash(Id)
result := ""
for _, allocation := range all.Mappings {
if hashval >= allocation.MinRange && hashval <= allocation.MaxRange {
result = allocation.Name
break
}
}
return result
}
var ErrUnsupportedKeyType = errors.New("unsupported key type")
type Hasher interface {
Hash(interface{}) (int, error)
}
// Default implementation of Hasher interface,
// which can hash key of string or int types
type DefaultHasher struct{}
func (dh *DefaultHasher) Hash(key interface{}) (int, error) {
switch k := key.(type) {
case string:
h := murmur3.New32WithSeed(seed)
_, err := h.Write([]byte(k))
if err != nil {
return -1, err
}
return int(h.Sum32()), nil
case int:
return k, nil
default:
return -1, ErrUnsupportedKeyType
}
}
// AllAllocBktUsingHasher tries to allocate a bucket to the specified key using
func (a *Allocations) AllocBktUsingHasher(h Hasher, key interface{}) (string, error) {
// use default hasher if no custom hasher was provided
if h == nil {
h = &DefaultHasher{}
}
// calculate the hash and bound it in available slots
hash, err := h.Hash(key)
if err != nil {
return "", err
}
hash = hash % slots
for _, alloc := range a.Mappings {
if hash >= alloc.MinRange && hash <= alloc.MaxRange {
return alloc.Name, nil
}
}
return "", fmt.Errorf("unable to allocate a bucket to `%v`", key)
}