github.com/thanos-io/thanos@v0.32.5/pkg/cacheutil/memcached_server_selector_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package cacheutil
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"testing"
    10  
    11  	"github.com/bradfitz/gomemcache/memcache"
    12  	"github.com/facette/natsort"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/efficientgo/core/testutil"
    16  )
    17  
    18  func TestNatSort(t *testing.T) {
    19  
    20  	// Validate that the order of SRV records returned by a DNS
    21  	// lookup for a k8s StatefulSet are ordered as expected when
    22  	// a natsort is done.
    23  	input := []string{
    24  		"memcached-10.memcached.thanos.svc.cluster.local.",
    25  		"memcached-1.memcached.thanos.svc.cluster.local.",
    26  		"memcached-6.memcached.thanos.svc.cluster.local.",
    27  		"memcached-3.memcached.thanos.svc.cluster.local.",
    28  		"memcached-25.memcached.thanos.svc.cluster.local.",
    29  	}
    30  
    31  	expected := []string{
    32  		"memcached-1.memcached.thanos.svc.cluster.local.",
    33  		"memcached-3.memcached.thanos.svc.cluster.local.",
    34  		"memcached-6.memcached.thanos.svc.cluster.local.",
    35  		"memcached-10.memcached.thanos.svc.cluster.local.",
    36  		"memcached-25.memcached.thanos.svc.cluster.local.",
    37  	}
    38  
    39  	natsort.Sort(input)
    40  	testutil.Equals(t, expected, input)
    41  }
    42  
    43  func TestMemcachedJumpHashSelector_PickServer(t *testing.T) {
    44  	tests := []struct {
    45  		addrs        []string
    46  		key          string
    47  		expectedAddr string
    48  		expectedErr  error
    49  	}{
    50  		{
    51  			addrs:       []string{},
    52  			key:         "test-1",
    53  			expectedErr: memcache.ErrNoServers,
    54  		},
    55  		{
    56  			addrs:        []string{"127.0.0.1:11211"},
    57  			key:          "test-1",
    58  			expectedAddr: "127.0.0.1:11211",
    59  		},
    60  		{
    61  			addrs:        []string{"127.0.0.1:11211", "127.0.0.2:11211"},
    62  			key:          "test-1",
    63  			expectedAddr: "127.0.0.1:11211",
    64  		},
    65  		{
    66  			addrs:        []string{"127.0.0.1:11211", "127.0.0.2:11211"},
    67  			key:          "test-2",
    68  			expectedAddr: "127.0.0.2:11211",
    69  		},
    70  	}
    71  
    72  	s := MemcachedJumpHashSelector{}
    73  
    74  	for _, test := range tests {
    75  		testutil.Ok(t, s.SetServers(test.addrs...))
    76  
    77  		actualAddr, err := s.PickServer(test.key)
    78  
    79  		if test.expectedErr != nil {
    80  			testutil.Equals(t, test.expectedErr, err)
    81  			testutil.Equals(t, nil, actualAddr)
    82  		} else {
    83  			testutil.Ok(t, err)
    84  			testutil.Equals(t, test.expectedAddr, actualAddr.String())
    85  		}
    86  	}
    87  }
    88  
    89  func TestMemcachedJumpHashSelector_Each_ShouldRespectServersOrdering(t *testing.T) {
    90  	tests := []struct {
    91  		input    []string
    92  		expected []string
    93  	}{
    94  		{
    95  			input:    []string{"127.0.0.1:11211", "127.0.0.2:11211", "127.0.0.3:11211"},
    96  			expected: []string{"127.0.0.1:11211", "127.0.0.2:11211", "127.0.0.3:11211"},
    97  		},
    98  		{
    99  			input:    []string{"127.0.0.2:11211", "127.0.0.3:11211", "127.0.0.1:11211"},
   100  			expected: []string{"127.0.0.1:11211", "127.0.0.2:11211", "127.0.0.3:11211"},
   101  		},
   102  	}
   103  
   104  	s := MemcachedJumpHashSelector{}
   105  
   106  	for _, test := range tests {
   107  		testutil.Ok(t, s.SetServers(test.input...))
   108  
   109  		actual := make([]string, 0, 3)
   110  		err := s.Each(func(addr net.Addr) error {
   111  			actual = append(actual, addr.String())
   112  			return nil
   113  		})
   114  
   115  		testutil.Ok(t, err)
   116  		testutil.Equals(t, test.expected, actual)
   117  	}
   118  }
   119  
   120  func TestMemcachedJumpHashSelector_PickServer_ShouldEvenlyDistributeKeysToServers(t *testing.T) {
   121  	servers := []string{"127.0.0.1:11211", "127.0.0.2:11211", "127.0.0.3:11211"}
   122  	selector := MemcachedJumpHashSelector{}
   123  	testutil.Ok(t, selector.SetServers(servers...))
   124  
   125  	// Calculate the distribution of keys.
   126  	distribution := make(map[string]int)
   127  
   128  	for i := 0; i < 1000; i++ {
   129  		key := fmt.Sprintf("key-%d", i)
   130  		addr, err := selector.PickServer(key)
   131  		testutil.Ok(t, err)
   132  		distribution[addr.String()]++
   133  	}
   134  
   135  	// Expect each server got at least 25% of keys, where the perfect split would be 33.3% each.
   136  	minKeysPerServer := int(float64(len(servers)) * 0.25)
   137  	testutil.Equals(t, len(servers), len(distribution))
   138  
   139  	for addr, count := range distribution {
   140  		if count < minKeysPerServer {
   141  			testutil.Ok(t, errors.Errorf("expected %s to have received at least %d keys instead it received %d", addr, minKeysPerServer, count))
   142  		}
   143  	}
   144  }
   145  
   146  func TestMemcachedJumpHashSelector_PickServer_ShouldUseConsistentHashing(t *testing.T) {
   147  	servers := []string{
   148  		"127.0.0.1:11211",
   149  		"127.0.0.2:11211",
   150  		"127.0.0.3:11211",
   151  		"127.0.0.4:11211",
   152  		"127.0.0.5:11211",
   153  		"127.0.0.6:11211",
   154  		"127.0.0.7:11211",
   155  		"127.0.0.8:11211",
   156  		"127.0.0.9:11211",
   157  	}
   158  
   159  	selector := MemcachedJumpHashSelector{}
   160  	testutil.Ok(t, selector.SetServers(servers...))
   161  
   162  	// Pick a server for each key.
   163  	distribution := make(map[string]string)
   164  	numKeys := 1000
   165  
   166  	for i := 0; i < 1000; i++ {
   167  		key := fmt.Sprintf("key-%d", i)
   168  		addr, err := selector.PickServer(key)
   169  		testutil.Ok(t, err)
   170  		distribution[key] = addr.String()
   171  	}
   172  
   173  	// Add 1 more server that - in a natural ordering - is added as last.
   174  	servers = append(servers, "127.0.0.10:11211")
   175  	testutil.Ok(t, selector.SetServers(servers...))
   176  
   177  	// Calculate the number of keys who has been moved due to the resharding.
   178  	moved := 0
   179  
   180  	for i := 0; i < 1000; i++ {
   181  		key := fmt.Sprintf("key-%d", i)
   182  		addr, err := selector.PickServer(key)
   183  		testutil.Ok(t, err)
   184  
   185  		if distribution[key] != addr.String() {
   186  			moved++
   187  		}
   188  	}
   189  
   190  	// Expect we haven't moved more than (1/shards)% +2% tolerance.
   191  	maxExpectedMovedPerc := (1.0 / float64(len(servers))) + 0.02
   192  	maxExpectedMoved := int(float64(numKeys) * maxExpectedMovedPerc)
   193  	if moved > maxExpectedMoved {
   194  		testutil.Ok(t, errors.Errorf("expected resharding moved no more then %d keys while %d have been moved", maxExpectedMoved, moved))
   195  	}
   196  }
   197  
   198  func TestMemcachedJumpHashSelector_PickServer_ShouldReturnErrNoServersOnNoServers(t *testing.T) {
   199  	s := MemcachedJumpHashSelector{}
   200  	_, err := s.PickServer("foo")
   201  	testutil.Equals(t, memcache.ErrNoServers, err)
   202  }
   203  
   204  func BenchmarkMemcachedJumpHashSelector_PickServer(b *testing.B) {
   205  	// Create a pretty long list of servers.
   206  	servers := make([]string, 0)
   207  	for i := 1; i <= 60; i++ {
   208  		servers = append(servers, fmt.Sprintf("127.0.0.%d:11211", i))
   209  	}
   210  
   211  	selector := MemcachedJumpHashSelector{}
   212  	err := selector.SetServers(servers...)
   213  	if err != nil {
   214  		b.Error(err)
   215  	}
   216  
   217  	b.ResetTimer()
   218  
   219  	for i := 0; i < b.N; i++ {
   220  		_, err := selector.PickServer(fmt.Sprint(i))
   221  		if err != nil {
   222  			b.Error(err)
   223  		}
   224  	}
   225  }