github.com/grafana/pyroscope@v1.18.0/pkg/compactor/split_merge_job_test.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  // Provenance-includes-location: https://github.com/grafana/mimir/blob/main/pkg/compactor/split_merge_job_test.go
     3  // Provenance-includes-license: Apache-2.0
     4  // Provenance-includes-copyright: The Cortex Authors.
     5  
     6  package compactor
     7  
     8  import (
     9  	"encoding/json"
    10  	"testing"
    11  
    12  	"github.com/oklog/ulid/v2"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    17  	sharding "github.com/grafana/pyroscope/pkg/phlaredb/sharding"
    18  )
    19  
    20  func TestJob_conflicts(t *testing.T) {
    21  	block1 := &block.Meta{ULID: ulid.MustNew(1, nil)}
    22  	block2 := &block.Meta{ULID: ulid.MustNew(2, nil)}
    23  	block3 := &block.Meta{ULID: ulid.MustNew(3, nil)}
    24  	block4 := &block.Meta{ULID: ulid.MustNew(4, nil)}
    25  
    26  	copyMeta := func(meta *block.Meta) *block.Meta {
    27  		encoded, err := json.Marshal(meta)
    28  		require.NoError(t, err)
    29  
    30  		decoded := block.Meta{}
    31  		require.NoError(t, json.Unmarshal(encoded, &decoded))
    32  
    33  		return &decoded
    34  	}
    35  
    36  	withShardIDLabel := func(meta *block.Meta, shardID string) *block.Meta {
    37  		meta = copyMeta(meta)
    38  		meta.Labels = map[string]string{sharding.CompactorShardIDLabel: shardID}
    39  		return meta
    40  	}
    41  
    42  	tests := map[string]struct {
    43  		first    *job
    44  		second   *job
    45  		expected bool
    46  	}{
    47  		"should conflict if jobs compact different blocks but with overlapping time ranges and same shard": {
    48  			first: &job{
    49  				stage:   stageMerge,
    50  				shardID: "1_of_2",
    51  				blocksGroup: blocksGroup{
    52  					rangeStart: 10,
    53  					rangeEnd:   20,
    54  					blocks:     []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")},
    55  				},
    56  			},
    57  			second: &job{
    58  				stage:   stageMerge,
    59  				shardID: "1_of_2",
    60  				blocksGroup: blocksGroup{
    61  					rangeStart: 15,
    62  					rangeEnd:   25,
    63  					blocks:     []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")},
    64  				},
    65  			},
    66  			expected: true,
    67  		},
    68  		"should NOT conflict if jobs compact different blocks with non-overlapping time ranges and same shard": {
    69  			first: &job{
    70  				stage:   stageMerge,
    71  				shardID: "1_of_2",
    72  				blocksGroup: blocksGroup{
    73  					rangeStart: 10,
    74  					rangeEnd:   20,
    75  					blocks:     []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")},
    76  				},
    77  			},
    78  			second: &job{
    79  				stage:   stageMerge,
    80  				shardID: "1_of_2",
    81  				blocksGroup: blocksGroup{
    82  					rangeStart: 21,
    83  					rangeEnd:   30,
    84  					blocks:     []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")},
    85  				},
    86  			},
    87  			expected: false,
    88  		},
    89  		"should NOT conflict if jobs compact same blocks with overlapping time ranges but different shard": {
    90  			first: &job{
    91  				stage:   stageMerge,
    92  				shardID: "1_of_2",
    93  				blocksGroup: blocksGroup{
    94  					rangeStart: 10,
    95  					rangeEnd:   20,
    96  					blocks:     []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")},
    97  				},
    98  			},
    99  			second: &job{
   100  				stage:   stageMerge,
   101  				shardID: "2_of_2",
   102  				blocksGroup: blocksGroup{
   103  					rangeStart: 10,
   104  					rangeEnd:   20,
   105  					blocks:     []*block.Meta{withShardIDLabel(block1, "2_of_2"), withShardIDLabel(block2, "2_of_2")},
   106  				},
   107  			},
   108  			expected: false,
   109  		},
   110  		"should conflict if jobs compact same blocks with overlapping time ranges and different shard but at a different stage": {
   111  			first: &job{
   112  				stage:   stageSplit,
   113  				shardID: "1_of_2",
   114  				blocksGroup: blocksGroup{
   115  					rangeStart: 10,
   116  					rangeEnd:   20,
   117  					blocks:     []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")},
   118  				},
   119  			},
   120  			second: &job{
   121  				stage:   stageMerge,
   122  				shardID: "2_of_2",
   123  				blocksGroup: blocksGroup{
   124  					rangeStart: 10,
   125  					rangeEnd:   20,
   126  					blocks:     []*block.Meta{withShardIDLabel(block1, "2_of_2"), withShardIDLabel(block2, "2_of_2")},
   127  				},
   128  			},
   129  			expected: true,
   130  		},
   131  		"should conflict between split and merge jobs with overlapping time ranges": {
   132  			first: &job{
   133  				stage:   stageSplit,
   134  				shardID: "",
   135  				blocksGroup: blocksGroup{
   136  					rangeStart: 10,
   137  					rangeEnd:   20,
   138  					blocks:     []*block.Meta{block1, block2},
   139  				},
   140  			},
   141  			second: &job{
   142  				stage:   stageMerge,
   143  				shardID: "1_of_2",
   144  				blocksGroup: blocksGroup{
   145  					rangeStart: 0,
   146  					rangeEnd:   40,
   147  					blocks:     []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")},
   148  				},
   149  			},
   150  			expected: true,
   151  		},
   152  		"should NOT conflict between split and merge jobs with non-overlapping time ranges": {
   153  			first: &job{
   154  				stage:   stageSplit,
   155  				shardID: "",
   156  				blocksGroup: blocksGroup{
   157  					rangeStart: 10,
   158  					rangeEnd:   20,
   159  					blocks:     []*block.Meta{block1, block2},
   160  				},
   161  			},
   162  			second: &job{
   163  				stage:   stageMerge,
   164  				shardID: "1_of_2",
   165  				blocksGroup: blocksGroup{
   166  					rangeStart: 21,
   167  					rangeEnd:   40,
   168  					blocks:     []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")},
   169  				},
   170  			},
   171  			expected: false,
   172  		},
   173  	}
   174  
   175  	for testName, testCase := range tests {
   176  		t.Run(testName, func(t *testing.T) {
   177  			assert.Equal(t, testCase.expected, testCase.first.conflicts(testCase.second))
   178  			assert.Equal(t, testCase.expected, testCase.second.conflicts(testCase.first))
   179  		})
   180  	}
   181  }
   182  
   183  func TestBlocksGroup_overlaps(t *testing.T) {
   184  	tests := []struct {
   185  		first    blocksGroup
   186  		second   blocksGroup
   187  		expected bool
   188  	}{
   189  		{
   190  			first:    blocksGroup{rangeStart: 10, rangeEnd: 20},
   191  			second:   blocksGroup{rangeStart: 20, rangeEnd: 30},
   192  			expected: false,
   193  		}, {
   194  			first:    blocksGroup{rangeStart: 10, rangeEnd: 20},
   195  			second:   blocksGroup{rangeStart: 21, rangeEnd: 30},
   196  			expected: false,
   197  		}, {
   198  			first:    blocksGroup{rangeStart: 10, rangeEnd: 20},
   199  			second:   blocksGroup{rangeStart: 19, rangeEnd: 30},
   200  			expected: true,
   201  		}, {
   202  			first:    blocksGroup{rangeStart: 10, rangeEnd: 21},
   203  			second:   blocksGroup{rangeStart: 20, rangeEnd: 30},
   204  			expected: true,
   205  		}, {
   206  			first:    blocksGroup{rangeStart: 10, rangeEnd: 20},
   207  			second:   blocksGroup{rangeStart: 12, rangeEnd: 18},
   208  			expected: true,
   209  		},
   210  	}
   211  
   212  	for _, tc := range tests {
   213  		assert.Equal(t, tc.expected, tc.first.overlaps(tc.second))
   214  		assert.Equal(t, tc.expected, tc.second.overlaps(tc.first))
   215  	}
   216  }
   217  
   218  func TestBlocksGroup_getNonShardedBlocks(t *testing.T) {
   219  	block1 := ulid.MustNew(1, nil)
   220  	block2 := ulid.MustNew(2, nil)
   221  	block3 := ulid.MustNew(3, nil)
   222  
   223  	tests := map[string]struct {
   224  		input    blocksGroup
   225  		expected []*block.Meta
   226  	}{
   227  		"should return nil if the group is empty": {
   228  			input:    blocksGroup{},
   229  			expected: nil,
   230  		},
   231  		"should return nil if the group contains only sharded blocks": {
   232  			input: blocksGroup{blocks: []*block.Meta{
   233  				{ULID: block1, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}},
   234  				{ULID: block2, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}},
   235  			}},
   236  			expected: nil,
   237  		},
   238  		"should return the list of non-sharded blocks if exist in the group": {
   239  			input: blocksGroup{blocks: []*block.Meta{
   240  				{ULID: block1},
   241  				{ULID: block2, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}},
   242  				{ULID: block3, Labels: map[string]string{"key": "value"}},
   243  			}},
   244  			expected: []*block.Meta{
   245  				{ULID: block1},
   246  				{ULID: block3, Labels: map[string]string{"key": "value"}},
   247  			},
   248  		},
   249  		"should consider non-sharded a block with the shard ID label but empty value": {
   250  			input: blocksGroup{blocks: []*block.Meta{
   251  				{ULID: block1, Labels: map[string]string{sharding.CompactorShardIDLabel: ""}},
   252  				{ULID: block2, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}},
   253  				{ULID: block3, Labels: map[string]string{"key": "value"}},
   254  			}},
   255  			expected: []*block.Meta{
   256  				{ULID: block1, Labels: map[string]string{sharding.CompactorShardIDLabel: ""}},
   257  				{ULID: block3, Labels: map[string]string{"key": "value"}},
   258  			},
   259  		},
   260  	}
   261  
   262  	for _, tc := range tests {
   263  		assert.Equal(t, tc.expected, tc.input.getNonShardedBlocks())
   264  	}
   265  }