github.com/grafana/pyroscope@v1.18.0/pkg/querier/replication_test.go (about)

     1  package querier
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/go-kit/log"
    11  	"github.com/prometheus/common/model"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    15  	"github.com/grafana/pyroscope/pkg/phlaredb/sharding"
    16  )
    17  
    18  type blockInfo struct {
    19  	i typesv1.BlockInfo
    20  }
    21  
    22  func newBlockInfo(ulid string) *blockInfo {
    23  	return &blockInfo{
    24  		i: typesv1.BlockInfo{
    25  			Ulid: ulid,
    26  			Compaction: &typesv1.BlockCompaction{
    27  				Level: 1,
    28  			},
    29  		},
    30  	}
    31  }
    32  
    33  func (b *blockInfo) withMinTime(minT time.Time, d time.Duration) *blockInfo {
    34  	b.i.MinTime = int64(model.TimeFromUnixNano(minT.UnixNano()))
    35  	b.i.MaxTime = int64(model.TimeFromUnixNano(minT.Add(d).UnixNano()))
    36  	return b
    37  }
    38  
    39  func (b *blockInfo) withCompactionLevel(i int32) *blockInfo {
    40  	b.i.Compaction.Level = i
    41  	return b
    42  }
    43  
    44  func (b *blockInfo) withCompactionSources(sources ...string) *blockInfo {
    45  	b.i.Compaction.Sources = sources
    46  	return b
    47  }
    48  
    49  func (b *blockInfo) withCompactionParents(parents ...string) *blockInfo {
    50  	b.i.Compaction.Parents = parents
    51  	return b
    52  }
    53  
    54  func (b *blockInfo) withLabelValue(k, v string) *blockInfo {
    55  	b.i.Labels = append(b.i.Labels, &typesv1.LabelPair{
    56  		Name:  k,
    57  		Value: v,
    58  	})
    59  	return b
    60  }
    61  
    62  func (b *blockInfo) withCompactorShard(shard, shardsCount uint64) *blockInfo {
    63  	return b.withLabelValue(
    64  		sharding.CompactorShardIDLabel,
    65  		sharding.FormatShardIDLabelValue(shard, shardsCount),
    66  	)
    67  }
    68  
    69  func (b *blockInfo) info() *typesv1.BlockInfo {
    70  	return &b.i
    71  }
    72  
    73  type validatorFunc func(t *testing.T, plan map[string]*blockPlanEntry)
    74  
    75  func validatePlanBlockIDs(expBlockIDs ...string) validatorFunc {
    76  	return func(t *testing.T, plan map[string]*blockPlanEntry) {
    77  		var blockIDs []string
    78  		for _, planEntry := range plan {
    79  			blockIDs = append(blockIDs, planEntry.Ulids...)
    80  		}
    81  		sort.Strings(blockIDs)
    82  		require.Equal(t, expBlockIDs, blockIDs)
    83  	}
    84  }
    85  
    86  func validatePlanBlocksOnReplica(replica string, blocks ...string) validatorFunc {
    87  	return func(t *testing.T, plan map[string]*blockPlanEntry) {
    88  		planEntry, ok := plan[replica]
    89  		require.True(t, ok, fmt.Sprintf("replica %s not found in plan", replica))
    90  		for _, block := range blocks {
    91  			require.Contains(t, planEntry.Ulids, block, "block %s not found in replica's %s plan", block, replica)
    92  		}
    93  	}
    94  }
    95  
    96  func Test_replicasPerBlockID_blockPlan(t *testing.T) {
    97  	for _, tc := range []struct {
    98  		name       string
    99  		inputs     func(r *replicasPerBlockID)
   100  		validators []validatorFunc
   101  	}{
   102  		{
   103  			name: "single ingester",
   104  			inputs: func(r *replicasPerBlockID) {
   105  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   106  					{
   107  						addr: "ingester-0",
   108  						response: []*typesv1.BlockInfo{
   109  							newBlockInfo("a").info(),
   110  							newBlockInfo("b").info(),
   111  							newBlockInfo("c").info(),
   112  						},
   113  					},
   114  				}, ingesterInstance)
   115  			},
   116  			validators: []validatorFunc{
   117  				validatePlanBlockIDs("a", "b", "c"),
   118  				validatePlanBlocksOnReplica("ingester-0", "a", "b", "c"),
   119  			},
   120  		},
   121  		{
   122  			name: "two ingester with duplicated blocks",
   123  			inputs: func(r *replicasPerBlockID) {
   124  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   125  					{
   126  						addr: "ingester-0",
   127  						response: []*typesv1.BlockInfo{
   128  							newBlockInfo("a").info(),
   129  							newBlockInfo("d").info(),
   130  						},
   131  					},
   132  					{
   133  						addr: "ingester-1",
   134  						response: []*typesv1.BlockInfo{
   135  							newBlockInfo("b").info(),
   136  							newBlockInfo("d").info(),
   137  						},
   138  					},
   139  				}, ingesterInstance)
   140  			},
   141  			validators: []validatorFunc{
   142  				validatePlanBlockIDs("a", "b", "d"),
   143  				validatePlanBlocksOnReplica("ingester-0", "a", "d"),
   144  				validatePlanBlocksOnReplica("ingester-1", "b"),
   145  			},
   146  		},
   147  		{
   148  			name: "prefer block on store-gateway over ingester",
   149  			inputs: func(r *replicasPerBlockID) {
   150  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   151  					{
   152  						addr: "ingester-0",
   153  						response: []*typesv1.BlockInfo{
   154  							newBlockInfo("a").info(),
   155  							newBlockInfo("b").info(),
   156  						},
   157  					},
   158  				}, ingesterInstance)
   159  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   160  					{
   161  						addr: "store-gateway-0",
   162  						response: []*typesv1.BlockInfo{
   163  							newBlockInfo("a").info(),
   164  						},
   165  					},
   166  				}, storeGatewayInstance)
   167  			},
   168  			validators: []validatorFunc{
   169  				validatePlanBlockIDs("a", "b"),
   170  				validatePlanBlocksOnReplica("store-gateway-0", "a"),
   171  				validatePlanBlocksOnReplica("ingester-0", "b"),
   172  			},
   173  		},
   174  		{
   175  			name: "ignore incomplete shards",
   176  			inputs: func(r *replicasPerBlockID) {
   177  				t1, _ := time.Parse(time.RFC3339, "2021-01-01T00:00:00Z")
   178  				t2, _ := time.Parse(time.RFC3339, "2021-01-01T01:00:00Z")
   179  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   180  					{
   181  						addr: "ingester-0",
   182  						response: []*typesv1.BlockInfo{
   183  							newBlockInfo("a").withMinTime(t1, time.Hour-time.Second).info(),
   184  							newBlockInfo("b").withMinTime(t2, time.Hour-time.Second).info(),
   185  						},
   186  					},
   187  				}, ingesterInstance)
   188  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   189  					{
   190  						addr: "store-gateway-0",
   191  						response: []*typesv1.BlockInfo{
   192  							newBlockInfo("a-1").
   193  								withCompactionLevel(3).
   194  								withCompactionSources("a").
   195  								withCompactionParents("a").
   196  								withCompactorShard(0, 2).
   197  								withMinTime(t1, time.Hour-time.Second).
   198  								info(),
   199  
   200  							newBlockInfo("b-1").
   201  								withCompactionLevel(3).
   202  								withCompactionSources("b").
   203  								withCompactionParents("b").
   204  								withCompactorShard(0, 2).
   205  								withMinTime(t2, time.Hour-(500*time.Millisecond)).info(),
   206  
   207  							newBlockInfo("b-2").
   208  								withCompactionLevel(3).
   209  								withCompactionSources("b").
   210  								withCompactionParents("b").
   211  								withCompactorShard(1, 2).
   212  								withMinTime(t2, time.Hour-time.Second).
   213  								info(),
   214  						},
   215  					},
   216  				}, storeGatewayInstance)
   217  			},
   218  			validators: []validatorFunc{
   219  				validatePlanBlockIDs("a", "b-1", "b-2"),
   220  				validatePlanBlocksOnReplica("store-gateway-0", "b-1"),
   221  				validatePlanBlocksOnReplica("store-gateway-0", "b-2"),
   222  				validatePlanBlocksOnReplica("ingester-0", "a"),
   223  			},
   224  		},
   225  		{
   226  			// Using a split-and-merge compactor, deduplication happens at level 3,
   227  			// level 2 is intermediate step, where series distributed among shards
   228  			// but not yet deduplicated.
   229  			name: "ignore blocks which are sharded and in level 2",
   230  			inputs: func(r *replicasPerBlockID) {
   231  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   232  					{
   233  						addr: "ingester-0",
   234  						response: []*typesv1.BlockInfo{
   235  							newBlockInfo("a").info(),
   236  							newBlockInfo("b").info(),
   237  						},
   238  					},
   239  				}, ingesterInstance)
   240  				r.add([]ResponseFromReplica[[]*typesv1.BlockInfo]{
   241  					{
   242  						addr: "store-gateway-0",
   243  						response: []*typesv1.BlockInfo{
   244  							newBlockInfo("a").info(),
   245  							newBlockInfo("a-1").
   246  								withCompactionLevel(2).
   247  								withCompactionSources("a").
   248  								withCompactionParents("a").
   249  								withCompactorShard(0, 2).
   250  								info(),
   251  
   252  							newBlockInfo("a-2").
   253  								withCompactionLevel(2).
   254  								withCompactionSources("a").
   255  								withCompactionParents("a").
   256  								withCompactorShard(1, 2).
   257  								info(),
   258  
   259  							newBlockInfo("a-3").
   260  								withCompactionLevel(3).
   261  								withCompactionSources("a-2").
   262  								withCompactorShard(0, 3).
   263  								info(),
   264  						},
   265  					},
   266  				}, storeGatewayInstance)
   267  			},
   268  			validators: []validatorFunc{
   269  				validatePlanBlockIDs("a", "b"),
   270  				validatePlanBlocksOnReplica("store-gateway-0", "a"),
   271  				validatePlanBlocksOnReplica("ingester-0", "b"),
   272  			},
   273  		},
   274  	} {
   275  		t.Run(tc.name, func(t *testing.T) {
   276  			r := newReplicasPerBlockID(log.NewNopLogger())
   277  			tc.inputs(r)
   278  
   279  			plan := r.blockPlan(context.TODO())
   280  			for _, v := range tc.validators {
   281  				v(t, plan)
   282  			}
   283  		})
   284  	}
   285  }