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 }