From 2e84f1931a6f56b37941521707d576718b3784ee Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Fri, 2 Aug 2019 10:40:07 -0400 Subject: [PATCH 1/4] chunk/cache: support consistent hashing in cache.NewMemcachedClient cache.MemcachedClientConfig has been updated with a new boolean variable ConsistentHash, available as consistent_hash in yaml and memcached.consistent-hash as a flag. When ConsistentHash is true, the MemcachedClient will use the newly created cache.MemcachedJumpHashSelector for server distribution. Jump hash is a consistent hashing algorithm that given a key and a number of buckets, returns a bucket number in the range [0, numBuckets). Adding or removing a bucket only results in 1/N keys being moved. A downside to using jump hash is that buckets can not be arbitrarily removed from the list; it effectively acts as a stack and only supports adding or removing buckets from the end. Therefore, jump hash is most effective when the servers are ordered and where the order is predicable. A good example of this is Kubernete's StatefulSet with a headless service. DNS names will be in the form memcached-[pod number], where the pod number will grow and shrink in the way that numBuckets does. There will never be a gap in the servers when scaling up or down. Signed-off-by: Robert Fratto --- go.mod | 1 + pkg/chunk/cache/memcached_client.go | 21 ++- pkg/chunk/cache/memcached_client_selector.go | 126 ++++++++++++++++++ .../cache/memcached_client_selector_test.go | 40 ++++++ 4 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 pkg/chunk/cache/memcached_client_selector.go create mode 100644 pkg/chunk/cache/memcached_client_selector_test.go diff --git a/go.mod b/go.mod index 1b210ae860a..c2c848decca 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d github.com/cenkalti/backoff v1.0.0 // indirect + github.com/cespare/xxhash v1.1.0 github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/cznic/ql v1.2.0 // indirect diff --git a/pkg/chunk/cache/memcached_client.go b/pkg/chunk/cache/memcached_client.go index 43e40cbbbaa..d8ece7594db 100644 --- a/pkg/chunk/cache/memcached_client.go +++ b/pkg/chunk/cache/memcached_client.go @@ -19,11 +19,16 @@ type MemcachedClient interface { Set(item *memcache.Item) error } +type serverSelector interface { + memcache.ServerSelector + SetServers(servers ...string) error +} + // memcachedClient is a memcache client that gets its server list from SRV // records, and periodically updates that ServerList. type memcachedClient struct { *memcache.Client - serverList *memcache.ServerList + serverList serverSelector hostname string service string @@ -38,6 +43,7 @@ type MemcachedClientConfig struct { Timeout time.Duration `yaml:"timeout,omitempty"` MaxIdleConns int `yaml:"max_idle_conns,omitempty"` UpdateInterval time.Duration `yaml:"update_interval,omitempty"` + ConsistentHash bool `yaml:"consistent_hash,omitempty"` } // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet @@ -47,19 +53,26 @@ func (cfg *MemcachedClientConfig) RegisterFlagsWithPrefix(prefix, description st f.IntVar(&cfg.MaxIdleConns, prefix+"memcached.max-idle-conns", 16, description+"Maximum number of idle connections in pool.") f.DurationVar(&cfg.Timeout, prefix+"memcached.timeout", 100*time.Millisecond, description+"Maximum time to wait before giving up on memcached requests.") f.DurationVar(&cfg.UpdateInterval, prefix+"memcached.update-interval", 1*time.Minute, description+"Period with which to poll DNS for memcache servers.") + f.BoolVar(&cfg.ConsistentHash, prefix+"memcached.consistent-hash", false, description+"Use consistent hashing to distribute to memcache servers.") } // NewMemcachedClient creates a new MemcacheClient that gets its server list // from SRV and updates the server list on a regular basis. func NewMemcachedClient(cfg MemcachedClientConfig) MemcachedClient { - var servers memcache.ServerList - client := memcache.NewFromSelector(&servers) + var selector serverSelector + if cfg.ConsistentHash { + selector = &MemcachedJumpHashSelector{} + } else { + selector = &memcache.ServerList{} + } + + client := memcache.NewFromSelector(selector) client.Timeout = cfg.Timeout client.MaxIdleConns = cfg.MaxIdleConns newClient := &memcachedClient{ Client: client, - serverList: &servers, + serverList: selector, hostname: cfg.Host, service: cfg.Service, quit: make(chan struct{}), diff --git a/pkg/chunk/cache/memcached_client_selector.go b/pkg/chunk/cache/memcached_client_selector.go new file mode 100644 index 00000000000..24cb7f14d92 --- /dev/null +++ b/pkg/chunk/cache/memcached_client_selector.go @@ -0,0 +1,126 @@ +package cache + +import ( + "net" + "strings" + "sync" + + "github.com/bradfitz/gomemcache/memcache" + "github.com/cespare/xxhash" +) + +// MemcachedJumpHashSelector implements the memcache.ServerSelector +// interface. MemcachedJumpHashSelector utilizes a jump hash to +// distribute keys to servers. +// +// While adding or removing servers only requires 1/N keys to move, +// servers are treated as a stack and can only be pushed/popped. +// Therefore, MemcachedJumpHashSelector works best for servers +// with consistent DNS names where the order doesn't arbitrarily +// change. +type MemcachedJumpHashSelector struct { + mu sync.RWMutex + addrs []net.Addr +} + +// staticAddr caches the Network() and String() values from +// any net.Addr. +// +// Copied from github.com/bradfitz/gomemcache/selector.go. +type staticAddr struct { + network, str string +} + +func newStaticAddr(a net.Addr) net.Addr { + return &staticAddr{ + network: a.Network(), + str: a.String(), + } +} + +func (a *staticAddr) Network() string { return a.network } +func (a *staticAddr) String() string { return a.str } + +// SetServers changes a MemcachedJumpHashSelector's set of servers at +// runtime and is safe for concurrent use by multiple goroutines. +// +// Each server is given equal weight. A server is given more weight +// if it's listed multiple times. +// +// SetServers returns an error if any of the server names fail to +// resolve. No attempt is made to connect to the server. If any +// error occurs, no changes are made to the internal server list. +// +// To minimize number of rehashes for keys when growing or shrinking +// the number of servers, servers should be provided in as consistent +// of an order as possible between invocations. +func (s *MemcachedJumpHashSelector) SetServers(servers ...string) error { + naddrs := make([]net.Addr, len(servers)) + for i, server := range servers { + if strings.Contains(server, "/") { + addr, err := net.ResolveUnixAddr("unix", server) + if err != nil { + return err + } + naddrs[i] = newStaticAddr(addr) + } else { + tcpAddr, err := net.ResolveTCPAddr("tcp", server) + if err != nil { + return err + } + naddrs[i] = newStaticAddr(tcpAddr) + } + } + + s.mu.Lock() + defer s.mu.Unlock() + s.addrs = naddrs + return nil +} + +// jumpHash consistently chooses a hash bucket number in the range [0, numBuckets) for the given key. +// numBuckets must be >= 1. +// +// Copied from github.com/dgryski/go-jump/blob/master/jump.go +func jumpHash(key uint64, numBuckets int) int32 { + + var b int64 = -1 + var j int64 + + for j < int64(numBuckets) { + b = j + key = key*2862933555777941757 + 1 + j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) + } + + return int32(b) +} + +// PickServer returns the server address that a given item +// should be shared onto. +func (s *MemcachedJumpHashSelector) PickServer(key string) (net.Addr, error) { + s.mu.RLock() + defer s.mu.RUnlock() + if len(s.addrs) == 0 { + return nil, memcache.ErrNoServers + } else if len(s.addrs) == 1 { + return s.addrs[0], nil + } + cs := xxhash.Sum64String(key) + idx := jumpHash(cs, len(s.addrs)) + return s.addrs[idx], nil +} + +// Each iterates over each server and calls the given function. +// If f returns a non-nil error, iteration will stop and that +// error will be returned. +func (s *MemcachedJumpHashSelector) Each(f func(net.Addr) error) error { + s.mu.RLock() + defer s.mu.RUnlock() + for _, def := range s.addrs { + if err := f(def); err != nil { + return err + } + } + return nil +} diff --git a/pkg/chunk/cache/memcached_client_selector_test.go b/pkg/chunk/cache/memcached_client_selector_test.go new file mode 100644 index 00000000000..866ef1bc451 --- /dev/null +++ b/pkg/chunk/cache/memcached_client_selector_test.go @@ -0,0 +1,40 @@ +package cache_test + +import ( + "fmt" + "net" + "testing" + + "github.com/bradfitz/gomemcache/memcache" + "github.com/cortexproject/cortex/pkg/chunk/cache" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMemcachedJumpHashSelector_PickSever(t *testing.T) { + s := cache.MemcachedJumpHashSelector{} + err := s.SetServers("google.com:80", "microsoft.com:80", "duckduckgo.com:80") + require.NoError(t, err) + + distribution := make(map[net.Addr]int) + + for i := 0; i < 100; i++ { + key := fmt.Sprintf("key-%d", i) + addr, err := s.PickServer(key) + if assert.NoError(t, err) { + distribution[addr]++ + } + } + + // All of the servers should have been returned at least + // once + for _, v := range distribution { + assert.NotZero(t, v) + } +} + +func TestMemcachedJumpHashSelector_PickSever_ErrNoServers(t *testing.T) { + s := cache.MemcachedJumpHashSelector{} + _, err := s.PickServer("foo") + assert.Error(t, memcache.ErrNoServers, err) +} From 5920ed2ffe85874a31e288472430f4202ec114ca Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Mon, 12 Aug 2019 12:08:48 -0400 Subject: [PATCH 2/4] chunk/cache: Add natural sort in jump hash server selector Signed-off-by: Robert Fratto --- go.mod | 1 + go.sum | 2 + pkg/chunk/cache/memcached_client_selector.go | 19 ++-- .../cache/memcached_client_selector_test.go | 16 +-- vendor/github.com/facette/natsort/LICENSE | 29 +++++ vendor/github.com/facette/natsort/README.md | 104 ++++++++++++++++++ vendor/github.com/facette/natsort/natsort.go | 85 ++++++++++++++ vendor/modules.txt | 2 + 8 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 vendor/github.com/facette/natsort/LICENSE create mode 100644 vendor/github.com/facette/natsort/README.md create mode 100644 vendor/github.com/facette/natsort/natsort.go diff --git a/go.mod b/go.mod index c2c848decca..9f210c8c88f 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/cznic/ql v1.2.0 // indirect + github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb github.com/fluent/fluent-logger-golang v1.2.1 // indirect github.com/fsouza/fake-gcs-server v1.3.0 github.com/go-kit/kit v0.8.0 diff --git a/go.sum b/go.sum index bad6d42b083..fca54830c08 100644 --- a/go.sum +++ b/go.sum @@ -129,6 +129,8 @@ github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= +github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fluent/fluent-logger-golang v1.2.1 h1:CMA+mw2zMiOGEOarZtaqM3GBWT1IVLNncNi0nKELtmU= github.com/fluent/fluent-logger-golang v1.2.1/go.mod h1:2/HCT/jTy78yGyeNGQLGQsjF3zzzAuy6Xlk6FCMV5eU= diff --git a/pkg/chunk/cache/memcached_client_selector.go b/pkg/chunk/cache/memcached_client_selector.go index 24cb7f14d92..e3e1c960ca7 100644 --- a/pkg/chunk/cache/memcached_client_selector.go +++ b/pkg/chunk/cache/memcached_client_selector.go @@ -7,6 +7,7 @@ import ( "github.com/bradfitz/gomemcache/memcache" "github.com/cespare/xxhash" + "github.com/facette/natsort" ) // MemcachedJumpHashSelector implements the memcache.ServerSelector @@ -16,8 +17,8 @@ import ( // While adding or removing servers only requires 1/N keys to move, // servers are treated as a stack and can only be pushed/popped. // Therefore, MemcachedJumpHashSelector works best for servers -// with consistent DNS names where the order doesn't arbitrarily -// change. +// with consistent DNS names where the naturally sorted order +// is predictable. type MemcachedJumpHashSelector struct { mu sync.RWMutex addrs []net.Addr @@ -51,12 +52,16 @@ func (a *staticAddr) String() string { return a.str } // resolve. No attempt is made to connect to the server. If any // error occurs, no changes are made to the internal server list. // -// To minimize number of rehashes for keys when growing or shrinking -// the number of servers, servers should be provided in as consistent -// of an order as possible between invocations. +// To minimize the number of rehashes for keys when scaling the +// number of servers in subsequent calls to SetServers, servers +// are stored in natural sort order. func (s *MemcachedJumpHashSelector) SetServers(servers ...string) error { - naddrs := make([]net.Addr, len(servers)) - for i, server := range servers { + sortedServers := make([]string, len(servers)) + copy(sortedServers, servers) + natsort.Sort(sortedServers) + + naddrs := make([]net.Addr, len(sortedServers)) + for i, server := range sortedServers { if strings.Contains(server, "/") { addr, err := net.ResolveUnixAddr("unix", server) if err != nil { diff --git a/pkg/chunk/cache/memcached_client_selector_test.go b/pkg/chunk/cache/memcached_client_selector_test.go index 866ef1bc451..0d638c41223 100644 --- a/pkg/chunk/cache/memcached_client_selector_test.go +++ b/pkg/chunk/cache/memcached_client_selector_test.go @@ -2,12 +2,10 @@ package cache_test import ( "fmt" - "net" "testing" "github.com/bradfitz/gomemcache/memcache" "github.com/cortexproject/cortex/pkg/chunk/cache" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,25 +14,27 @@ func TestMemcachedJumpHashSelector_PickSever(t *testing.T) { err := s.SetServers("google.com:80", "microsoft.com:80", "duckduckgo.com:80") require.NoError(t, err) - distribution := make(map[net.Addr]int) + // We store the string representation instead of the net.Addr + // to make sure different IPs were discovered during SetServers + distribution := make(map[string]int) for i := 0; i < 100; i++ { key := fmt.Sprintf("key-%d", i) addr, err := s.PickServer(key) - if assert.NoError(t, err) { - distribution[addr]++ - } + require.NoError(t, err) + distribution[addr.String()]++ } // All of the servers should have been returned at least // once + require.Len(t, distribution, 3) for _, v := range distribution { - assert.NotZero(t, v) + require.NotZero(t, v) } } func TestMemcachedJumpHashSelector_PickSever_ErrNoServers(t *testing.T) { s := cache.MemcachedJumpHashSelector{} _, err := s.PickServer("foo") - assert.Error(t, memcache.ErrNoServers, err) + require.Error(t, memcache.ErrNoServers, err) } diff --git a/vendor/github.com/facette/natsort/LICENSE b/vendor/github.com/facette/natsort/LICENSE new file mode 100644 index 00000000000..cc8f232b387 --- /dev/null +++ b/vendor/github.com/facette/natsort/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2015, Vincent Batoufflet and Marc Falzon +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the authors nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/facette/natsort/README.md b/vendor/github.com/facette/natsort/README.md new file mode 100644 index 00000000000..a4362b7283f --- /dev/null +++ b/vendor/github.com/facette/natsort/README.md @@ -0,0 +1,104 @@ +# natsort: natural strings sorting in Go + +This is an implementation of the "Alphanum Algorithm" by [Dave Koelle][0] in Go. + +[![GoDoc](https://godoc.org/facette.io/natsort?status.svg)](https://godoc.org/facette.io/natsort) + +## Usage + +```go +package main + +import ( + "fmt" + "strings" + + "facette.io/natsort" +) + +func main() { + list := []string{ + "1000X Radonius Maximus", + "10X Radonius", + "200X Radonius", + "20X Radonius", + "20X Radonius Prime", + "30X Radonius", + "40X Radonius", + "Allegia 50 Clasteron", + "Allegia 500 Clasteron", + "Allegia 50B Clasteron", + "Allegia 51 Clasteron", + "Allegia 6R Clasteron", + "Alpha 100", + "Alpha 2", + "Alpha 200", + "Alpha 2A", + "Alpha 2A-8000", + "Alpha 2A-900", + "Callisto Morphamax", + "Callisto Morphamax 500", + "Callisto Morphamax 5000", + "Callisto Morphamax 600", + "Callisto Morphamax 6000 SE", + "Callisto Morphamax 6000 SE2", + "Callisto Morphamax 700", + "Callisto Morphamax 7000", + "Xiph Xlater 10000", + "Xiph Xlater 2000", + "Xiph Xlater 300", + "Xiph Xlater 40", + "Xiph Xlater 5", + "Xiph Xlater 50", + "Xiph Xlater 500", + "Xiph Xlater 5000", + "Xiph Xlater 58", + } + + natsort.Sort(list) + + fmt.Println(strings.Join(list, "\n")) +} +``` + +Output: + +``` +10X Radonius +20X Radonius +20X Radonius Prime +30X Radonius +40X Radonius +200X Radonius +1000X Radonius Maximus +Allegia 6R Clasteron +Allegia 50 Clasteron +Allegia 50B Clasteron +Allegia 51 Clasteron +Allegia 500 Clasteron +Alpha 2 +Alpha 2A +Alpha 2A-900 +Alpha 2A-8000 +Alpha 100 +Alpha 200 +Callisto Morphamax +Callisto Morphamax 500 +Callisto Morphamax 600 +Callisto Morphamax 700 +Callisto Morphamax 5000 +Callisto Morphamax 6000 SE +Callisto Morphamax 6000 SE2 +Callisto Morphamax 7000 +Xiph Xlater 5 +Xiph Xlater 40 +Xiph Xlater 50 +Xiph Xlater 58 +Xiph Xlater 300 +Xiph Xlater 500 +Xiph Xlater 2000 +Xiph Xlater 5000 +Xiph Xlater 10000 +``` + +[0]: http://davekoelle.com/alphanum.html diff --git a/vendor/github.com/facette/natsort/natsort.go b/vendor/github.com/facette/natsort/natsort.go new file mode 100644 index 00000000000..5c3c28d5ab1 --- /dev/null +++ b/vendor/github.com/facette/natsort/natsort.go @@ -0,0 +1,85 @@ +// Package natsort implements natural strings sorting +package natsort + +import ( + "regexp" + "sort" + "strconv" +) + +type stringSlice []string + +func (s stringSlice) Len() int { + return len(s) +} + +func (s stringSlice) Less(a, b int) bool { + return Compare(s[a], s[b]) +} + +func (s stringSlice) Swap(a, b int) { + s[a], s[b] = s[b], s[a] +} + +var chunkifyRegexp = regexp.MustCompile(`(\d+|\D+)`) + +func chunkify(s string) []string { + return chunkifyRegexp.FindAllString(s, -1) +} + +// Sort sorts a list of strings in a natural order +func Sort(l []string) { + sort.Sort(stringSlice(l)) +} + +// Compare returns true if the first string precedes the second one according to natural order +func Compare(a, b string) bool { + chunksA := chunkify(a) + chunksB := chunkify(b) + + nChunksA := len(chunksA) + nChunksB := len(chunksB) + + for i := range chunksA { + if i >= nChunksB { + return false + } + + aInt, aErr := strconv.Atoi(chunksA[i]) + bInt, bErr := strconv.Atoi(chunksB[i]) + + // If both chunks are numeric, compare them as integers + if aErr == nil && bErr == nil { + if aInt == bInt { + if i == nChunksA-1 { + // We reached the last chunk of A, thus B is greater than A + return true + } else if i == nChunksB-1 { + // We reached the last chunk of B, thus A is greater than B + return false + } + + continue + } + + return aInt < bInt + } + + // So far both strings are equal, continue to next chunk + if chunksA[i] == chunksB[i] { + if i == nChunksA-1 { + // We reached the last chunk of A, thus B is greater than A + return true + } else if i == nChunksB-1 { + // We reached the last chunk of B, thus A is greater than B + return false + } + + continue + } + + return chunksA[i] < chunksB[i] + } + + return false +} diff --git a/vendor/modules.txt b/vendor/modules.txt index fcf4bbcfaf1..5078122ae67 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -112,6 +112,8 @@ github.com/davecgh/go-spew/spew github.com/dgrijalva/jwt-go # github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize +# github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb +github.com/facette/natsort # github.com/fluent/fluent-logger-golang v1.2.1 github.com/fluent/fluent-logger-golang/fluent # github.com/fsouza/fake-gcs-server v1.3.0 From 0ddbb2611829d09c8d1f2b717e3797241063db58 Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Tue, 13 Aug 2019 09:53:58 -0400 Subject: [PATCH 3/4] chunk/cache: add test to demonstrate natsort works TestNatSort has been added to validate that the natsort package works as expected when sorting a list of servers that are returned from SRV lookups. The example used corresponds to SRV records that would be returned for a k8s headless service backed by a StatefulSet. Signed-off-by: Robert Fratto --- .../cache/memcached_client_selector_test.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/chunk/cache/memcached_client_selector_test.go b/pkg/chunk/cache/memcached_client_selector_test.go index 0d638c41223..f7134ce9984 100644 --- a/pkg/chunk/cache/memcached_client_selector_test.go +++ b/pkg/chunk/cache/memcached_client_selector_test.go @@ -6,9 +6,34 @@ import ( "github.com/bradfitz/gomemcache/memcache" "github.com/cortexproject/cortex/pkg/chunk/cache" + "github.com/facette/natsort" "github.com/stretchr/testify/require" ) +func TestNatSort(t *testing.T) { + // Validate that the order of SRV records returned by a DNS + // lookup for a k8s StatefulSet are ordered as expected when + // a natsort is done. + input := []string{ + "memcached-10.memcached.cortex.svc.cluster.local.", + "memcached-1.memcached.cortex.svc.cluster.local.", + "memcached-6.memcached.cortex.svc.cluster.local.", + "memcached-3.memcached.cortex.svc.cluster.local.", + "memcached-25.memcached.cortex.svc.cluster.local.", + } + + expected := []string{ + "memcached-1.memcached.cortex.svc.cluster.local.", + "memcached-3.memcached.cortex.svc.cluster.local.", + "memcached-6.memcached.cortex.svc.cluster.local.", + "memcached-10.memcached.cortex.svc.cluster.local.", + "memcached-25.memcached.cortex.svc.cluster.local.", + } + + natsort.Sort(input) + require.Equal(t, expected, input) +} + func TestMemcachedJumpHashSelector_PickSever(t *testing.T) { s := cache.MemcachedJumpHashSelector{} err := s.SetServers("google.com:80", "microsoft.com:80", "duckduckgo.com:80") From 1b76ceef33b56f39f863ebd1a0b3893262a5460a Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 14 Aug 2019 10:01:19 +0100 Subject: [PATCH 4/4] Add changelog entry. Signed-off-by: Tom Wilkie --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd11afd005..51a3400106c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## master / unreleased +* [FEATURE] Add option to use jump hashing to load balance requests to memcached #1554 + ## 0.1.0 / 2019-08-07 * [CHANGE] HA Tracker flags were renamed to provide more clarity #1465