github.com/thanos-io/thanos@v0.32.5/pkg/receive/multitsdb_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package receive
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"math"
    10  	"os"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/efficientgo/core/testutil"
    16  	"github.com/go-kit/log"
    17  	"github.com/prometheus/client_golang/prometheus"
    18  	"github.com/prometheus/prometheus/model/exemplar"
    19  	"github.com/prometheus/prometheus/model/labels"
    20  	"github.com/prometheus/prometheus/storage"
    21  	"github.com/prometheus/prometheus/tsdb"
    22  	"github.com/thanos-io/objstore"
    23  	"golang.org/x/sync/errgroup"
    24  	"google.golang.org/grpc"
    25  
    26  	"github.com/thanos-io/thanos/pkg/block/metadata"
    27  	"github.com/thanos-io/thanos/pkg/component"
    28  	"github.com/thanos-io/thanos/pkg/exemplars/exemplarspb"
    29  	"github.com/thanos-io/thanos/pkg/runutil"
    30  	"github.com/thanos-io/thanos/pkg/store"
    31  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    32  	"github.com/thanos-io/thanos/pkg/store/storepb"
    33  )
    34  
    35  func TestMultiTSDB(t *testing.T) {
    36  	dir := t.TempDir()
    37  
    38  	logger := log.NewLogfmtLogger(os.Stderr)
    39  	t.Run("run fresh", func(t *testing.T) {
    40  		m := NewMultiTSDB(
    41  			dir, logger, prometheus.NewRegistry(), &tsdb.Options{
    42  				MinBlockDuration:      (2 * time.Hour).Milliseconds(),
    43  				MaxBlockDuration:      (2 * time.Hour).Milliseconds(),
    44  				RetentionDuration:     (6 * time.Hour).Milliseconds(),
    45  				NoLockfile:            true,
    46  				MaxExemplars:          100,
    47  				EnableExemplarStorage: true,
    48  			},
    49  			labels.FromStrings("replica", "01"),
    50  			"tenant_id",
    51  			nil,
    52  			false,
    53  			metadata.NoneFunc,
    54  		)
    55  		defer func() { testutil.Ok(t, m.Close()) }()
    56  
    57  		testutil.Ok(t, m.Flush())
    58  		testutil.Ok(t, m.Open())
    59  
    60  		app, err := m.TenantAppendable("foo")
    61  		testutil.Ok(t, err)
    62  
    63  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    64  		defer cancel()
    65  
    66  		var a storage.Appender
    67  		testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error {
    68  			a, err = app.Appender(context.Background())
    69  			return err
    70  		}))
    71  
    72  		_, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 1, 2.41241)
    73  		testutil.Ok(t, err)
    74  		_, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 2, 3.41241)
    75  		testutil.Ok(t, err)
    76  		ref, err := a.Append(0, labels.FromStrings("a", "1", "b", "2"), 3, 4.41241)
    77  		testutil.Ok(t, err)
    78  
    79  		// Test exemplars.
    80  		_, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 1, Ts: 1, HasTs: true})
    81  		testutil.Ok(t, err)
    82  		_, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 2.1212, Ts: 2, HasTs: true})
    83  		testutil.Ok(t, err)
    84  		_, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 3.1313, Ts: 3, HasTs: true})
    85  		testutil.Ok(t, err)
    86  		testutil.Ok(t, a.Commit())
    87  
    88  		// Check if not leaking.
    89  		_, err = m.TenantAppendable("foo")
    90  		testutil.Ok(t, err)
    91  		_, err = m.TenantAppendable("foo")
    92  		testutil.Ok(t, err)
    93  		_, err = m.TenantAppendable("foo")
    94  		testutil.Ok(t, err)
    95  
    96  		ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
    97  		defer cancel()
    98  
    99  		app, err = m.TenantAppendable("bar")
   100  		testutil.Ok(t, err)
   101  
   102  		testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error {
   103  			a, err = app.Appender(context.Background())
   104  			return err
   105  		}))
   106  
   107  		_, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 1, 20.41241)
   108  		testutil.Ok(t, err)
   109  		_, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 2, 30.41241)
   110  		testutil.Ok(t, err)
   111  		ref, err = a.Append(0, labels.FromStrings("a", "1", "b", "2"), 3, 40.41241)
   112  		testutil.Ok(t, err)
   113  
   114  		_, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 11, Ts: 1, HasTs: true, Labels: labels.FromStrings("traceID", "abc")})
   115  		testutil.Ok(t, err)
   116  		_, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 22.1212, Ts: 2, HasTs: true, Labels: labels.FromStrings("traceID", "def")})
   117  		testutil.Ok(t, err)
   118  		_, err = a.AppendExemplar(ref, labels.FromStrings("a", "1", "b", "2"), exemplar.Exemplar{Value: 33.1313, Ts: 3, HasTs: true, Labels: labels.FromStrings("traceID", "ghi")})
   119  		testutil.Ok(t, err)
   120  		testutil.Ok(t, a.Commit())
   121  
   122  		testMulitTSDBSeries(t, m)
   123  		testMultiTSDBExemplars(t, m)
   124  	})
   125  	t.Run("run on existing storage", func(t *testing.T) {
   126  		m := NewMultiTSDB(
   127  			dir, logger, prometheus.NewRegistry(), &tsdb.Options{
   128  				MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   129  				MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   130  				RetentionDuration: (6 * time.Hour).Milliseconds(),
   131  				NoLockfile:        true,
   132  			},
   133  			labels.FromStrings("replica", "01"),
   134  			"tenant_id",
   135  			nil,
   136  			false,
   137  			metadata.NoneFunc,
   138  		)
   139  		defer func() { testutil.Ok(t, m.Close()) }()
   140  
   141  		testutil.Ok(t, m.Flush())
   142  		testutil.Ok(t, m.Open())
   143  
   144  		// Get appender just for test.
   145  		app, err := m.TenantAppendable("foo")
   146  		testutil.Ok(t, err)
   147  
   148  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   149  		defer cancel()
   150  
   151  		testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error {
   152  			_, err := app.Appender(context.Background())
   153  			return err
   154  		}))
   155  
   156  		// Check if not leaking.
   157  		_, err = m.TenantAppendable("foo")
   158  		testutil.Ok(t, err)
   159  		_, err = m.TenantAppendable("foo")
   160  		testutil.Ok(t, err)
   161  		_, err = m.TenantAppendable("foo")
   162  		testutil.Ok(t, err)
   163  
   164  		testMulitTSDBSeries(t, m)
   165  	})
   166  
   167  	t.Run("flush with one sample produces a block", func(t *testing.T) {
   168  		const testTenant = "test_tenant"
   169  		m := NewMultiTSDB(
   170  			dir, logger, prometheus.NewRegistry(), &tsdb.Options{
   171  				MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   172  				MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   173  				RetentionDuration: (6 * time.Hour).Milliseconds(),
   174  				NoLockfile:        true,
   175  			},
   176  			labels.FromStrings("replica", "01"),
   177  			"tenant_id",
   178  			nil,
   179  			false,
   180  			metadata.NoneFunc,
   181  		)
   182  		defer func() { testutil.Ok(t, m.Close()) }()
   183  
   184  		testutil.Ok(t, m.Flush())
   185  		testutil.Ok(t, m.Open())
   186  		testutil.Ok(t, appendSample(m, testTenant, time.Now()))
   187  
   188  		tenant := m.tenants[testTenant]
   189  		db := tenant.readyStorage().Get()
   190  
   191  		testutil.Equals(t, 0, len(db.Blocks()))
   192  		testutil.Ok(t, m.Flush())
   193  		testutil.Equals(t, 1, len(db.Blocks()))
   194  	})
   195  }
   196  
   197  var (
   198  	expectedFooResp = &storepb.Series{
   199  		Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "foo"}},
   200  		Chunks: []storepb.AggrChunk{{MinTime: 1, MaxTime: 3, Raw: &storepb.Chunk{Data: []byte("\000\003\002@\003L\235\2354X\315\001\330\r\257Mui\251\327:U"), Hash: 9768694233508509040}}},
   201  	}
   202  	expectedBarResp = &storepb.Series{
   203  		Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "bar"}},
   204  		Chunks: []storepb.AggrChunk{{MinTime: 1, MaxTime: 3, Raw: &storepb.Chunk{Data: []byte("\000\003\002@4i\223\263\246\213\032\001\330\035i\337\322\352\323S\256t\270"), Hash: 2304287992246504442}}},
   205  	}
   206  )
   207  
   208  func testMulitTSDBSeries(t *testing.T, m *MultiTSDB) {
   209  	g := &errgroup.Group{}
   210  	respFoo := make(chan *storepb.Series)
   211  	respBar := make(chan *storepb.Series)
   212  	for i := 0; i < 100; i++ {
   213  		ss := m.TSDBLocalClients()
   214  		testutil.Assert(t, len(ss) == 2)
   215  
   216  		for _, s := range ss {
   217  			s := s
   218  
   219  			switch isFoo := strings.Contains(s.String(), "foo"); isFoo {
   220  			case true:
   221  				g.Go(func() error {
   222  					return getResponses(s, respFoo)
   223  				})
   224  			case false:
   225  				g.Go(func() error {
   226  					return getResponses(s, respBar)
   227  				})
   228  			}
   229  		}
   230  	}
   231  	var err error
   232  	go func() {
   233  		err = g.Wait()
   234  		close(respFoo)
   235  		close(respBar)
   236  	}()
   237  Outer:
   238  	for {
   239  		select {
   240  		case r, ok := <-respFoo:
   241  			if !ok {
   242  				break Outer
   243  			}
   244  			testutil.Equals(t, expectedFooResp, r)
   245  		case r, ok := <-respBar:
   246  			if !ok {
   247  				break Outer
   248  			}
   249  			testutil.Equals(t, expectedBarResp, r)
   250  		}
   251  	}
   252  	testutil.Ok(t, err)
   253  }
   254  
   255  func getResponses(storeClient store.Client, respCh chan<- *storepb.Series) error {
   256  	sc, err := storeClient.Series(context.Background(), &storepb.SeriesRequest{
   257  		MinTime:  0,
   258  		MaxTime:  10,
   259  		Matchers: []storepb.LabelMatcher{{Name: "a", Value: ".*", Type: storepb.LabelMatcher_RE}},
   260  	})
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	for {
   266  		resp, err := sc.Recv()
   267  		if err == io.EOF {
   268  			break
   269  		}
   270  
   271  		if err != nil {
   272  			return err
   273  		}
   274  
   275  		respCh <- resp.GetSeries()
   276  	}
   277  
   278  	return nil
   279  }
   280  
   281  var (
   282  	expectedFooRespExemplars = []exemplarspb.ExemplarData{
   283  		{
   284  			SeriesLabels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "foo"}}},
   285  			Exemplars: []*exemplarspb.Exemplar{
   286  				{Value: 1, Ts: 1},
   287  				{Value: 2.1212, Ts: 2},
   288  				{Value: 3.1313, Ts: 3},
   289  			},
   290  		},
   291  	}
   292  	expectedBarRespExemplars = []exemplarspb.ExemplarData{
   293  		{
   294  			SeriesLabels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "replica", Value: "01"}, {Name: "tenant_id", Value: "bar"}}},
   295  			Exemplars: []*exemplarspb.Exemplar{
   296  				{Value: 11, Ts: 1, Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "traceID", Value: "abc"}}}},
   297  				{Value: 22.1212, Ts: 2, Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "traceID", Value: "def"}}}},
   298  				{Value: 33.1313, Ts: 3, Labels: labelpb.ZLabelSet{Labels: []labelpb.ZLabel{{Name: "traceID", Value: "ghi"}}}},
   299  			},
   300  		},
   301  	}
   302  )
   303  
   304  func testMultiTSDBExemplars(t *testing.T, m *MultiTSDB) {
   305  	g := &errgroup.Group{}
   306  	respFoo := make(chan []exemplarspb.ExemplarData)
   307  	respBar := make(chan []exemplarspb.ExemplarData)
   308  	for i := 0; i < 100; i++ {
   309  		s := m.TSDBExemplars()
   310  		testutil.Assert(t, len(s) == 2)
   311  
   312  		g.Go(func() error {
   313  			srv := newExemplarsServer(context.Background())
   314  			if err := s["foo"].Exemplars(
   315  				[][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}},
   316  				0,
   317  				10,
   318  				srv,
   319  			); err != nil {
   320  				return err
   321  			}
   322  			respFoo <- srv.Data
   323  			return nil
   324  		})
   325  		g.Go(func() error {
   326  			srv := newExemplarsServer(context.Background())
   327  			if err := s["bar"].Exemplars(
   328  				[][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}},
   329  				0,
   330  				10,
   331  				srv,
   332  			); err != nil {
   333  				return err
   334  			}
   335  			respBar <- srv.Data
   336  			return nil
   337  		})
   338  	}
   339  	var err error
   340  	go func() {
   341  		err = g.Wait()
   342  		close(respFoo)
   343  		close(respBar)
   344  	}()
   345  OuterE:
   346  	for {
   347  		select {
   348  		case r, ok := <-respFoo:
   349  			if !ok {
   350  				break OuterE
   351  			}
   352  			checkExemplarsResponse(t, expectedFooRespExemplars, r)
   353  		case r, ok := <-respBar:
   354  			if !ok {
   355  				break OuterE
   356  			}
   357  			checkExemplarsResponse(t, expectedBarRespExemplars, r)
   358  		}
   359  	}
   360  	testutil.Ok(t, err)
   361  }
   362  
   363  // exemplarsServer is test gRPC exemplarsAPI exemplars server.
   364  type exemplarsServer struct {
   365  	// This field just exist to pseudo-implement the unused methods of the interface.
   366  	exemplarspb.Exemplars_ExemplarsServer
   367  
   368  	ctx context.Context
   369  
   370  	Data     []exemplarspb.ExemplarData
   371  	Warnings []string
   372  
   373  	Size int64
   374  }
   375  
   376  func newExemplarsServer(ctx context.Context) *exemplarsServer {
   377  	return &exemplarsServer{ctx: ctx}
   378  }
   379  
   380  func (e *exemplarsServer) Send(r *exemplarspb.ExemplarsResponse) error {
   381  	e.Size += int64(r.Size())
   382  
   383  	if r.GetWarning() != "" {
   384  		e.Warnings = append(e.Warnings, r.GetWarning())
   385  		return nil
   386  	}
   387  
   388  	if r.GetData() != nil {
   389  		e.Data = append(e.Data, *r.GetData())
   390  		return nil
   391  	}
   392  
   393  	// Unsupported field, skip.
   394  	return nil
   395  }
   396  
   397  func (s *exemplarsServer) Context() context.Context {
   398  	return s.ctx
   399  }
   400  
   401  func checkExemplarsResponse(t *testing.T, expected, data []exemplarspb.ExemplarData) {
   402  	testutil.Equals(t, len(expected), len(data))
   403  	for i := range data {
   404  		testutil.Equals(t, expected[i].SeriesLabels, data[i].SeriesLabels)
   405  		testutil.Equals(t, len(expected[i].Exemplars), len(data[i].Exemplars))
   406  		for j := range data[i].Exemplars {
   407  			testutil.Equals(t, *expected[i].Exemplars[j], *data[i].Exemplars[j])
   408  		}
   409  	}
   410  }
   411  
   412  func TestMultiTSDBPrune(t *testing.T) {
   413  	tests := []struct {
   414  		name            string
   415  		bucket          objstore.Bucket
   416  		expectedTenants int
   417  		expectedUploads int
   418  	}{
   419  		{
   420  			name:            "prune tsdbs without object storage",
   421  			bucket:          nil,
   422  			expectedTenants: 2,
   423  			expectedUploads: 0,
   424  		},
   425  		{
   426  			name:            "prune tsdbs with object storage",
   427  			bucket:          objstore.NewInMemBucket(),
   428  			expectedTenants: 2,
   429  			expectedUploads: 2,
   430  		},
   431  	}
   432  
   433  	for _, test := range tests {
   434  		t.Run(test.name, func(t *testing.T) {
   435  			dir := t.TempDir()
   436  
   437  			m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(),
   438  				&tsdb.Options{
   439  					MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   440  					MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   441  					RetentionDuration: (6 * time.Hour).Milliseconds(),
   442  				},
   443  				labels.FromStrings("replica", "test"),
   444  				"tenant_id",
   445  				test.bucket,
   446  				false,
   447  				metadata.NoneFunc,
   448  			)
   449  			defer func() { testutil.Ok(t, m.Close()) }()
   450  
   451  			for i := 0; i < 100; i++ {
   452  				testutil.Ok(t, appendSample(m, "deleted-tenant", time.UnixMilli(int64(10+i))))
   453  				testutil.Ok(t, appendSample(m, "compacted-tenant", time.Now().Add(-4*time.Hour)))
   454  				testutil.Ok(t, appendSample(m, "active-tenant", time.Now().Add(time.Duration(i)*time.Second)))
   455  			}
   456  			testutil.Equals(t, 3, len(m.TSDBLocalClients()))
   457  
   458  			ctx, cancel := context.WithCancel(context.Background())
   459  			defer cancel()
   460  
   461  			if test.bucket != nil {
   462  				go func() {
   463  					testutil.Ok(t, syncTSDBs(ctx, m, 10*time.Millisecond))
   464  				}()
   465  			}
   466  
   467  			testutil.Ok(t, m.Prune(ctx))
   468  			testutil.Equals(t, test.expectedTenants, len(m.TSDBLocalClients()))
   469  			var shippedBlocks int
   470  			if test.bucket != nil {
   471  				testutil.Ok(t, test.bucket.Iter(context.Background(), "", func(s string) error {
   472  					shippedBlocks++
   473  					return nil
   474  				}))
   475  			}
   476  			testutil.Equals(t, test.expectedUploads, shippedBlocks)
   477  		})
   478  	}
   479  }
   480  
   481  func syncTSDBs(ctx context.Context, m *MultiTSDB, interval time.Duration) error {
   482  	for {
   483  		select {
   484  		case <-ctx.Done():
   485  			return nil
   486  		case <-time.After(interval):
   487  			_, err := m.Sync(ctx)
   488  			if err != nil {
   489  				return err
   490  			}
   491  		}
   492  	}
   493  }
   494  
   495  func TestMultiTSDBRecreatePrunedTenant(t *testing.T) {
   496  	dir := t.TempDir()
   497  
   498  	m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(),
   499  		&tsdb.Options{
   500  			MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   501  			MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   502  			RetentionDuration: (6 * time.Hour).Milliseconds(),
   503  		},
   504  		labels.FromStrings("replica", "test"),
   505  		"tenant_id",
   506  		objstore.NewInMemBucket(),
   507  		false,
   508  		metadata.NoneFunc,
   509  	)
   510  	defer func() { testutil.Ok(t, m.Close()) }()
   511  
   512  	testutil.Ok(t, appendSample(m, "foo", time.UnixMilli(int64(10))))
   513  	testutil.Ok(t, m.Prune(context.Background()))
   514  	testutil.Equals(t, 0, len(m.TSDBLocalClients()))
   515  
   516  	testutil.Ok(t, appendSample(m, "foo", time.UnixMilli(int64(10))))
   517  	testutil.Equals(t, 1, len(m.TSDBLocalClients()))
   518  }
   519  
   520  func TestMultiTSDBStats(t *testing.T) {
   521  	tests := []struct {
   522  		name          string
   523  		tenants       []string
   524  		expectedStats int
   525  	}{
   526  		{
   527  			name:          "single tenant",
   528  			tenants:       []string{"foo"},
   529  			expectedStats: 1,
   530  		},
   531  		{
   532  			name:          "missing tenant",
   533  			tenants:       []string{"missing-foo"},
   534  			expectedStats: 0,
   535  		},
   536  		{
   537  			name:          "multiple tenants with missing tenant",
   538  			tenants:       []string{"foo", "missing-foo"},
   539  			expectedStats: 1,
   540  		},
   541  		{
   542  			name:          "all tenants",
   543  			tenants:       []string{"foo", "bar", "baz"},
   544  			expectedStats: 3,
   545  		},
   546  	}
   547  
   548  	for _, test := range tests {
   549  		t.Run(test.name, func(t *testing.T) {
   550  			dir := t.TempDir()
   551  
   552  			m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(),
   553  				&tsdb.Options{
   554  					MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   555  					MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   556  					RetentionDuration: (6 * time.Hour).Milliseconds(),
   557  				},
   558  				labels.FromStrings("replica", "test"),
   559  				"tenant_id",
   560  				nil,
   561  				false,
   562  				metadata.NoneFunc,
   563  			)
   564  			defer func() { testutil.Ok(t, m.Close()) }()
   565  
   566  			testutil.Ok(t, appendSample(m, "foo", time.Now()))
   567  			testutil.Ok(t, appendSample(m, "bar", time.Now()))
   568  			testutil.Ok(t, appendSample(m, "baz", time.Now()))
   569  			testutil.Equals(t, 3, len(m.TSDBLocalClients()))
   570  
   571  			stats := m.TenantStats(10, labels.MetricName, test.tenants...)
   572  			testutil.Equals(t, test.expectedStats, len(stats))
   573  		})
   574  	}
   575  }
   576  
   577  // Regression test for https://github.com/thanos-io/thanos/issues/6047.
   578  func TestMultiTSDBWithNilStore(t *testing.T) {
   579  	dir := t.TempDir()
   580  
   581  	m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(),
   582  		&tsdb.Options{
   583  			MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   584  			MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   585  			RetentionDuration: (6 * time.Hour).Milliseconds(),
   586  		},
   587  		labels.FromStrings("replica", "test"),
   588  		"tenant_id",
   589  		nil,
   590  		false,
   591  		metadata.NoneFunc,
   592  	)
   593  	defer func() { testutil.Ok(t, m.Close()) }()
   594  
   595  	const tenantID = "test-tenant"
   596  	_, err := m.TenantAppendable(tenantID)
   597  	testutil.Ok(t, err)
   598  
   599  	// Get LabelSets of newly created TSDB.
   600  	clients := m.TSDBLocalClients()
   601  	for _, client := range clients {
   602  		testutil.Ok(t, testutil.FaultOrPanicToErr(func() { client.LabelSets() }))
   603  	}
   604  
   605  	// Wait for tenant to become ready before terminating the test.
   606  	// This allows the tear down procedure to cleanup properly.
   607  	testutil.Ok(t, appendSample(m, tenantID, time.Now()))
   608  }
   609  
   610  type slowClient struct {
   611  	store.Client
   612  }
   613  
   614  func (s *slowClient) LabelValues(ctx context.Context, r *storepb.LabelValuesRequest, _ ...grpc.CallOption) (*storepb.LabelValuesResponse, error) {
   615  	<-time.After(10 * time.Millisecond)
   616  	return s.Client.LabelValues(ctx, r)
   617  }
   618  
   619  func TestProxyLabelValues(t *testing.T) {
   620  	dir := t.TempDir()
   621  	m := NewMultiTSDB(
   622  		dir, nil, prometheus.NewRegistry(), &tsdb.Options{
   623  			RetentionDuration: 10 * time.Minute.Milliseconds(),
   624  			MinBlockDuration:  5 * time.Minute.Milliseconds(),
   625  			MaxBlockDuration:  5 * time.Minute.Milliseconds(),
   626  			NoLockfile:        true,
   627  		},
   628  		labels.FromStrings("replica", "01"),
   629  		"tenant_id",
   630  		nil,
   631  		false,
   632  		metadata.NoneFunc,
   633  	)
   634  	defer func() { testutil.Ok(t, m.Close()) }()
   635  
   636  	ctx, cancel := context.WithCancel(context.Background())
   637  	defer cancel()
   638  	go func() {
   639  		for {
   640  			select {
   641  			case <-ctx.Done():
   642  				return
   643  			default:
   644  				testutil.Ok(t, queryLabelValues(ctx, m))
   645  			}
   646  		}
   647  	}()
   648  
   649  	// Append several samples to a TSDB outside the retention period.
   650  	testutil.Ok(t, appendSampleWithLabels(m, "tenant-a", labels.FromStrings(labels.MetricName, "metric-a"), time.Now().Add(-5*time.Hour)))
   651  	testutil.Ok(t, appendSampleWithLabels(m, "tenant-a", labels.FromStrings(labels.MetricName, "metric-b"), time.Now().Add(-3*time.Hour)))
   652  	testutil.Ok(t, appendSampleWithLabels(m, "tenant-b", labels.FromStrings(labels.MetricName, "metric-c"), time.Now().Add(-1*time.Hour)))
   653  
   654  	// Append a sample within the retention period and flush all tenants.
   655  	// This will lead deletion of blocks that fall out of the retention period.
   656  	testutil.Ok(t, appendSampleWithLabels(m, "tenant-b", labels.FromStrings(labels.MetricName, "metric-d"), time.Now()))
   657  	testutil.Ok(t, m.Flush())
   658  }
   659  
   660  func appendSample(m *MultiTSDB, tenant string, timestamp time.Time) error {
   661  	return appendSampleWithLabels(m, tenant, labels.FromStrings("foo", "bar"), timestamp)
   662  }
   663  
   664  func appendSampleWithLabels(m *MultiTSDB, tenant string, lbls labels.Labels, timestamp time.Time) error {
   665  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   666  	defer cancel()
   667  
   668  	app, err := m.TenantAppendable(tenant)
   669  	if err != nil {
   670  		return err
   671  	}
   672  
   673  	var a storage.Appender
   674  	if err := runutil.Retry(1*time.Second, ctx.Done(), func() error {
   675  		a, err = app.Appender(ctx)
   676  		return err
   677  	}); err != nil {
   678  		return err
   679  	}
   680  
   681  	_, err = a.Append(0, lbls, timestamp.UnixMilli(), 10)
   682  	if err != nil {
   683  		return err
   684  	}
   685  
   686  	return a.Commit()
   687  }
   688  
   689  func queryLabelValues(ctx context.Context, m *MultiTSDB) error {
   690  	proxy := store.NewProxyStore(nil, nil, func() []store.Client {
   691  		clients := m.TSDBLocalClients()
   692  		if len(clients) > 0 {
   693  			clients[0] = &slowClient{clients[0]}
   694  		}
   695  		return clients
   696  	}, component.Store, nil, 1*time.Minute, store.LazyRetrieval)
   697  
   698  	req := &storepb.LabelValuesRequest{
   699  		Label: labels.MetricName,
   700  		Start: math.MinInt64,
   701  		End:   math.MaxInt64,
   702  	}
   703  	_, err := proxy.LabelValues(ctx, req)
   704  	if err != nil {
   705  		return err
   706  	}
   707  	return nil
   708  }
   709  
   710  func BenchmarkMultiTSDB(b *testing.B) {
   711  	dir := b.TempDir()
   712  
   713  	m := NewMultiTSDB(dir, log.NewNopLogger(), prometheus.NewRegistry(), &tsdb.Options{
   714  		MinBlockDuration:  (2 * time.Hour).Milliseconds(),
   715  		MaxBlockDuration:  (2 * time.Hour).Milliseconds(),
   716  		RetentionDuration: (6 * time.Hour).Milliseconds(),
   717  		NoLockfile:        true,
   718  	}, labels.FromStrings("replica", "test"),
   719  		"tenant_id",
   720  		nil,
   721  		false,
   722  		metadata.NoneFunc,
   723  	)
   724  	defer func() { testutil.Ok(b, m.Close()) }()
   725  
   726  	testutil.Ok(b, m.Flush())
   727  	testutil.Ok(b, m.Open())
   728  
   729  	app, err := m.TenantAppendable("foo")
   730  	testutil.Ok(b, err)
   731  
   732  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   733  	defer cancel()
   734  
   735  	var a storage.Appender
   736  	testutil.Ok(b, runutil.Retry(1*time.Second, ctx.Done(), func() error {
   737  		a, err = app.Appender(context.Background())
   738  		return err
   739  	}))
   740  
   741  	l := labels.FromStrings("a", "1", "b", "2")
   742  
   743  	b.ReportAllocs()
   744  	b.ResetTimer()
   745  
   746  	for i := 0; i < b.N; i++ {
   747  		_, _ = a.Append(0, l, int64(i), float64(i))
   748  	}
   749  }