github.com/thanos-io/thanos@v0.32.5/pkg/store/tsdb_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package store
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"math"
    11  	"math/rand"
    12  	"sort"
    13  	"testing"
    14  
    15  	"github.com/cespare/xxhash"
    16  	"github.com/go-kit/log"
    17  	"github.com/prometheus/prometheus/model/labels"
    18  	"github.com/prometheus/prometheus/tsdb"
    19  
    20  	"github.com/efficientgo/core/testutil"
    21  
    22  	"github.com/thanos-io/thanos/pkg/component"
    23  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    24  	"github.com/thanos-io/thanos/pkg/store/storepb"
    25  	storetestutil "github.com/thanos-io/thanos/pkg/store/storepb/testutil"
    26  	"github.com/thanos-io/thanos/pkg/testutil/custom"
    27  	"github.com/thanos-io/thanos/pkg/testutil/e2eutil"
    28  )
    29  
    30  const skipMessage = "Chunk behavior changed due to https://github.com/prometheus/prometheus/pull/8723. Skip for now."
    31  
    32  func TestTSDBStore_Info(t *testing.T) {
    33  	defer custom.TolerantVerifyLeak(t)
    34  
    35  	ctx, cancel := context.WithCancel(context.Background())
    36  	defer cancel()
    37  
    38  	db, err := e2eutil.NewTSDB()
    39  	defer func() { testutil.Ok(t, db.Close()) }()
    40  	testutil.Ok(t, err)
    41  
    42  	tsdbStore := NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west"))
    43  
    44  	resp, err := tsdbStore.Info(ctx, &storepb.InfoRequest{})
    45  	testutil.Ok(t, err)
    46  
    47  	testutil.Equals(t, []labelpb.ZLabel{{Name: "region", Value: "eu-west"}}, resp.Labels)
    48  	testutil.Equals(t, storepb.StoreType_RULE, resp.StoreType)
    49  	testutil.Equals(t, int64(math.MaxInt64), resp.MinTime)
    50  	testutil.Equals(t, int64(math.MaxInt64), resp.MaxTime)
    51  
    52  	app := db.Appender(context.Background())
    53  	_, err = app.Append(0, labels.FromStrings("a", "a"), 12, 0.1)
    54  	testutil.Ok(t, err)
    55  	testutil.Ok(t, app.Commit())
    56  
    57  	resp, err = tsdbStore.Info(ctx, &storepb.InfoRequest{})
    58  	testutil.Ok(t, err)
    59  
    60  	testutil.Equals(t, []labelpb.ZLabel{{Name: "region", Value: "eu-west"}}, resp.Labels)
    61  	testutil.Equals(t, storepb.StoreType_RULE, resp.StoreType)
    62  	testutil.Equals(t, int64(12), resp.MinTime)
    63  	testutil.Equals(t, int64(math.MaxInt64), resp.MaxTime)
    64  }
    65  
    66  func TestTSDBStore_Series_ChunkChecksum(t *testing.T) {
    67  	defer custom.TolerantVerifyLeak(t)
    68  
    69  	ctx, cancel := context.WithCancel(context.Background())
    70  	defer cancel()
    71  
    72  	db, err := e2eutil.NewTSDB()
    73  	defer func() { testutil.Ok(t, db.Close()) }()
    74  	testutil.Ok(t, err)
    75  
    76  	tsdbStore := NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west"))
    77  
    78  	appender := db.Appender(context.Background())
    79  
    80  	for i := 1; i <= 3; i++ {
    81  		_, err = appender.Append(0, labels.FromStrings("a", "1"), int64(i), float64(i))
    82  		testutil.Ok(t, err)
    83  	}
    84  	err = appender.Commit()
    85  	testutil.Ok(t, err)
    86  
    87  	srv := newStoreSeriesServer(ctx)
    88  
    89  	req := &storepb.SeriesRequest{
    90  		MinTime: 1,
    91  		MaxTime: 3,
    92  		Matchers: []storepb.LabelMatcher{
    93  			{Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"},
    94  		},
    95  	}
    96  
    97  	err = tsdbStore.Series(req, srv)
    98  	testutil.Ok(t, err)
    99  
   100  	for _, chk := range srv.SeriesSet[0].Chunks {
   101  		want := xxhash.Sum64(chk.Raw.Data)
   102  		testutil.Equals(t, want, chk.Raw.Hash)
   103  	}
   104  }
   105  
   106  func TestTSDBStore_Series(t *testing.T) {
   107  	defer custom.TolerantVerifyLeak(t)
   108  
   109  	ctx, cancel := context.WithCancel(context.Background())
   110  	defer cancel()
   111  
   112  	db, err := e2eutil.NewTSDB()
   113  	defer func() { testutil.Ok(t, db.Close()) }()
   114  	testutil.Ok(t, err)
   115  
   116  	tsdbStore := NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west"))
   117  
   118  	appender := db.Appender(context.Background())
   119  
   120  	for i := 1; i <= 3; i++ {
   121  		_, err = appender.Append(0, labels.FromStrings("a", "1"), int64(i), float64(i))
   122  		testutil.Ok(t, err)
   123  	}
   124  	err = appender.Commit()
   125  	testutil.Ok(t, err)
   126  
   127  	for _, tc := range []struct {
   128  		title          string
   129  		req            *storepb.SeriesRequest
   130  		expectedSeries []rawSeries
   131  		expectedError  string
   132  	}{
   133  		{
   134  			title: "total match series",
   135  			req: &storepb.SeriesRequest{
   136  				MinTime: 1,
   137  				MaxTime: 3,
   138  				Matchers: []storepb.LabelMatcher{
   139  					{Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"},
   140  				},
   141  			},
   142  			expectedSeries: []rawSeries{
   143  				{
   144  					lset:   labels.FromStrings("a", "1", "region", "eu-west"),
   145  					chunks: [][]sample{{{1, 1}, {2, 2}, {3, 3}}},
   146  				},
   147  			},
   148  		},
   149  		{
   150  			title: "partially match time range series",
   151  			req: &storepb.SeriesRequest{
   152  				MinTime: 1,
   153  				MaxTime: 2,
   154  				Matchers: []storepb.LabelMatcher{
   155  					{Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"},
   156  				},
   157  			},
   158  			expectedSeries: []rawSeries{
   159  				{
   160  					lset:   labels.FromStrings("a", "1", "region", "eu-west"),
   161  					chunks: [][]sample{{{1, 1}, {2, 2}}},
   162  				},
   163  			},
   164  		},
   165  		{
   166  			title: "dont't match time range series",
   167  			req: &storepb.SeriesRequest{
   168  				MinTime: 4,
   169  				MaxTime: 6,
   170  				Matchers: []storepb.LabelMatcher{
   171  					{Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"},
   172  				},
   173  			},
   174  			expectedSeries: []rawSeries{},
   175  		},
   176  		{
   177  			title: "only match external label",
   178  			req: &storepb.SeriesRequest{
   179  				MinTime: 1,
   180  				MaxTime: 3,
   181  				Matchers: []storepb.LabelMatcher{
   182  					{Type: storepb.LabelMatcher_EQ, Name: "region", Value: "eu-west"},
   183  				},
   184  			},
   185  			expectedError: "rpc error: code = InvalidArgument desc = no matchers specified (excluding external labels)",
   186  		},
   187  		{
   188  			title: "dont't match labels",
   189  			req: &storepb.SeriesRequest{
   190  				MinTime: 1,
   191  				MaxTime: 3,
   192  				Matchers: []storepb.LabelMatcher{
   193  					{Type: storepb.LabelMatcher_EQ, Name: "b", Value: "1"},
   194  				},
   195  			},
   196  			expectedSeries: []rawSeries{},
   197  		},
   198  		{
   199  			title: "no chunk",
   200  			req: &storepb.SeriesRequest{
   201  				MinTime: 1,
   202  				MaxTime: 3,
   203  				Matchers: []storepb.LabelMatcher{
   204  					{Type: storepb.LabelMatcher_EQ, Name: "a", Value: "1"},
   205  				},
   206  				SkipChunks: true,
   207  			},
   208  			expectedSeries: []rawSeries{
   209  				{
   210  					lset: labels.FromStrings("a", "1", "region", "eu-west"),
   211  				},
   212  			},
   213  		},
   214  	} {
   215  		if ok := t.Run(tc.title, func(t *testing.T) {
   216  			srv := newStoreSeriesServer(ctx)
   217  			err := tsdbStore.Series(tc.req, srv)
   218  			if len(tc.expectedError) > 0 {
   219  				testutil.NotOk(t, err)
   220  				testutil.Equals(t, tc.expectedError, err.Error())
   221  			} else {
   222  				testutil.Ok(t, err)
   223  				seriesEquals(t, tc.expectedSeries, srv.SeriesSet)
   224  			}
   225  		}); !ok {
   226  			return
   227  		}
   228  	}
   229  }
   230  
   231  // Regression test for https://github.com/thanos-io/thanos/issues/1038.
   232  func TestTSDBStore_Series_SplitSamplesIntoChunksWithMaxSizeOf120(t *testing.T) {
   233  	defer custom.TolerantVerifyLeak(t)
   234  
   235  	db, err := e2eutil.NewTSDB()
   236  	defer func() { testutil.Ok(t, db.Close()) }()
   237  	testutil.Ok(t, err)
   238  
   239  	testSeries_SplitSamplesIntoChunksWithMaxSizeOf120(t, db.Appender(context.Background()), func() storepb.StoreServer {
   240  		return NewTSDBStore(nil, db, component.Rule, labels.FromStrings("region", "eu-west"))
   241  
   242  	})
   243  }
   244  
   245  type delegatorServer struct {
   246  	*storetestutil.SeriesServer
   247  
   248  	closers []io.Closer
   249  }
   250  
   251  func (s *delegatorServer) Delegate(c io.Closer) {
   252  	s.closers = append(s.closers, c)
   253  }
   254  
   255  // Regression test for: https://github.com/thanos-io/thanos/issues/3013 .
   256  func TestTSDBStore_SeriesAccessWithDelegateClosing(t *testing.T) {
   257  	t.Skip(skipMessage)
   258  
   259  	tmpDir := t.TempDir()
   260  
   261  	var (
   262  		random = rand.New(rand.NewSource(120))
   263  		logger = log.NewNopLogger()
   264  	)
   265  
   266  	// Generate one series in two parts. Put first part in block, second in just WAL.
   267  	head, _ := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{
   268  		TSDBDir:          tmpDir,
   269  		SamplesPerSeries: 300,
   270  		Series:           2,
   271  		Random:           random,
   272  		SkipChunks:       true,
   273  	})
   274  	_ = createBlockFromHead(t, tmpDir, head)
   275  	testutil.Ok(t, head.Close())
   276  
   277  	head, _ = storetestutil.CreateHeadWithSeries(t, 1, storetestutil.HeadGenOptions{
   278  		TSDBDir:          tmpDir,
   279  		SamplesPerSeries: 300,
   280  		Series:           2,
   281  		WithWAL:          true,
   282  		Random:           random,
   283  		SkipChunks:       true,
   284  	})
   285  	testutil.Ok(t, head.Close())
   286  
   287  	db, err := tsdb.OpenDBReadOnly(tmpDir, logger)
   288  	testutil.Ok(t, err)
   289  
   290  	dbToClose := make(chan *tsdb.DBReadOnly, 1)
   291  	dbToClose <- db
   292  	t.Cleanup(func() {
   293  		// Close if not closed before.
   294  		select {
   295  		case db := <-dbToClose:
   296  			testutil.Ok(t, db.Close())
   297  		default:
   298  		}
   299  	})
   300  
   301  	extLabels := labels.FromStrings("ext", "1")
   302  	store := NewTSDBStore(logger, &mockedStartTimeDB{DBReadOnly: db, startTime: 0}, component.Receive, extLabels)
   303  
   304  	srv := storetestutil.NewSeriesServer(context.Background())
   305  	csrv := &delegatorServer{SeriesServer: srv}
   306  	t.Run("call series and access results", func(t *testing.T) {
   307  		testutil.Ok(t, store.Series(&storepb.SeriesRequest{
   308  			MinTime: 0,
   309  			MaxTime: math.MaxInt64,
   310  			Matchers: []storepb.LabelMatcher{
   311  				{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
   312  			},
   313  			PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
   314  		}, csrv))
   315  		testutil.Equals(t, 0, len(srv.Warnings))
   316  		testutil.Equals(t, 0, len(srv.HintsSet))
   317  		testutil.Equals(t, 4, len(srv.SeriesSet))
   318  
   319  		// All chunks should be accessible for read, but not necessarily for write.
   320  		for _, s := range srv.SeriesSet {
   321  			testutil.Equals(t, 3, len(s.Chunks))
   322  			for _, c := range s.Chunks {
   323  				testutil.Ok(t, testutil.FaultOrPanicToErr(func() {
   324  					_ = string(c.Raw.Data) // Access bytes by converting them to different type.
   325  				}))
   326  			}
   327  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   328  				s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   329  				s.Chunks[1].Raw.Data[0] = 0
   330  				s.Chunks[2].Raw.Data[0] = 0
   331  			}))
   332  		}
   333  	})
   334  
   335  	flushDone := make(chan error)
   336  	t.Run("flush WAL and access results", func(t *testing.T) {
   337  		go func() {
   338  			// This should block until all queries are closed.
   339  			flushDone <- db.FlushWAL(tmpDir)
   340  			close(flushDone)
   341  		}()
   342  		// All chunks should be still accessible for read, but not necessarily for write.
   343  		for _, s := range srv.SeriesSet {
   344  			for _, c := range s.Chunks {
   345  				testutil.Ok(t, testutil.FaultOrPanicToErr(func() {
   346  					_ = string(c.Raw.Data) // Access bytes by converting them to different type.
   347  				}))
   348  			}
   349  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   350  				s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   351  				s.Chunks[1].Raw.Data[0] = 0
   352  				s.Chunks[2].Raw.Data[0] = 0
   353  			}))
   354  		}
   355  	})
   356  	select {
   357  	case err, ok := <-flushDone:
   358  		if !ok {
   359  			t.Fatalf("expected flush to be blocked, but it seems it completed. Result: %v", err)
   360  		}
   361  	default:
   362  	}
   363  
   364  	closeDone := make(chan error)
   365  	t.Run("close db with block readers and access results", func(t *testing.T) {
   366  		go func() {
   367  			select {
   368  			case db := <-dbToClose:
   369  				// This should block until all queries are closed.
   370  				closeDone <- db.Close()
   371  			default:
   372  			}
   373  			close(closeDone)
   374  		}()
   375  		// All chunks should be still accessible for read, but not necessarily for write.
   376  		for _, s := range srv.SeriesSet {
   377  			for _, c := range s.Chunks {
   378  				testutil.Ok(t, testutil.FaultOrPanicToErr(func() {
   379  					_ = string(c.Raw.Data) // Access bytes by converting them to different type.
   380  				}))
   381  			}
   382  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   383  				s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   384  				s.Chunks[1].Raw.Data[0] = 0
   385  				s.Chunks[2].Raw.Data[0] = 0
   386  			}))
   387  		}
   388  	})
   389  	select {
   390  	case _, ok := <-closeDone:
   391  		if !ok {
   392  			t.Fatalf("expected db cloe to be blocked, but it seems it completed. Result: %v", err)
   393  
   394  		}
   395  	default:
   396  	}
   397  
   398  	t.Run("close querier and access results", func(t *testing.T) {
   399  		// Let's close pending querier!
   400  		testutil.Equals(t, 1, len(csrv.closers))
   401  		testutil.Ok(t, csrv.closers[0].Close())
   402  
   403  		// Expect flush and close to be unblocked and without errors.
   404  		testutil.Ok(t, <-flushDone)
   405  		testutil.Ok(t, <-closeDone)
   406  
   407  		// Expect segfault on read and write.
   408  		t.Run("non delegatable", func(t *testing.T) {
   409  			for _, s := range srv.SeriesSet {
   410  				testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   411  					_ = string(s.Chunks[0].Raw.Data) // Access bytes by converting them to different type.
   412  					_ = string(s.Chunks[1].Raw.Data)
   413  					_ = string(s.Chunks[2].Raw.Data)
   414  				}))
   415  				testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   416  					s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   417  					s.Chunks[1].Raw.Data[0] = 0
   418  					s.Chunks[2].Raw.Data[0] = 0
   419  				}))
   420  			}
   421  		})
   422  	})
   423  }
   424  
   425  func TestTSDBStore_SeriesAccessWithoutDelegateClosing(t *testing.T) {
   426  	t.Skip(skipMessage)
   427  
   428  	tmpDir := t.TempDir()
   429  
   430  	var (
   431  		random = rand.New(rand.NewSource(120))
   432  		logger = log.NewNopLogger()
   433  	)
   434  
   435  	// Generate one series in two parts. Put first part in block, second in just WAL.
   436  	head, _ := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{
   437  		TSDBDir:          tmpDir,
   438  		SamplesPerSeries: 300,
   439  		Series:           2,
   440  		Random:           random,
   441  		SkipChunks:       true,
   442  	})
   443  	_ = createBlockFromHead(t, tmpDir, head)
   444  	testutil.Ok(t, head.Close())
   445  
   446  	head, _ = storetestutil.CreateHeadWithSeries(t, 1, storetestutil.HeadGenOptions{
   447  		TSDBDir:          tmpDir,
   448  		SamplesPerSeries: 300,
   449  		Series:           2,
   450  		WithWAL:          true,
   451  		Random:           random,
   452  		SkipChunks:       true,
   453  	})
   454  	testutil.Ok(t, head.Close())
   455  
   456  	db, err := tsdb.OpenDBReadOnly(tmpDir, logger)
   457  	testutil.Ok(t, err)
   458  	t.Cleanup(func() {
   459  		if db != nil {
   460  			testutil.Ok(t, db.Close())
   461  		}
   462  	})
   463  
   464  	extLabels := labels.FromStrings("ext", "1")
   465  	store := NewTSDBStore(logger, &mockedStartTimeDB{DBReadOnly: db, startTime: 0}, component.Receive, extLabels)
   466  
   467  	srv := storetestutil.NewSeriesServer(context.Background())
   468  	t.Run("call series and access results", func(t *testing.T) {
   469  		testutil.Ok(t, store.Series(&storepb.SeriesRequest{
   470  			MinTime: 0,
   471  			MaxTime: math.MaxInt64,
   472  			Matchers: []storepb.LabelMatcher{
   473  				{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
   474  			},
   475  			PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
   476  		}, srv))
   477  		testutil.Equals(t, 0, len(srv.Warnings))
   478  		testutil.Equals(t, 0, len(srv.HintsSet))
   479  		testutil.Equals(t, 4, len(srv.SeriesSet))
   480  
   481  		// All chunks should be accessible for read, but not necessarily for write.
   482  		for _, s := range srv.SeriesSet {
   483  			testutil.Equals(t, 3, len(s.Chunks))
   484  			for _, c := range s.Chunks {
   485  				testutil.Ok(t, testutil.FaultOrPanicToErr(func() {
   486  					_ = string(c.Raw.Data) // Access bytes by converting them to different type.
   487  				}))
   488  			}
   489  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   490  				s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   491  				s.Chunks[1].Raw.Data[0] = 0
   492  				s.Chunks[2].Raw.Data[0] = 0
   493  			}))
   494  		}
   495  	})
   496  
   497  	t.Run("flush WAL and access results", func(t *testing.T) {
   498  		// This should NOT block as close was not delegated.
   499  		testutil.Ok(t, db.FlushWAL(tmpDir))
   500  
   501  		// Expect segfault on read and write.
   502  		for _, s := range srv.SeriesSet {
   503  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   504  				_ = string(s.Chunks[0].Raw.Data) // Access bytes by converting them to different type.
   505  				_ = string(s.Chunks[1].Raw.Data)
   506  				_ = string(s.Chunks[2].Raw.Data)
   507  			}))
   508  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   509  				s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   510  				s.Chunks[1].Raw.Data[0] = 0
   511  				s.Chunks[2].Raw.Data[0] = 0
   512  			}))
   513  		}
   514  	})
   515  	t.Run("close db with block readers and access results", func(t *testing.T) {
   516  		// This should NOT block as close was not delegated.
   517  		testutil.Ok(t, db.Close())
   518  		db = nil
   519  
   520  		// Expect segfault on read and write.
   521  		for _, s := range srv.SeriesSet {
   522  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   523  				_ = string(s.Chunks[0].Raw.Data) // Access bytes by converting them to different type.
   524  				_ = string(s.Chunks[1].Raw.Data)
   525  				_ = string(s.Chunks[2].Raw.Data)
   526  			}))
   527  			testutil.NotOk(t, testutil.FaultOrPanicToErr(func() {
   528  				s.Chunks[0].Raw.Data[0] = 0 // Check if we can write to the byte range.
   529  				s.Chunks[1].Raw.Data[0] = 0
   530  				s.Chunks[2].Raw.Data[0] = 0
   531  			}))
   532  		}
   533  	})
   534  }
   535  
   536  func TestTSDBStoreSeries(t *testing.T) {
   537  	tb := testutil.NewTB(t)
   538  	// Make sure there are more samples, so we can check framing code.
   539  	storetestutil.RunSeriesInterestingCases(tb, 10e6, 200e3, func(t testutil.TB, samplesPerSeries, series int) {
   540  		benchTSDBStoreSeries(t, samplesPerSeries, series)
   541  	})
   542  }
   543  
   544  func BenchmarkTSDBStoreSeries(b *testing.B) {
   545  	tb := testutil.NewTB(b)
   546  	storetestutil.RunSeriesInterestingCases(tb, 10e6, 10e5, func(t testutil.TB, samplesPerSeries, series int) {
   547  		benchTSDBStoreSeries(t, samplesPerSeries, series)
   548  	})
   549  }
   550  
   551  func benchTSDBStoreSeries(t testutil.TB, totalSamples, totalSeries int) {
   552  	tmpDir := t.TempDir()
   553  
   554  	// This means 3 blocks and the head.
   555  	const numOfBlocks = 4
   556  
   557  	samplesPerSeriesPerBlock := totalSamples / numOfBlocks
   558  	if samplesPerSeriesPerBlock == 0 {
   559  		samplesPerSeriesPerBlock = 1
   560  	}
   561  	seriesPerBlock := totalSeries / numOfBlocks
   562  	if seriesPerBlock == 0 {
   563  		seriesPerBlock = 1
   564  	}
   565  
   566  	var (
   567  		resps  = make([][]*storepb.SeriesResponse, 4)
   568  		random = rand.New(rand.NewSource(120))
   569  		logger = log.NewNopLogger()
   570  	)
   571  
   572  	for j := 0; j < 3; j++ {
   573  		head, created := storetestutil.CreateHeadWithSeries(t, j, storetestutil.HeadGenOptions{
   574  			TSDBDir:          tmpDir,
   575  			SamplesPerSeries: samplesPerSeriesPerBlock,
   576  			Series:           seriesPerBlock,
   577  			Random:           random,
   578  		})
   579  		for i := 0; i < len(created); i++ {
   580  			resps[j] = append(resps[j], storepb.NewSeriesResponse(created[i]))
   581  		}
   582  
   583  		_ = createBlockFromHead(t, tmpDir, head)
   584  		testutil.Ok(t, head.Close())
   585  	}
   586  
   587  	head2, created := storetestutil.CreateHeadWithSeries(t, 3, storetestutil.HeadGenOptions{
   588  		TSDBDir:          tmpDir,
   589  		SamplesPerSeries: samplesPerSeriesPerBlock,
   590  		Series:           seriesPerBlock,
   591  		WithWAL:          true,
   592  		Random:           random,
   593  	})
   594  	testutil.Ok(t, head2.Close())
   595  
   596  	for i := 0; i < len(created); i++ {
   597  		resps[3] = append(resps[3], storepb.NewSeriesResponse(created[i]))
   598  	}
   599  
   600  	db, err := tsdb.OpenDBReadOnly(tmpDir, logger)
   601  	testutil.Ok(t, err)
   602  
   603  	defer func() { testutil.Ok(t, db.Close()) }()
   604  
   605  	extLabels := labels.FromStrings("ext", "1")
   606  	store := NewTSDBStore(logger, &mockedStartTimeDB{DBReadOnly: db, startTime: 0}, component.Receive, extLabels)
   607  
   608  	var expected []*storepb.Series
   609  	for _, resp := range resps {
   610  		for _, r := range resp {
   611  			// Add external labels & frame it.
   612  			s := r.GetSeries()
   613  			bytesLeftForChunks := store.maxBytesPerFrame
   614  			lbls := make([]labelpb.ZLabel, 0, len(s.Labels)+len(extLabels))
   615  			for _, l := range s.Labels {
   616  				lbls = append(lbls, labelpb.ZLabel{
   617  					Name:  l.Name,
   618  					Value: l.Value,
   619  				})
   620  				bytesLeftForChunks -= lbls[len(lbls)-1].Size()
   621  			}
   622  			for _, l := range extLabels {
   623  				lbls = append(lbls, labelpb.ZLabel{
   624  					Name:  l.Name,
   625  					Value: l.Value,
   626  				})
   627  				bytesLeftForChunks -= lbls[len(lbls)-1].Size()
   628  			}
   629  			sort.Slice(lbls, func(i, j int) bool {
   630  				return lbls[i].Name < lbls[j].Name
   631  			})
   632  
   633  			frameBytesLeft := bytesLeftForChunks
   634  			frame := &storepb.Series{Labels: lbls}
   635  			for i, c := range s.Chunks {
   636  				frame.Chunks = append(frame.Chunks, c)
   637  				frameBytesLeft -= c.Size()
   638  
   639  				if i == len(s.Chunks)-1 {
   640  					break
   641  				}
   642  
   643  				if frameBytesLeft > 0 {
   644  					continue
   645  				}
   646  				expected = append(expected, frame)
   647  				frameBytesLeft = bytesLeftForChunks
   648  				frame = &storepb.Series{Labels: lbls}
   649  			}
   650  			expected = append(expected, frame)
   651  		}
   652  	}
   653  
   654  	storetestutil.TestServerSeries(t, store,
   655  		&storetestutil.SeriesCase{
   656  			Name: fmt.Sprintf("%d blocks and one WAL with %d samples, %d series each", numOfBlocks-1, samplesPerSeriesPerBlock, seriesPerBlock),
   657  			Req: &storepb.SeriesRequest{
   658  				MinTime: 0,
   659  				MaxTime: math.MaxInt64,
   660  				Matchers: []storepb.LabelMatcher{
   661  					{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
   662  				},
   663  				PartialResponseStrategy: storepb.PartialResponseStrategy_ABORT,
   664  			},
   665  			ExpectedSeries: expected,
   666  		},
   667  	)
   668  }