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 }