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  }