knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/hash/bucketer.go (about) 1 /* 2 Copyright 2020 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 https://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // This file contains the utilities to make bucketing decisions. 18 19 package hash 20 21 import ( 22 "sync" 23 24 lru "github.com/hashicorp/golang-lru" 25 "k8s.io/apimachinery/pkg/types" 26 "k8s.io/apimachinery/pkg/util/sets" 27 "knative.dev/pkg/reconciler" 28 ) 29 30 var _ reconciler.Bucket = (*Bucket)(nil) 31 32 // BucketSet answers to what bucket does key X belong in a 33 // consistent manner (consistent as in consistent hashing). 34 type BucketSet struct { 35 // Stores the cached lookups. cache is internally thread safe. 36 cache *lru.Cache 37 38 // mu guards buckets. 39 mu sync.RWMutex 40 // All the bucket names. Needed for building hash universe. 41 buckets sets.Set[string] 42 } 43 44 // Bucket implements reconciler.Bucket and wraps around BuketSet 45 // for bucketing functions. 46 type Bucket struct { 47 name string 48 // `name` must be in this BucketSet.buckets. 49 buckets *BucketSet 50 } 51 52 // Scientifically inferred preferred cache size. 53 const cacheSize = 4096 54 55 func newCache() *lru.Cache { 56 c, _ := lru.New(cacheSize) 57 return c 58 } 59 60 // NewBucketSet creates a new bucket set with the given universe 61 // of bucket names. 62 func NewBucketSet(bucketList sets.Set[string]) *BucketSet { 63 return &BucketSet{ 64 cache: newCache(), 65 buckets: bucketList, 66 } 67 } 68 69 // Name implements Bucket. 70 func (b *Bucket) Name() string { 71 return b.name 72 } 73 74 // Has returns true if this bucket owns the key and 75 // implements reconciler.Bucket interface. 76 func (b *Bucket) Has(nn types.NamespacedName) bool { 77 return b.buckets.Owner(nn.String()) == b.name 78 } 79 80 // Buckets creates a new list of all possible Bucket based on this bucketset 81 // ordered by bucket name. 82 func (bs *BucketSet) Buckets() []reconciler.Bucket { 83 bkts := make([]reconciler.Bucket, len(bs.buckets)) 84 for i, n := range bs.BucketList() { 85 bkts[i] = &Bucket{ 86 name: n, 87 buckets: bs, 88 } 89 } 90 return bkts 91 } 92 93 // Owner returns the owner of the key. 94 // Owner will cache the results for faster lookup. 95 func (bs *BucketSet) Owner(key string) string { 96 if v, ok := bs.cache.Get(key); ok { 97 return v.(string) 98 } 99 bs.mu.RLock() 100 defer bs.mu.RUnlock() 101 ret, ok := GetAny(ChooseSubset(bs.buckets, 1 /*single query wanted*/, key)) 102 if ok { 103 bs.cache.Add(key, ret) 104 } 105 return ret 106 } 107 108 // Returns a single element from the set. 109 func GetAny(s sets.Set[string]) (string, bool) { 110 for key := range s { 111 return key, true 112 } 113 var zeroValue string 114 return zeroValue, false 115 } 116 117 // HasBucket returns true if this BucketSet has the given bucket name. 118 func (bs *BucketSet) HasBucket(bkt string) bool { 119 return bs.buckets.Has(bkt) 120 } 121 122 // BucketList returns the bucket names of this BucketSet in sorted order. 123 func (bs *BucketSet) BucketList() []string { 124 bs.mu.RLock() 125 defer bs.mu.RUnlock() 126 127 return sets.List(bs.buckets) 128 } 129 130 // Update updates the universe of buckets. 131 func (bs *BucketSet) Update(newB sets.Set[string]) { 132 bs.mu.Lock() 133 defer bs.mu.Unlock() 134 // In theory we can iterate over the map and 135 // purge only the keys that moved to a new shard. 136 // But this might be more expensive than re-build 137 // the cache as reconciliations happen. 138 bs.cache.Purge() 139 bs.buckets = newB 140 }