github.com/grafana/pyroscope@v1.18.0/pkg/segmentwriter/client/distributor/distributor_test.go (about)

     1  package distributor
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/grafana/dskit/ring"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    14  	"github.com/grafana/pyroscope/pkg/iter"
    15  	"github.com/grafana/pyroscope/pkg/segmentwriter/client/distributor/placement"
    16  	"github.com/grafana/pyroscope/pkg/test/mocks/mockplacement"
    17  	"github.com/grafana/pyroscope/pkg/testhelper"
    18  )
    19  
    20  // TODO(kolesnikovae): Test distribution fairness.
    21  
    22  var (
    23  	testLabels = []*typesv1.LabelPair{
    24  		{Name: "foo", Value: "bar"},
    25  		{Name: "baz", Value: "qux"},
    26  		{Name: "service_name", Value: "my-service"},
    27  	}
    28  	testInstances = []ring.InstanceDesc{
    29  		{Id: "a", Tokens: make([]uint32, 1)},
    30  		{Id: "b", Tokens: make([]uint32, 1)},
    31  		{Id: "c", State: ring.LEAVING, Tokens: make([]uint32, 1)},
    32  	}
    33  	zeroShard = func(int) int { return 0 }
    34  )
    35  
    36  func Test_EmptyRing(t *testing.T) {
    37  	m := new(mockplacement.MockPlacement)
    38  	r := testhelper.NewMockRing(nil, 1)
    39  	d := NewDistributor(m, r)
    40  
    41  	k := NewTenantServiceDatasetKey("")
    42  	_, err := d.Distribute(k)
    43  	assert.ErrorIs(t, err, ring.ErrEmptyRing)
    44  }
    45  
    46  func Test_Distribution_AvailableShards(t *testing.T) {
    47  	for _, tc := range []struct {
    48  		description string
    49  		placement.Policy
    50  	}{
    51  		{
    52  			description: "zero",
    53  			Policy: placement.Policy{
    54  				TenantShards:  0,
    55  				DatasetShards: 0,
    56  				PickShard:     zeroShard,
    57  			},
    58  		},
    59  		{
    60  			description: "min",
    61  			Policy: placement.Policy{
    62  				TenantShards:  1,
    63  				DatasetShards: 1,
    64  				PickShard:     zeroShard,
    65  			},
    66  		},
    67  		{
    68  			description: "insufficient",
    69  			Policy: placement.Policy{
    70  				TenantShards:  1 << 10,
    71  				DatasetShards: 1 << 9,
    72  				PickShard:     zeroShard,
    73  			},
    74  		},
    75  		{
    76  			description: "invalid",
    77  			Policy: placement.Policy{
    78  				TenantShards:  1 << 10,
    79  				DatasetShards: 2 << 10,
    80  				PickShard:     zeroShard,
    81  			},
    82  		},
    83  	} {
    84  		t.Run(tc.description, func(t *testing.T) {
    85  			k := NewTenantServiceDatasetKey("tenant-a", testLabels...)
    86  			m := new(mockplacement.MockPlacement)
    87  			m.On("Policy", k, mock.Anything).Return(tc.Policy).Once()
    88  			r := testhelper.NewMockRing(testInstances, 1)
    89  			d := NewDistributor(m, r)
    90  			p, err := d.Distribute(k)
    91  			require.NoError(t, err)
    92  			c := make([]ring.InstanceDesc, 0, 2)
    93  			for p.Instances.Next() {
    94  				c = append(c, p.Instances.At())
    95  			}
    96  
    97  			assert.Equal(t, 3, len(c))
    98  			m.AssertExpectations(t)
    99  		})
   100  	}
   101  }
   102  
   103  func Test_RingUpdate(t *testing.T) {
   104  	k := NewTenantServiceDatasetKey("")
   105  	m := new(mockplacement.MockPlacement)
   106  	m.On("Policy", k, mock.Anything).Return(placement.Policy{
   107  		TenantShards:  1,
   108  		DatasetShards: 1,
   109  		PickShard:     zeroShard,
   110  	})
   111  
   112  	r := testhelper.NewMockRing(testInstances, 1)
   113  	d := NewDistributor(m, r)
   114  	_, err := d.Distribute(k)
   115  	require.NoError(t, err)
   116  
   117  	instances := make([]ring.InstanceDesc, 2)
   118  	copy(instances, testInstances[:1])
   119  	r.SetInstances(instances)
   120  	require.NoError(t, d.updateDistribution(r, 0))
   121  
   122  	p, err := d.Distribute(k)
   123  	require.NoError(t, err)
   124  	c := make([]ring.InstanceDesc, 0, 1)
   125  	for p.Instances.Next() {
   126  		c = append(c, p.Instances.At())
   127  	}
   128  
   129  	// Only one instance is available.
   130  	assert.Equal(t, 1, len(c))
   131  	m.AssertExpectations(t)
   132  }
   133  
   134  func Test_Distributor_Distribute(t *testing.T) {
   135  	m := new(mockplacement.MockPlacement)
   136  	r := testhelper.NewMockRing([]ring.InstanceDesc{
   137  		{Id: "a", Tokens: make([]uint32, 4)},
   138  		{Id: "b", Tokens: make([]uint32, 4)},
   139  		{Id: "c", Tokens: make([]uint32, 4)},
   140  	}, 1)
   141  
   142  	d := NewDistributor(m, r)
   143  	collect := func(offset, n int) []string {
   144  		h := uint64(14046587775414411003)
   145  		k := placement.Key{
   146  			Tenant:      h,
   147  			Dataset:     h,
   148  			Fingerprint: h,
   149  		}
   150  		m.On("Policy", k).Return(placement.Policy{
   151  			TenantShards:  8,
   152  			DatasetShards: 4,
   153  			PickShard:     func(int) int { return offset },
   154  		}).Once()
   155  		p, err := d.Distribute(k)
   156  		require.NoError(t, err)
   157  		return collectN(p.Instances, n)
   158  	}
   159  
   160  	//   0 1 2 3 4 5 6 7 8 9 10 11  all shards
   161  	//   * * * *         > * *  *   tenant (size 8, offset 8)
   162  	//       > *         * *        dataset (size 4, offset 6+8 mod 12 = 2)
   163  	//   2 1 0 1 1 2 2 0 1 0 0  2   shuffling (see d.distribution.shards)
   164  	//   ----------------------------------------------------------------------
   165  	//       0 1         2 3 4      PickShard 0 (offset within dataset)
   166  	//                       ^      borrowed from the tenant
   167  	//
   168  	//       3 0         1 2 4      PickShard 1
   169  	//       2 3         0 1 4      PickShard 2
   170  	//       1 2         3 0 4      PickShard 3
   171  
   172  	// Identical keys have identical placement.
   173  	assert.Equal(t, []string{"a", "b", "b", "a", "a"}, collect(0, 5))
   174  	assert.Equal(t, []string{"a", "b", "b", "a", "a"}, collect(0, 5))
   175  
   176  	// Placement of different keys in the dataset is bound.
   177  	assert.Equal(t, []string{"b", "b", "a", "a", "a"}, collect(1, 5))
   178  	assert.Equal(t, []string{"b", "a", "a", "b", "a"}, collect(2, 5))
   179  	assert.Equal(t, []string{"a", "a", "b", "b", "a"}, collect(3, 5))
   180  
   181  	// Now we're trying to collect more instances than available.
   182  	//   0 1 2 3 4 5 6  7  8 9 10 11  all shards
   183  	//   * * * *           > * *  *   tenant (size 8, offset 8)
   184  	//       > *           x *        dataset (size 4, offset 6+8 mod 12 = 2)
   185  	//       0 1           2          PickShard 2 (13)
   186  	//   6 7 2 3 8 9 10 11 0 1 4  5
   187  	//   ^ ^                   ^  ^   borrowed from the tenant
   188  	//           ^ ^ ^  ^             borrowed from the top ring
   189  	//   2 1 0 1 1 2 2  0  1 0 0  2   shuffling (see d.distribution.shards)
   190  	assert.Equal(t, []string{"b", "a", "a", "b", "a", "c", "c", "b", "b", "c", "c", "a"}, collect(2, 13))
   191  }
   192  
   193  func Test_distribution_iterator(t *testing.T) {
   194  	d := &distribution{
   195  		shards: []uint32{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2},
   196  		desc:   []ring.InstanceDesc{{Id: "a"}, {Id: "b"}, {Id: "c"}},
   197  	}
   198  
   199  	t.Run("empty ring", func(t *testing.T) {
   200  		assert.Equal(t, []string{}, collectN(d.instances(subring{}, 0), 10))
   201  	})
   202  
   203  	t.Run("matching subrings", func(t *testing.T) {
   204  		r := subring{
   205  			n: 12,
   206  			a: 8,
   207  			b: 16,
   208  			c: 8,
   209  			d: 16,
   210  		}
   211  
   212  		//   0 1 2 3 4 5 6  7  8 9 10 11  all shards
   213  		//   a a a a b b b  b  c c c  c   no shuffling (!)
   214  		//   * * * *           > * *  *   tenant (size 8, offset 8)
   215  		//   * * * *           > * *  *   dataset (size 8, offset 8)
   216  		//
   217  		//   4 5 6 7|8 9 10 11|0 1 2  3   PickShard 0 (offset within dataset/tenant)
   218  		//   3 4 5 6|8 9 10 11|7 0 1  2   PickShard 1
   219  		//   2 3 4 5|8 9 10 11|6 7 0  1   PickShard 2
   220  
   221  		var expected bytes.Buffer
   222  		for _, line := range []string{
   223  			"0 [c c c c a a a a b b b b]",
   224  			"1 [c c c a a a a c b b b b]",
   225  			"2 [c c a a a a c c b b b b]",
   226  			"3 [c a a a a c c c b b b b]",
   227  			"4 [a a a a c c c c b b b b]",
   228  			"5 [a a a c c c c a b b b b]",
   229  			"6 [a a c c c c a a b b b b]",
   230  			"7 [a c c c c a a a b b b b]",
   231  			"8 [c c c c a a a a b b b b]",
   232  			"9 [c c c a a a a c b b b b]",
   233  		} {
   234  			_, _ = fmt.Fprintln(&expected, line)
   235  		}
   236  
   237  		var actual bytes.Buffer
   238  		for i := 0; i < 10; i++ {
   239  			_, _ = fmt.Fprintln(&actual, i, collectN(d.instances(r, i), 20))
   240  		}
   241  
   242  		assert.Equal(t, expected.String(), actual.String())
   243  	})
   244  
   245  	t.Run("nested subrings", func(t *testing.T) {
   246  		r := subring{
   247  			n: 12,
   248  			a: 1,
   249  			b: 9,
   250  			c: 3,
   251  			d: 7,
   252  		}
   253  
   254  		//   0  1  2 3 4 5 6 7 8 9 10 11  all shards
   255  		//   a  a  a a b b b b c c c  c   no shuffling (!)
   256  		//      >  * * * * * * *          tenant (size 8, offset 1)
   257  		//           > * * *              dataset (size 4, offset 3)
   258  		//
   259  		//   11 4  5 0 1 2 3 6 7 8 9  10  PickShard 0 (offset within dataset)
   260  		//   11 4  5 3 0 1 2 6 7 8 9  10  PickShard 1
   261  
   262  		var expected bytes.Buffer
   263  		for _, line := range []string{
   264  			"0 [a b b b b c a a c c c a]",
   265  			"1 [b b b a b c a a c c c a]",
   266  			"2 [b b a b b c a a c c c a]",
   267  			"3 [b a b b b c a a c c c a]",
   268  			"4 [a b b b b c a a c c c a]",
   269  			"5 [b b b a b c a a c c c a]",
   270  			"6 [b b a b b c a a c c c a]",
   271  			"7 [b a b b b c a a c c c a]",
   272  			"8 [a b b b b c a a c c c a]",
   273  			"9 [b b b a b c a a c c c a]",
   274  		} {
   275  			_, _ = fmt.Fprintln(&expected, line)
   276  		}
   277  
   278  		var actual bytes.Buffer
   279  		for i := 0; i < 10; i++ {
   280  			_, _ = fmt.Fprintln(&actual, i, collectN(d.instances(r, i), 20))
   281  		}
   282  
   283  		assert.Equal(t, expected.String(), actual.String())
   284  	})
   285  
   286  	t.Run("nested subrings aligned", func(t *testing.T) {
   287  		r := subring{
   288  			n: 12,
   289  			a: 1,
   290  			b: 9,
   291  			c: 1,
   292  			d: 5,
   293  		}
   294  
   295  		//   0  1 2 3 4 5 6 7 8 9 10 11  all shards
   296  		//   a  a a a b b b b c c c  c   no shuffling (!)
   297  		//      > * * * * * * *          tenant (size 8, offset 1)
   298  		//      > * * *                  dataset (size 4, offset 1)
   299  
   300  		var expected bytes.Buffer
   301  		for _, line := range []string{
   302  			"0 [a a a b b b b c c c c a]",
   303  			"1 [a a b a b b b c c c c a]",
   304  			"2 [a b a a b b b c c c c a]",
   305  			"3 [b a a a b b b c c c c a]",
   306  			"4 [a a a b b b b c c c c a]",
   307  			"5 [a a b a b b b c c c c a]",
   308  			"6 [a b a a b b b c c c c a]",
   309  			"7 [b a a a b b b c c c c a]",
   310  			"8 [a a a b b b b c c c c a]",
   311  			"9 [a a b a b b b c c c c a]",
   312  		} {
   313  			_, _ = fmt.Fprintln(&expected, line)
   314  		}
   315  
   316  		var actual bytes.Buffer
   317  		for i := 0; i < 10; i++ {
   318  			_, _ = fmt.Fprintln(&actual, i, collectN(d.instances(r, i), 20))
   319  		}
   320  
   321  		assert.Equal(t, expected.String(), actual.String())
   322  	})
   323  
   324  	t.Run("nested subrings wrap", func(t *testing.T) {
   325  		r := subring{
   326  			n: 12,
   327  			a: 8,
   328  			b: 16,
   329  			c: 10,
   330  			d: 14,
   331  		}
   332  
   333  		//   0 1 2 3 4 5 6 7 8 9 10 11  all shards
   334  		//   a a a a b b b b c c c  c   no shuffling (!)
   335  		//   * * * *         > * *  *   tenant (size 8, offset 8)
   336  		//   * *                 >  *   dataset (size 4, offset 14 mod 12 = 2)
   337  
   338  		var expected bytes.Buffer
   339  		for _, line := range []string{
   340  			"0 [c c a a a a c c b b b b]",
   341  			"1 [c a a c a a c c b b b b]",
   342  			"2 [a a c c a a c c b b b b]",
   343  			"3 [a c c a a a c c b b b b]",
   344  			"4 [c c a a a a c c b b b b]",
   345  			"5 [c a a c a a c c b b b b]",
   346  			"6 [a a c c a a c c b b b b]",
   347  			"7 [a c c a a a c c b b b b]",
   348  			"8 [c c a a a a c c b b b b]",
   349  			"9 [c a a c a a c c b b b b]",
   350  		} {
   351  			_, _ = fmt.Fprintln(&expected, line)
   352  		}
   353  
   354  		var actual bytes.Buffer
   355  		for i := 0; i < 10; i++ {
   356  			_, _ = fmt.Fprintln(&actual, i, collectN(d.instances(r, i), 20))
   357  		}
   358  
   359  		assert.Equal(t, expected.String(), actual.String())
   360  	})
   361  
   362  	t.Run("overlapping subrings", func(t *testing.T) {
   363  		r := subring{
   364  			n: 12,
   365  			a: 8,
   366  			b: 16,
   367  			c: 14,
   368  			d: 18,
   369  		}
   370  
   371  		//   0 1 2 3 4 5 6  7  8 9 10 11  all shards
   372  		//   a a a a b b b  b  c c c  c   no shuffling (!)
   373  		//   * * * *           > * *  *   tenant (size 8, offset 8)
   374  		//       > *           * *        dataset (size 4, offset 14 mod 12 = 2)
   375  		//   6 7|0 1|8 9 10 11|2 3|4  5   PickShard 0 (offset within dataset)
   376  		//   6 7|3 0|8 9 10 11|1 2|4  5   PickShard 1
   377  		//   6 7|2 3|8 9 10 11|0 1|4  5   PickShard 2
   378  		//   6 7|1 2|8 9 10 11|3 0|4  5   PickShard 3
   379  
   380  		var expected bytes.Buffer
   381  		for _, line := range []string{
   382  			"0 [a a c c c c a a b b b b]",
   383  			"1 [a c c a c c a a b b b b]",
   384  			"2 [c c a a c c a a b b b b]",
   385  			"3 [c a a c c c a a b b b b]",
   386  			"4 [a a c c c c a a b b b b]",
   387  			"5 [a c c a c c a a b b b b]",
   388  			"6 [c c a a c c a a b b b b]",
   389  			"7 [c a a c c c a a b b b b]",
   390  			"8 [a a c c c c a a b b b b]",
   391  			"9 [a c c a c c a a b b b b]",
   392  		} {
   393  			_, _ = fmt.Fprintln(&expected, line)
   394  		}
   395  
   396  		var actual bytes.Buffer
   397  		for i := 0; i < 10; i++ {
   398  			_, _ = fmt.Fprintln(&actual, i, collectN(d.instances(r, i), 20))
   399  		}
   400  
   401  		assert.Equal(t, expected.String(), actual.String())
   402  	})
   403  }
   404  
   405  func Test_permutation(t *testing.T) {
   406  	actual := make([][]uint32, 0, 16)
   407  	copyP := func(s []uint32) []uint32 {
   408  		c := make([]uint32, len(s))
   409  		copy(c, s)
   410  		return c
   411  	}
   412  
   413  	var p perm
   414  	for i := 0; i <= 32; i += 4 {
   415  		p.resize(i)
   416  		actual = append(actual, copyP(p.v))
   417  	}
   418  	for i := 32; i >= 0; i -= 4 {
   419  		p.resize(i)
   420  		actual = append(actual, copyP(p.v))
   421  	}
   422  	expected := [][]uint32{
   423  		{},
   424  		{3, 1, 0, 2},
   425  		{3, 6, 0, 5, 7, 4, 1, 2},
   426  		{11, 6, 0, 5, 7, 8, 9, 2, 4, 1, 3, 10},
   427  		{11, 6, 0, 5, 14, 8, 15, 2, 12, 1, 3, 13, 4, 10, 7, 9},
   428  		{11, 6, 0, 19, 14, 8, 15, 2, 12, 17, 3, 18, 4, 16, 7, 9, 10, 1, 13, 5},
   429  		{11, 6, 0, 19, 14, 8, 15, 2, 21, 17, 3, 18, 4, 16, 7, 22, 10, 1, 23, 5, 9, 12, 20, 13},
   430  		{11, 6, 0, 19, 14, 8, 15, 2, 26, 17, 25, 18, 24, 16, 7, 22, 10, 1, 23, 5, 9, 12, 20, 13, 4, 3, 27, 21},
   431  		{11, 6, 0, 28, 31, 8, 15, 2, 26, 17, 25, 18, 24, 16, 7, 22, 10, 1, 23, 5, 30, 12, 20, 13, 4, 29, 27, 21, 19, 3, 9, 14},
   432  		{11, 6, 0, 28, 31, 8, 15, 2, 26, 17, 25, 18, 24, 16, 7, 22, 10, 1, 23, 5, 30, 12, 20, 13, 4, 29, 27, 21, 19, 3, 9, 14},
   433  		{11, 6, 0, 19, 14, 8, 15, 2, 26, 17, 25, 18, 24, 16, 7, 22, 10, 1, 23, 5, 9, 12, 20, 13, 4, 3, 27, 21},
   434  		{11, 6, 0, 19, 14, 8, 15, 2, 21, 17, 3, 18, 4, 16, 7, 22, 10, 1, 23, 5, 9, 12, 20, 13},
   435  		{11, 6, 0, 19, 14, 8, 15, 2, 12, 17, 3, 18, 4, 16, 7, 9, 10, 1, 13, 5},
   436  		{11, 6, 0, 5, 14, 8, 15, 2, 12, 1, 3, 13, 4, 10, 7, 9},
   437  		{11, 6, 0, 5, 7, 8, 9, 2, 4, 1, 3, 10},
   438  		{3, 6, 0, 5, 7, 4, 1, 2},
   439  		{3, 1, 0, 2},
   440  		{},
   441  	}
   442  
   443  	assert.Equal(t, expected, actual)
   444  }
   445  
   446  func collectN(i iter.Iterator[ring.InstanceDesc], n int) []string {
   447  	s := make([]string, 0, n)
   448  	for n > 0 && i.Next() {
   449  		s = append(s, i.At().Id)
   450  		n--
   451  	}
   452  	return s
   453  }