github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/stores/tsdb/head_manager_test.go (about) 1 package tsdb 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/go-kit/log" 12 "github.com/prometheus/common/model" 13 "github.com/prometheus/prometheus/model/labels" 14 "github.com/prometheus/prometheus/tsdb/chunks" 15 "github.com/prometheus/prometheus/tsdb/record" 16 "github.com/stretchr/testify/require" 17 18 "github.com/grafana/loki/pkg/storage/chunk/client/util" 19 "github.com/grafana/loki/pkg/storage/stores/tsdb/index" 20 ) 21 22 type noopTSDBManager struct { 23 dir string 24 *tenantHeads 25 } 26 27 func newNoopTSDBManager(dir string) noopTSDBManager { 28 return noopTSDBManager{ 29 dir: dir, 30 tenantHeads: newTenantHeads(time.Now(), defaultHeadManagerStripeSize, NewMetrics(nil), log.NewNopLogger()), 31 } 32 } 33 34 func (m noopTSDBManager) BuildFromWALs(_ time.Time, wals []WALIdentifier) error { 35 return recoverHead(m.dir, m.tenantHeads, wals) 36 } 37 func (m noopTSDBManager) Start() error { return nil } 38 39 func chunkMetasToChunkRefs(user string, fp uint64, xs index.ChunkMetas) (res []ChunkRef) { 40 for _, x := range xs { 41 res = append(res, ChunkRef{ 42 User: user, 43 Fingerprint: model.Fingerprint(fp), 44 Start: x.From(), 45 End: x.Through(), 46 Checksum: x.Checksum, 47 }) 48 } 49 return 50 } 51 52 // Test append 53 func Test_TenantHeads_Append(t *testing.T) { 54 h := newTenantHeads(time.Now(), defaultHeadManagerStripeSize, NewMetrics(nil), log.NewNopLogger()) 55 ls := mustParseLabels(`{foo="bar"}`) 56 chks := []index.ChunkMeta{ 57 { 58 Checksum: 0, 59 MinTime: 1, 60 MaxTime: 10, 61 KB: 2, 62 Entries: 30, 63 }, 64 } 65 _ = h.Append("fake", ls, chks) 66 67 found, err := h.GetChunkRefs( 68 context.Background(), 69 "fake", 70 0, 71 100, 72 nil, nil, 73 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 74 ) 75 require.Nil(t, err) 76 require.Equal(t, chunkMetasToChunkRefs("fake", ls.Hash(), chks), found) 77 78 } 79 80 // Test multitenant reads 81 func Test_TenantHeads_MultiRead(t *testing.T) { 82 h := newTenantHeads(time.Now(), defaultHeadManagerStripeSize, NewMetrics(nil), log.NewNopLogger()) 83 ls := mustParseLabels(`{foo="bar"}`) 84 chks := []index.ChunkMeta{ 85 { 86 Checksum: 0, 87 MinTime: 1, 88 MaxTime: 10, 89 KB: 2, 90 Entries: 30, 91 }, 92 } 93 94 tenants := []struct { 95 user string 96 ls labels.Labels 97 }{ 98 { 99 user: "tenant1", 100 ls: append(ls.Copy(), labels.Label{ 101 Name: "tenant", 102 Value: "tenant1", 103 }), 104 }, 105 { 106 user: "tenant2", 107 ls: append(ls.Copy(), labels.Label{ 108 Name: "tenant", 109 Value: "tenant2", 110 }), 111 }, 112 } 113 114 // add data for both tenants 115 for _, tenant := range tenants { 116 _ = h.Append(tenant.user, tenant.ls, chks) 117 118 } 119 120 // ensure we're only returned the data from the correct tenant 121 for _, tenant := range tenants { 122 found, err := h.GetChunkRefs( 123 context.Background(), 124 tenant.user, 125 0, 126 100, 127 nil, nil, 128 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 129 ) 130 require.Nil(t, err) 131 require.Equal(t, chunkMetasToChunkRefs(tenant.user, tenant.ls.Hash(), chks), found) 132 } 133 134 } 135 136 // test head recover from wal 137 func Test_HeadManager_RecoverHead(t *testing.T) { 138 now := time.Now() 139 dir := t.TempDir() 140 cases := []struct { 141 Labels labels.Labels 142 Chunks []index.ChunkMeta 143 User string 144 }{ 145 { 146 User: "tenant1", 147 Labels: mustParseLabels(`{foo="bar", bazz="buzz"}`), 148 Chunks: []index.ChunkMeta{ 149 { 150 MinTime: 1, 151 MaxTime: 10, 152 Checksum: 3, 153 }, 154 }, 155 }, 156 { 157 User: "tenant2", 158 Labels: mustParseLabels(`{foo="bard", bazz="bozz", bonk="borb"}`), 159 Chunks: []index.ChunkMeta{ 160 { 161 MinTime: 1, 162 MaxTime: 7, 163 Checksum: 4, 164 }, 165 }, 166 }, 167 } 168 169 mgr := NewHeadManager(log.NewNopLogger(), dir, nil, newNoopTSDBManager(dir)) 170 // This bit is normally handled by the Start() fn, but we're testing a smaller surface area 171 // so ensure our dirs exist 172 for _, d := range managerRequiredDirs(dir) { 173 require.Nil(t, util.EnsureDirectory(d)) 174 } 175 176 // Call Rotate() to ensure the new head tenant heads exist, etc 177 require.Nil(t, mgr.Rotate(now)) 178 179 // now build a WAL independently to test recovery 180 w, err := newHeadWAL(log.NewNopLogger(), walPath(mgr.dir, now), now) 181 require.Nil(t, err) 182 183 for i, c := range cases { 184 require.Nil(t, w.Log(&WALRecord{ 185 UserID: c.User, 186 Series: record.RefSeries{ 187 Ref: chunks.HeadSeriesRef(i), 188 Labels: c.Labels, 189 }, 190 Chks: ChunkMetasRecord{ 191 Chks: c.Chunks, 192 Ref: uint64(i), 193 }, 194 })) 195 } 196 197 require.Nil(t, w.Stop()) 198 199 grp, ok, err := walsForPeriod(mgr.dir, mgr.period, mgr.period.PeriodFor(now)) 200 require.Nil(t, err) 201 require.True(t, ok) 202 require.Equal(t, 1, len(grp.wals)) 203 require.Nil(t, recoverHead(mgr.dir, mgr.activeHeads, grp.wals)) 204 205 for _, c := range cases { 206 refs, err := mgr.GetChunkRefs( 207 context.Background(), 208 c.User, 209 0, math.MaxInt64, 210 nil, nil, 211 labels.MustNewMatcher(labels.MatchRegexp, "foo", ".+"), 212 ) 213 require.Nil(t, err) 214 require.Equal(t, chunkMetasToChunkRefs(c.User, c.Labels.Hash(), c.Chunks), refs) 215 } 216 217 } 218 219 // test mgr recover from multiple wals across multiple periods 220 func Test_HeadManager_Lifecycle(t *testing.T) { 221 dir := t.TempDir() 222 curPeriod := time.Now() 223 cases := []struct { 224 Labels labels.Labels 225 Chunks []index.ChunkMeta 226 User string 227 }{ 228 { 229 User: "tenant1", 230 Labels: mustParseLabels(`{foo="bar", bazz="buzz"}`), 231 Chunks: []index.ChunkMeta{ 232 { 233 MinTime: 1, 234 MaxTime: 10, 235 Checksum: 3, 236 }, 237 }, 238 }, 239 { 240 User: "tenant2", 241 Labels: mustParseLabels(`{foo="bard", bazz="bozz", bonk="borb"}`), 242 Chunks: []index.ChunkMeta{ 243 { 244 MinTime: 1, 245 MaxTime: 7, 246 Checksum: 4, 247 }, 248 }, 249 }, 250 } 251 252 mgr := NewHeadManager(log.NewNopLogger(), dir, nil, newNoopTSDBManager(dir)) 253 w, err := newHeadWAL(log.NewNopLogger(), walPath(mgr.dir, curPeriod), curPeriod) 254 require.Nil(t, err) 255 256 // Write old WALs 257 for i, c := range cases { 258 require.Nil(t, w.Log(&WALRecord{ 259 UserID: c.User, 260 Series: record.RefSeries{ 261 Ref: chunks.HeadSeriesRef(i), 262 Labels: c.Labels, 263 }, 264 Chks: ChunkMetasRecord{ 265 Chks: c.Chunks, 266 Ref: uint64(i), 267 }, 268 })) 269 } 270 271 require.Nil(t, w.Stop()) 272 273 // Start, ensuring recovery from old WALs 274 require.Nil(t, mgr.Start()) 275 276 // Ensure old WAL data is queryable 277 multiIndex, err := NewMultiIndex(mgr, mgr.tsdbManager.(noopTSDBManager).tenantHeads) 278 require.Nil(t, err) 279 280 for _, c := range cases { 281 refs, err := multiIndex.GetChunkRefs( 282 context.Background(), 283 c.User, 284 0, math.MaxInt64, 285 nil, nil, 286 labels.MustNewMatcher(labels.MatchRegexp, "foo", ".+"), 287 ) 288 require.Nil(t, err) 289 290 lbls := labels.NewBuilder(c.Labels) 291 lbls.Set(TenantLabel, c.User) 292 require.Equal(t, chunkMetasToChunkRefs(c.User, c.Labels.Hash(), c.Chunks), refs) 293 } 294 295 // Add data 296 newCase := struct { 297 Labels labels.Labels 298 Chunks []index.ChunkMeta 299 User string 300 }{ 301 User: "tenant3", 302 Labels: mustParseLabels(`{foo="bard", other="hi"}`), 303 Chunks: []index.ChunkMeta{ 304 { 305 MinTime: 1, 306 MaxTime: 7, 307 Checksum: 4, 308 }, 309 }, 310 } 311 312 require.Nil(t, mgr.Append(newCase.User, newCase.Labels, newCase.Chunks)) 313 314 // Ensure old + new data is queryable 315 for _, c := range append(cases, newCase) { 316 refs, err := multiIndex.GetChunkRefs( 317 context.Background(), 318 c.User, 319 0, math.MaxInt64, 320 nil, nil, 321 labels.MustNewMatcher(labels.MatchRegexp, "foo", ".+"), 322 ) 323 require.Nil(t, err) 324 325 lbls := labels.NewBuilder(c.Labels) 326 lbls.Set(TenantLabel, c.User) 327 require.Equal(t, chunkMetasToChunkRefs(c.User, c.Labels.Hash(), c.Chunks), refs) 328 } 329 } 330 331 func BenchmarkTenantHeads(b *testing.B) { 332 for _, tc := range []struct { 333 readers, writers int 334 }{ 335 { 336 readers: 10, 337 }, 338 { 339 readers: 100, 340 }, 341 { 342 readers: 1000, 343 }, 344 } { 345 b.Run(fmt.Sprintf("%d", tc.readers), func(b *testing.B) { 346 heads := newTenantHeads(time.Now(), defaultHeadManagerStripeSize, NewMetrics(nil), log.NewNopLogger()) 347 // 1000 series across 100 tenants 348 nTenants := 10 349 for i := 0; i < 1000; i++ { 350 tenant := i % nTenants 351 ls := mustParseLabels(fmt.Sprintf(`{foo="bar", i="%d"}`, i)) 352 heads.Append(fmt.Sprint(tenant), ls, index.ChunkMetas{ 353 {}, 354 }) 355 } 356 357 for n := 0; n < b.N; n++ { 358 var wg sync.WaitGroup 359 for r := 0; r < tc.readers; r++ { 360 wg.Add(1) 361 go func(r int) { 362 defer wg.Done() 363 var res []ChunkRef 364 tenant := r % nTenants 365 366 // nolint:ineffassign,staticcheck 367 res, _ = heads.GetChunkRefs( 368 context.Background(), 369 fmt.Sprint(tenant), 370 0, math.MaxInt64, 371 res, 372 nil, 373 labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"), 374 ) 375 }(r) 376 } 377 378 wg.Wait() 379 } 380 381 }) 382 } 383 }