github.com/thanos-io/thanos@v0.32.5/pkg/store/bucket_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  	"bytes"
     8  	"context"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"math"
    13  	"math/rand"
    14  	"os"
    15  	"path"
    16  	"path/filepath"
    17  	"regexp"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/cespare/xxhash"
    26  	"github.com/go-kit/log"
    27  	"github.com/gogo/protobuf/proto"
    28  	"github.com/gogo/protobuf/types"
    29  	"github.com/leanovate/gopter"
    30  	"github.com/leanovate/gopter/gen"
    31  	"github.com/leanovate/gopter/prop"
    32  	"github.com/oklog/ulid"
    33  	"github.com/prometheus/client_golang/prometheus"
    34  	promtest "github.com/prometheus/client_golang/prometheus/testutil"
    35  	"github.com/prometheus/prometheus/model/labels"
    36  	"github.com/prometheus/prometheus/model/relabel"
    37  	"github.com/prometheus/prometheus/model/timestamp"
    38  	"github.com/prometheus/prometheus/storage"
    39  	"github.com/prometheus/prometheus/tsdb"
    40  	"github.com/prometheus/prometheus/tsdb/chunkenc"
    41  	"github.com/prometheus/prometheus/tsdb/encoding"
    42  	"github.com/prometheus/prometheus/tsdb/index"
    43  	"go.uber.org/atomic"
    44  	"golang.org/x/exp/slices"
    45  
    46  	"github.com/thanos-io/objstore"
    47  	"github.com/thanos-io/objstore/providers/filesystem"
    48  
    49  	"github.com/efficientgo/core/testutil"
    50  
    51  	"github.com/thanos-io/thanos/pkg/block"
    52  	"github.com/thanos-io/thanos/pkg/block/indexheader"
    53  	"github.com/thanos-io/thanos/pkg/block/metadata"
    54  	"github.com/thanos-io/thanos/pkg/compact"
    55  	"github.com/thanos-io/thanos/pkg/compact/downsample"
    56  	"github.com/thanos-io/thanos/pkg/gate"
    57  	"github.com/thanos-io/thanos/pkg/pool"
    58  	storecache "github.com/thanos-io/thanos/pkg/store/cache"
    59  	"github.com/thanos-io/thanos/pkg/store/hintspb"
    60  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    61  	"github.com/thanos-io/thanos/pkg/store/storepb"
    62  	storetestutil "github.com/thanos-io/thanos/pkg/store/storepb/testutil"
    63  	"github.com/thanos-io/thanos/pkg/testutil/custom"
    64  	"github.com/thanos-io/thanos/pkg/testutil/e2eutil"
    65  )
    66  
    67  var emptyRelabelConfig = make([]*relabel.Config, 0)
    68  
    69  func TestBucketBlock_Property(t *testing.T) {
    70  	parameters := gopter.DefaultTestParameters()
    71  	parameters.Rng.Seed(2000)
    72  	parameters.MinSuccessfulTests = 20000
    73  	properties := gopter.NewProperties(parameters)
    74  
    75  	set := newBucketBlockSet(labels.Labels{})
    76  
    77  	type resBlock struct {
    78  		mint, maxt int64
    79  		window     int64
    80  	}
    81  	// This input resembles a typical production-level block layout
    82  	// in remote object storage.
    83  	input := []resBlock{
    84  		{window: downsample.ResLevel0, mint: 0, maxt: 100},
    85  		{window: downsample.ResLevel0, mint: 100, maxt: 200},
    86  		// Compaction level 2 begins but not downsampling (8 hour block length).
    87  		{window: downsample.ResLevel0, mint: 200, maxt: 600},
    88  		{window: downsample.ResLevel0, mint: 600, maxt: 1000},
    89  		// Compaction level 3 begins, Some of it is downsampled but still retained (48 hour block length).
    90  		{window: downsample.ResLevel0, mint: 1000, maxt: 1750},
    91  		{window: downsample.ResLevel1, mint: 1000, maxt: 1750},
    92  		// Compaction level 4 begins, different downsampling levels cover the same (336 hour block length).
    93  		{window: downsample.ResLevel0, mint: 1750, maxt: 7000},
    94  		{window: downsample.ResLevel1, mint: 1750, maxt: 7000},
    95  		{window: downsample.ResLevel2, mint: 1750, maxt: 7000},
    96  		// Compaction level 4 already happened, raw samples have been deleted.
    97  		{window: downsample.ResLevel0, mint: 7000, maxt: 14000},
    98  		{window: downsample.ResLevel1, mint: 7000, maxt: 14000},
    99  		// Compaction level 4 already happened, raw and downsample res level 1 samples have been deleted.
   100  		{window: downsample.ResLevel2, mint: 14000, maxt: 21000},
   101  	}
   102  
   103  	for _, in := range input {
   104  		var m metadata.Meta
   105  		m.Thanos.Downsample.Resolution = in.window
   106  		m.MinTime = in.mint
   107  		m.MaxTime = in.maxt
   108  
   109  		testutil.Ok(t, set.add(&bucketBlock{meta: &m}))
   110  	}
   111  
   112  	properties.Property("getFor always gets at least some data in range", prop.ForAllNoShrink(
   113  		func(low, high, maxResolution int64) bool {
   114  			// Bogus case.
   115  			if low >= high {
   116  				return true
   117  			}
   118  
   119  			res := set.getFor(low, high, maxResolution, nil)
   120  
   121  			// The data that we get must all encompass our requested range.
   122  			if len(res) == 1 && (res[0].meta.Thanos.Downsample.Resolution > maxResolution ||
   123  				res[0].meta.MinTime > low) {
   124  				return false
   125  			} else if len(res) > 1 {
   126  				mint := int64(21001)
   127  				for i := 0; i < len(res)-1; i++ {
   128  					if res[i].meta.Thanos.Downsample.Resolution > maxResolution {
   129  						return false
   130  					}
   131  					if res[i+1].meta.MinTime != res[i].meta.MaxTime {
   132  						return false
   133  					}
   134  					if res[i].meta.MinTime < mint {
   135  						mint = res[i].meta.MinTime
   136  					}
   137  				}
   138  				if res[len(res)-1].meta.MinTime < mint {
   139  					mint = res[len(res)-1].meta.MinTime
   140  				}
   141  				if low < mint {
   142  					return false
   143  				}
   144  
   145  			}
   146  			return true
   147  		}, gen.Int64Range(0, 21000), gen.Int64Range(0, 21000), gen.Int64Range(0, 60*60*1000)),
   148  	)
   149  
   150  	properties.Property("getFor always gets all data in range", prop.ForAllNoShrink(
   151  		func(low, high int64) bool {
   152  			// Bogus case.
   153  			if low >= high {
   154  				return true
   155  			}
   156  
   157  			maxResolution := downsample.ResLevel2
   158  			res := set.getFor(low, high, maxResolution, nil)
   159  
   160  			// The data that we get must all encompass our requested range.
   161  			if len(res) == 1 && (res[0].meta.Thanos.Downsample.Resolution > maxResolution ||
   162  				res[0].meta.MinTime > low || res[0].meta.MaxTime < high) {
   163  				return false
   164  			} else if len(res) > 1 {
   165  				mint := int64(21001)
   166  				maxt := int64(0)
   167  				for i := 0; i < len(res)-1; i++ {
   168  					if res[i+1].meta.MinTime != res[i].meta.MaxTime {
   169  						return false
   170  					}
   171  					if res[i].meta.MinTime < mint {
   172  						mint = res[i].meta.MinTime
   173  					}
   174  					if res[i].meta.MaxTime > maxt {
   175  						maxt = res[i].meta.MaxTime
   176  					}
   177  				}
   178  				if res[len(res)-1].meta.MinTime < mint {
   179  					mint = res[len(res)-1].meta.MinTime
   180  				}
   181  				if res[len(res)-1].meta.MaxTime > maxt {
   182  					maxt = res[len(res)-1].meta.MaxTime
   183  				}
   184  				if low < mint {
   185  					return false
   186  				}
   187  				if high > maxt {
   188  					return false
   189  				}
   190  
   191  			}
   192  			return true
   193  		}, gen.Int64Range(0, 21000), gen.Int64Range(0, 21000)),
   194  	)
   195  
   196  	properties.TestingRun(t)
   197  }
   198  
   199  func TestBucketFilterExtLabelsMatchers(t *testing.T) {
   200  	defer custom.TolerantVerifyLeak(t)
   201  
   202  	dir := t.TempDir()
   203  	bkt, err := filesystem.NewBucket(dir)
   204  	testutil.Ok(t, err)
   205  	defer func() { testutil.Ok(t, bkt.Close()) }()
   206  
   207  	blockID := ulid.MustNew(1, nil)
   208  	meta := &metadata.Meta{
   209  		BlockMeta: tsdb.BlockMeta{ULID: blockID},
   210  		Thanos: metadata.Thanos{
   211  			Labels: map[string]string{
   212  				"a": "b",
   213  				"c": "d",
   214  			},
   215  		},
   216  	}
   217  	b, _ := newBucketBlock(context.Background(), log.NewNopLogger(), newBucketStoreMetrics(nil), meta, bkt, path.Join(dir, blockID.String()), nil, nil, nil, nil, nil, nil)
   218  	ms := []*labels.Matcher{
   219  		{Type: labels.MatchNotEqual, Name: "a", Value: "b"},
   220  	}
   221  	res, _ := b.FilterExtLabelsMatchers(ms)
   222  	testutil.Equals(t, len(res), 0)
   223  
   224  	ms = []*labels.Matcher{
   225  		{Type: labels.MatchNotEqual, Name: "a", Value: "a"},
   226  	}
   227  	_, ok := b.FilterExtLabelsMatchers(ms)
   228  	testutil.Equals(t, ok, false)
   229  
   230  	ms = []*labels.Matcher{
   231  		{Type: labels.MatchNotEqual, Name: "a", Value: "a"},
   232  		{Type: labels.MatchNotEqual, Name: "c", Value: "d"},
   233  	}
   234  	res, _ = b.FilterExtLabelsMatchers(ms)
   235  	testutil.Equals(t, len(res), 0)
   236  
   237  	ms = []*labels.Matcher{
   238  		{Type: labels.MatchNotEqual, Name: "a2", Value: "a"},
   239  	}
   240  	res, _ = b.FilterExtLabelsMatchers(ms)
   241  	testutil.Equals(t, len(res), 1)
   242  	testutil.Equals(t, res, ms)
   243  }
   244  
   245  func TestBucketBlock_matchLabels(t *testing.T) {
   246  	defer custom.TolerantVerifyLeak(t)
   247  
   248  	dir := t.TempDir()
   249  
   250  	bkt, err := filesystem.NewBucket(dir)
   251  	testutil.Ok(t, err)
   252  	defer func() { testutil.Ok(t, bkt.Close()) }()
   253  
   254  	blockID := ulid.MustNew(1, nil)
   255  	meta := &metadata.Meta{
   256  		BlockMeta: tsdb.BlockMeta{ULID: blockID},
   257  		Thanos: metadata.Thanos{
   258  			Labels: map[string]string{
   259  				"a": "b",
   260  				"c": "d",
   261  			},
   262  		},
   263  	}
   264  
   265  	b, err := newBucketBlock(context.Background(), log.NewNopLogger(), newBucketStoreMetrics(nil), meta, bkt, path.Join(dir, blockID.String()), nil, nil, nil, nil, nil, nil)
   266  	testutil.Ok(t, err)
   267  
   268  	cases := []struct {
   269  		in    []*labels.Matcher
   270  		match bool
   271  	}{
   272  		{
   273  			in:    []*labels.Matcher{},
   274  			match: true,
   275  		},
   276  		{
   277  			in: []*labels.Matcher{
   278  				{Type: labels.MatchEqual, Name: "a", Value: "b"},
   279  				{Type: labels.MatchEqual, Name: "c", Value: "d"},
   280  			},
   281  			match: true,
   282  		},
   283  		{
   284  			in: []*labels.Matcher{
   285  				{Type: labels.MatchEqual, Name: "a", Value: "b"},
   286  				{Type: labels.MatchEqual, Name: "c", Value: "b"},
   287  			},
   288  			match: false,
   289  		},
   290  		{
   291  			in: []*labels.Matcher{
   292  				{Type: labels.MatchEqual, Name: "a", Value: "b"},
   293  				{Type: labels.MatchEqual, Name: "e", Value: "f"},
   294  			},
   295  			match: false,
   296  		},
   297  		{
   298  			in: []*labels.Matcher{
   299  				{Type: labels.MatchEqual, Name: block.BlockIDLabel, Value: blockID.String()},
   300  			},
   301  			match: true,
   302  		},
   303  		{
   304  			in: []*labels.Matcher{
   305  				{Type: labels.MatchEqual, Name: block.BlockIDLabel, Value: "xxx"},
   306  			},
   307  			match: false,
   308  		},
   309  		{
   310  			in: []*labels.Matcher{
   311  				{Type: labels.MatchEqual, Name: block.BlockIDLabel, Value: blockID.String()},
   312  				{Type: labels.MatchEqual, Name: "c", Value: "b"},
   313  			},
   314  			match: false,
   315  		},
   316  		{
   317  			in: []*labels.Matcher{
   318  				{Type: labels.MatchNotEqual, Name: "", Value: "x"},
   319  			},
   320  			match: true,
   321  		},
   322  		{
   323  			in: []*labels.Matcher{
   324  				{Type: labels.MatchNotEqual, Name: "", Value: "d"},
   325  			},
   326  			match: true,
   327  		},
   328  	}
   329  	for _, c := range cases {
   330  		ok := b.matchRelabelLabels(c.in)
   331  		testutil.Equals(t, c.match, ok)
   332  	}
   333  
   334  	// Ensure block's labels in the meta have not been manipulated.
   335  	testutil.Equals(t, map[string]string{
   336  		"a": "b",
   337  		"c": "d",
   338  	}, meta.Thanos.Labels)
   339  }
   340  
   341  func TestBucketBlockSet_addGet(t *testing.T) {
   342  	defer custom.TolerantVerifyLeak(t)
   343  
   344  	set := newBucketBlockSet(labels.Labels{})
   345  
   346  	type resBlock struct {
   347  		mint, maxt int64
   348  		window     int64
   349  	}
   350  	// Input is expected to be sorted. It is sorted in addBlock.
   351  	input := []resBlock{
   352  		// Blocks from 0 to 100 with raw resolution.
   353  		{window: downsample.ResLevel0, mint: 0, maxt: 100},
   354  		{window: downsample.ResLevel0, mint: 100, maxt: 200},
   355  		{window: downsample.ResLevel0, mint: 100, maxt: 200}, // Same overlap.
   356  		{window: downsample.ResLevel0, mint: 200, maxt: 299}, // Short overlap.
   357  		{window: downsample.ResLevel0, mint: 200, maxt: 300},
   358  		{window: downsample.ResLevel0, mint: 300, maxt: 400},
   359  		{window: downsample.ResLevel0, mint: 300, maxt: 600}, // Long overlap.
   360  		{window: downsample.ResLevel0, mint: 400, maxt: 500},
   361  		// Lower resolution data not covering last block.
   362  		{window: downsample.ResLevel1, mint: 0, maxt: 100},
   363  		{window: downsample.ResLevel1, mint: 100, maxt: 200},
   364  		{window: downsample.ResLevel1, mint: 200, maxt: 300},
   365  		{window: downsample.ResLevel1, mint: 300, maxt: 400},
   366  		// Lower resolution data only covering middle blocks.
   367  		{window: downsample.ResLevel2, mint: 100, maxt: 200},
   368  		{window: downsample.ResLevel2, mint: 200, maxt: 300},
   369  	}
   370  
   371  	for _, in := range input {
   372  		var m metadata.Meta
   373  		m.Thanos.Downsample.Resolution = in.window
   374  		m.MinTime = in.mint
   375  		m.MaxTime = in.maxt
   376  
   377  		testutil.Ok(t, set.add(&bucketBlock{meta: &m}))
   378  	}
   379  
   380  	for _, c := range []struct {
   381  		mint, maxt    int64
   382  		maxResolution int64
   383  		res           []resBlock
   384  	}{
   385  		{
   386  			mint:          -100,
   387  			maxt:          1000,
   388  			maxResolution: 0,
   389  			res: []resBlock{
   390  				{window: downsample.ResLevel0, mint: 0, maxt: 100},
   391  				{window: downsample.ResLevel0, mint: 100, maxt: 200},
   392  				{window: downsample.ResLevel0, mint: 100, maxt: 200},
   393  				{window: downsample.ResLevel0, mint: 200, maxt: 299},
   394  				{window: downsample.ResLevel0, mint: 200, maxt: 300},
   395  				{window: downsample.ResLevel0, mint: 300, maxt: 400},
   396  				{window: downsample.ResLevel0, mint: 300, maxt: 600},
   397  				{window: downsample.ResLevel0, mint: 400, maxt: 500},
   398  			},
   399  		}, {
   400  			mint:          100,
   401  			maxt:          400,
   402  			maxResolution: downsample.ResLevel1 - 1,
   403  			res: []resBlock{
   404  				{window: downsample.ResLevel0, mint: 100, maxt: 200},
   405  				{window: downsample.ResLevel0, mint: 100, maxt: 200},
   406  				{window: downsample.ResLevel0, mint: 200, maxt: 299},
   407  				{window: downsample.ResLevel0, mint: 200, maxt: 300},
   408  				{window: downsample.ResLevel0, mint: 300, maxt: 400},
   409  				{window: downsample.ResLevel0, mint: 300, maxt: 600},
   410  				// Block intervals are half-open: [b.MinTime, b.MaxTime), so 400-500 contains single sample.
   411  				{window: downsample.ResLevel0, mint: 400, maxt: 500},
   412  			},
   413  		}, {
   414  			mint:          100,
   415  			maxt:          500,
   416  			maxResolution: downsample.ResLevel1,
   417  			res: []resBlock{
   418  				{window: downsample.ResLevel1, mint: 100, maxt: 200},
   419  				{window: downsample.ResLevel1, mint: 200, maxt: 300},
   420  				{window: downsample.ResLevel1, mint: 300, maxt: 400},
   421  				{window: downsample.ResLevel0, mint: 300, maxt: 600},
   422  				{window: downsample.ResLevel0, mint: 400, maxt: 500},
   423  			},
   424  		}, {
   425  			mint:          0,
   426  			maxt:          500,
   427  			maxResolution: downsample.ResLevel2,
   428  			res: []resBlock{
   429  				{window: downsample.ResLevel1, mint: 0, maxt: 100},
   430  				{window: downsample.ResLevel2, mint: 100, maxt: 200},
   431  				{window: downsample.ResLevel2, mint: 200, maxt: 300},
   432  				{window: downsample.ResLevel1, mint: 300, maxt: 400},
   433  				{window: downsample.ResLevel0, mint: 300, maxt: 600},
   434  				{window: downsample.ResLevel0, mint: 400, maxt: 500},
   435  			},
   436  		},
   437  	} {
   438  		t.Run("", func(t *testing.T) {
   439  			var exp []*bucketBlock
   440  			for _, b := range c.res {
   441  				var m metadata.Meta
   442  				m.Thanos.Downsample.Resolution = b.window
   443  				m.MinTime = b.mint
   444  				m.MaxTime = b.maxt
   445  				exp = append(exp, &bucketBlock{meta: &m})
   446  			}
   447  			testutil.Equals(t, exp, set.getFor(c.mint, c.maxt, c.maxResolution, nil))
   448  		})
   449  	}
   450  }
   451  
   452  func TestBucketBlockSet_remove(t *testing.T) {
   453  	defer custom.TolerantVerifyLeak(t)
   454  
   455  	set := newBucketBlockSet(labels.Labels{})
   456  
   457  	type resBlock struct {
   458  		id         ulid.ULID
   459  		mint, maxt int64
   460  	}
   461  	input := []resBlock{
   462  		{id: ulid.MustNew(1, nil), mint: 0, maxt: 100},
   463  		{id: ulid.MustNew(2, nil), mint: 100, maxt: 200},
   464  		{id: ulid.MustNew(3, nil), mint: 200, maxt: 300},
   465  	}
   466  
   467  	for _, in := range input {
   468  		var m metadata.Meta
   469  		m.ULID = in.id
   470  		m.MinTime = in.mint
   471  		m.MaxTime = in.maxt
   472  		testutil.Ok(t, set.add(&bucketBlock{meta: &m}))
   473  	}
   474  	set.remove(input[1].id)
   475  	res := set.getFor(0, 300, 0, nil)
   476  
   477  	testutil.Equals(t, 2, len(res))
   478  	testutil.Equals(t, input[0].id, res[0].meta.ULID)
   479  	testutil.Equals(t, input[2].id, res[1].meta.ULID)
   480  }
   481  
   482  func TestBucketBlockSet_labelMatchers(t *testing.T) {
   483  	defer custom.TolerantVerifyLeak(t)
   484  
   485  	set := newBucketBlockSet(labels.FromStrings("a", "b", "c", "d"))
   486  
   487  	cases := []struct {
   488  		in    []*labels.Matcher
   489  		res   []*labels.Matcher
   490  		match bool
   491  	}{
   492  		{
   493  			in:    []*labels.Matcher{},
   494  			res:   []*labels.Matcher{},
   495  			match: true,
   496  		},
   497  		{
   498  			in: []*labels.Matcher{
   499  				{Type: labels.MatchEqual, Name: "a", Value: "b"},
   500  				{Type: labels.MatchEqual, Name: "c", Value: "d"},
   501  			},
   502  			res:   []*labels.Matcher{},
   503  			match: true,
   504  		},
   505  		{
   506  			in: []*labels.Matcher{
   507  				{Type: labels.MatchEqual, Name: "a", Value: "b"},
   508  				{Type: labels.MatchEqual, Name: "c", Value: "b"},
   509  			},
   510  			match: false,
   511  		},
   512  		{
   513  			in: []*labels.Matcher{
   514  				{Type: labels.MatchEqual, Name: "a", Value: "b"},
   515  				{Type: labels.MatchEqual, Name: "e", Value: "f"},
   516  			},
   517  			res: []*labels.Matcher{
   518  				{Type: labels.MatchEqual, Name: "e", Value: "f"},
   519  			},
   520  			match: true,
   521  		},
   522  		// Those are matchers mentioned here: https://github.com/prometheus/prometheus/pull/3578#issuecomment-351653555
   523  		// We want to provide explicit tests that says when Thanos supports its and when not. We don't support it here in
   524  		// external labelset level.
   525  		{
   526  			in: []*labels.Matcher{
   527  				{Type: labels.MatchNotEqual, Name: "", Value: "x"},
   528  			},
   529  			res: []*labels.Matcher{
   530  				{Type: labels.MatchNotEqual, Name: "", Value: "x"},
   531  			},
   532  			match: true,
   533  		},
   534  		{
   535  			in: []*labels.Matcher{
   536  				{Type: labels.MatchNotEqual, Name: "", Value: "d"},
   537  			},
   538  			res: []*labels.Matcher{
   539  				{Type: labels.MatchNotEqual, Name: "", Value: "d"},
   540  			},
   541  			match: true,
   542  		},
   543  	}
   544  	for _, c := range cases {
   545  		res, ok := set.labelMatchers(c.in...)
   546  		testutil.Equals(t, c.match, ok)
   547  		testutil.Equals(t, c.res, res)
   548  	}
   549  }
   550  
   551  func TestGapBasedPartitioner_Partition(t *testing.T) {
   552  	defer custom.TolerantVerifyLeak(t)
   553  
   554  	const maxGapSize = 1024 * 512
   555  
   556  	for _, c := range []struct {
   557  		input    [][2]int
   558  		expected []Part
   559  	}{
   560  		{
   561  			input:    [][2]int{{1, 10}},
   562  			expected: []Part{{Start: 1, End: 10, ElemRng: [2]int{0, 1}}},
   563  		},
   564  		{
   565  			input:    [][2]int{{1, 2}, {3, 5}, {7, 10}},
   566  			expected: []Part{{Start: 1, End: 10, ElemRng: [2]int{0, 3}}},
   567  		},
   568  		{
   569  			input: [][2]int{
   570  				{1, 2},
   571  				{3, 5},
   572  				{20, 30},
   573  				{maxGapSize + 31, maxGapSize + 32},
   574  			},
   575  			expected: []Part{
   576  				{Start: 1, End: 30, ElemRng: [2]int{0, 3}},
   577  				{Start: maxGapSize + 31, End: maxGapSize + 32, ElemRng: [2]int{3, 4}},
   578  			},
   579  		},
   580  		// Overlapping ranges.
   581  		{
   582  			input: [][2]int{
   583  				{1, 30},
   584  				{1, 4},
   585  				{3, 28},
   586  				{maxGapSize + 31, maxGapSize + 32},
   587  				{maxGapSize + 31, maxGapSize + 40},
   588  			},
   589  			expected: []Part{
   590  				{Start: 1, End: 30, ElemRng: [2]int{0, 3}},
   591  				{Start: maxGapSize + 31, End: maxGapSize + 40, ElemRng: [2]int{3, 5}},
   592  			},
   593  		},
   594  		{
   595  			input: [][2]int{
   596  				// Mimick AllPostingsKey, where range specified whole range.
   597  				{1, 15},
   598  				{1, maxGapSize + 100},
   599  				{maxGapSize + 31, maxGapSize + 40},
   600  			},
   601  			expected: []Part{{Start: 1, End: maxGapSize + 100, ElemRng: [2]int{0, 3}}},
   602  		},
   603  	} {
   604  		res := gapBasedPartitioner{maxGapSize: maxGapSize}.Partition(len(c.input), func(i int) (uint64, uint64) {
   605  			return uint64(c.input[i][0]), uint64(c.input[i][1])
   606  		})
   607  		testutil.Equals(t, c.expected, res)
   608  	}
   609  }
   610  
   611  func TestBucketStoreConfig_validate(t *testing.T) {
   612  	tests := map[string]struct {
   613  		config   *BucketStore
   614  		expected error
   615  	}{
   616  		"should pass on valid config": {
   617  			config: &BucketStore{
   618  				blockSyncConcurrency: 1,
   619  			},
   620  			expected: nil,
   621  		},
   622  		"should fail on blockSyncConcurrency < 1": {
   623  			config: &BucketStore{
   624  				blockSyncConcurrency: 0,
   625  			},
   626  			expected: errBlockSyncConcurrencyNotValid,
   627  		},
   628  	}
   629  
   630  	for testName, testData := range tests {
   631  		t.Run(testName, func(t *testing.T) {
   632  			testutil.Equals(t, testData.expected, testData.config.validate())
   633  		})
   634  	}
   635  }
   636  
   637  func TestBucketStore_Info(t *testing.T) {
   638  	defer custom.TolerantVerifyLeak(t)
   639  
   640  	ctx, cancel := context.WithCancel(context.Background())
   641  	defer cancel()
   642  
   643  	dir := t.TempDir()
   644  
   645  	chunkPool, err := NewDefaultChunkBytesPool(2e5)
   646  	testutil.Ok(t, err)
   647  
   648  	bucketStore, err := NewBucketStore(
   649  		nil,
   650  		nil,
   651  		dir,
   652  		NewChunksLimiterFactory(0),
   653  		NewSeriesLimiterFactory(0),
   654  		NewBytesLimiterFactory(0),
   655  		NewGapBasedPartitioner(PartitionerMaxGapSize),
   656  		20,
   657  		true,
   658  		DefaultPostingOffsetInMemorySampling,
   659  		false,
   660  		false,
   661  		0,
   662  		WithChunkPool(chunkPool),
   663  		WithFilterConfig(allowAllFilterConf),
   664  	)
   665  	testutil.Ok(t, err)
   666  	defer func() { testutil.Ok(t, bucketStore.Close()) }()
   667  
   668  	resp, err := bucketStore.Info(ctx, &storepb.InfoRequest{})
   669  	testutil.Ok(t, err)
   670  
   671  	testutil.Equals(t, storepb.StoreType_STORE, resp.StoreType)
   672  	testutil.Equals(t, int64(math.MaxInt64), resp.MinTime)
   673  	testutil.Equals(t, int64(math.MinInt64), resp.MaxTime)
   674  	testutil.Equals(t, []labelpb.ZLabelSet(nil), resp.LabelSets)
   675  	testutil.Equals(t, []labelpb.ZLabel(nil), resp.Labels)
   676  }
   677  
   678  type recorder struct {
   679  	mtx sync.Mutex
   680  	objstore.Bucket
   681  
   682  	getRangeTouched []string
   683  	getTouched      []string
   684  }
   685  
   686  func (r *recorder) Get(ctx context.Context, name string) (io.ReadCloser, error) {
   687  	r.mtx.Lock()
   688  	defer r.mtx.Unlock()
   689  
   690  	r.getTouched = append(r.getTouched, name)
   691  	return r.Bucket.Get(ctx, name)
   692  }
   693  
   694  func (r *recorder) GetRange(ctx context.Context, name string, off, length int64) (io.ReadCloser, error) {
   695  	r.mtx.Lock()
   696  	defer r.mtx.Unlock()
   697  
   698  	r.getRangeTouched = append(r.getRangeTouched, name)
   699  	return r.Bucket.GetRange(ctx, name, off, length)
   700  }
   701  
   702  func TestBucketStore_Sharding(t *testing.T) {
   703  	ctx := context.Background()
   704  	logger := log.NewNopLogger()
   705  
   706  	dir := t.TempDir()
   707  
   708  	bkt := objstore.NewInMemBucket()
   709  	series := []labels.Labels{labels.FromStrings("a", "1", "b", "1")}
   710  
   711  	id1, err := e2eutil.CreateBlock(ctx, dir, series, 10, 0, 1000, labels.Labels{{Name: "cluster", Value: "a"}, {Name: "region", Value: "r1"}}, 0, metadata.NoneFunc)
   712  	testutil.Ok(t, err)
   713  	testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id1.String()), metadata.NoneFunc))
   714  
   715  	id2, err := e2eutil.CreateBlock(ctx, dir, series, 10, 1000, 2000, labels.Labels{{Name: "cluster", Value: "a"}, {Name: "region", Value: "r1"}}, 0, metadata.NoneFunc)
   716  	testutil.Ok(t, err)
   717  	testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id2.String()), metadata.NoneFunc))
   718  
   719  	id3, err := e2eutil.CreateBlock(ctx, dir, series, 10, 0, 1000, labels.Labels{{Name: "cluster", Value: "b"}, {Name: "region", Value: "r1"}}, 0, metadata.NoneFunc)
   720  	testutil.Ok(t, err)
   721  	testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id3.String()), metadata.NoneFunc))
   722  
   723  	id4, err := e2eutil.CreateBlock(ctx, dir, series, 10, 0, 1000, labels.Labels{{Name: "cluster", Value: "a"}, {Name: "region", Value: "r2"}}, 0, metadata.NoneFunc)
   724  	testutil.Ok(t, err)
   725  	testutil.Ok(t, block.Upload(ctx, logger, bkt, filepath.Join(dir, id4.String()), metadata.NoneFunc))
   726  
   727  	if ok := t.Run("new_runs", func(t *testing.T) {
   728  		testSharding(t, "", bkt, id1, id2, id3, id4)
   729  	}); !ok {
   730  		return
   731  	}
   732  
   733  	dir2 := t.TempDir()
   734  
   735  	t.Run("reuse_disk", func(t *testing.T) {
   736  		testSharding(t, dir2, bkt, id1, id2, id3, id4)
   737  	})
   738  }
   739  
   740  func testSharding(t *testing.T, reuseDisk string, bkt objstore.Bucket, all ...ulid.ULID) {
   741  	var cached []ulid.ULID
   742  
   743  	logger := log.NewLogfmtLogger(os.Stderr)
   744  	for _, sc := range []struct {
   745  		name              string
   746  		relabel           string
   747  		expectedIDs       []ulid.ULID
   748  		expectedAdvLabels []labelpb.ZLabelSet
   749  	}{
   750  		{
   751  			name:        "no sharding",
   752  			expectedIDs: all,
   753  			expectedAdvLabels: []labelpb.ZLabelSet{
   754  				{
   755  					Labels: []labelpb.ZLabel{
   756  						{Name: "cluster", Value: "a"},
   757  						{Name: "region", Value: "r1"},
   758  					},
   759  				},
   760  				{
   761  					Labels: []labelpb.ZLabel{
   762  						{Name: "cluster", Value: "a"},
   763  						{Name: "region", Value: "r2"},
   764  					},
   765  				},
   766  				{
   767  					Labels: []labelpb.ZLabel{
   768  						{Name: "cluster", Value: "b"},
   769  						{Name: "region", Value: "r1"},
   770  					},
   771  				},
   772  				{
   773  					Labels: []labelpb.ZLabel{
   774  						{Name: CompatibilityTypeLabelName, Value: "store"},
   775  					},
   776  				},
   777  			},
   778  		},
   779  		{
   780  			name: "drop cluster=a sources",
   781  			relabel: `
   782              - action: drop
   783                regex: "a"
   784                source_labels:
   785                - cluster
   786              `,
   787  			expectedIDs: []ulid.ULID{all[2]},
   788  			expectedAdvLabels: []labelpb.ZLabelSet{
   789  				{
   790  					Labels: []labelpb.ZLabel{
   791  						{Name: "cluster", Value: "b"},
   792  						{Name: "region", Value: "r1"},
   793  					},
   794  				},
   795  				{
   796  					Labels: []labelpb.ZLabel{
   797  						{Name: CompatibilityTypeLabelName, Value: "store"},
   798  					},
   799  				},
   800  			},
   801  		},
   802  		{
   803  			name: "keep only cluster=a sources",
   804  			relabel: `
   805              - action: keep
   806                regex: "a"
   807                source_labels:
   808                - cluster
   809              `,
   810  			expectedIDs: []ulid.ULID{all[0], all[1], all[3]},
   811  			expectedAdvLabels: []labelpb.ZLabelSet{
   812  				{
   813  					Labels: []labelpb.ZLabel{
   814  						{Name: "cluster", Value: "a"},
   815  						{Name: "region", Value: "r1"},
   816  					},
   817  				},
   818  				{
   819  					Labels: []labelpb.ZLabel{
   820  						{Name: "cluster", Value: "a"},
   821  						{Name: "region", Value: "r2"},
   822  					},
   823  				},
   824  				{
   825  					Labels: []labelpb.ZLabel{
   826  						{Name: CompatibilityTypeLabelName, Value: "store"},
   827  					},
   828  				},
   829  			},
   830  		},
   831  		{
   832  			name: "keep only cluster=a without .*2 region sources",
   833  			relabel: `
   834              - action: keep
   835                regex: "a"
   836                source_labels:
   837                - cluster
   838              - action: drop
   839                regex: ".*2"
   840                source_labels:
   841                - region
   842              `,
   843  			expectedIDs: []ulid.ULID{all[0], all[1]},
   844  			expectedAdvLabels: []labelpb.ZLabelSet{
   845  				{
   846  					Labels: []labelpb.ZLabel{
   847  						{Name: "cluster", Value: "a"},
   848  						{Name: "region", Value: "r1"},
   849  					},
   850  				},
   851  				{
   852  					Labels: []labelpb.ZLabel{
   853  						{Name: CompatibilityTypeLabelName, Value: "store"},
   854  					},
   855  				},
   856  			},
   857  		},
   858  		{
   859  			name: "drop all",
   860  			relabel: `
   861              - action: drop
   862                regex: "a"
   863                source_labels:
   864                - cluster
   865              - action: drop
   866                regex: "r1"
   867                source_labels:
   868                - region
   869              `,
   870  			expectedIDs:       []ulid.ULID{},
   871  			expectedAdvLabels: []labelpb.ZLabelSet{},
   872  		},
   873  	} {
   874  		t.Run(sc.name, func(t *testing.T) {
   875  			dir := reuseDisk
   876  
   877  			if dir == "" {
   878  				dir = t.TempDir()
   879  			}
   880  			relabelConf, err := block.ParseRelabelConfig([]byte(sc.relabel), block.SelectorSupportedRelabelActions)
   881  			testutil.Ok(t, err)
   882  
   883  			rec := &recorder{Bucket: bkt}
   884  			metaFetcher, err := block.NewMetaFetcher(logger, 20, objstore.WithNoopInstr(bkt), dir, nil, []block.MetadataFilter{
   885  				block.NewTimePartitionMetaFilter(allowAllFilterConf.MinTime, allowAllFilterConf.MaxTime),
   886  				block.NewLabelShardedMetaFilter(relabelConf),
   887  			})
   888  			testutil.Ok(t, err)
   889  
   890  			bucketStore, err := NewBucketStore(
   891  				objstore.WithNoopInstr(rec),
   892  				metaFetcher,
   893  				dir,
   894  				NewChunksLimiterFactory(0),
   895  				NewSeriesLimiterFactory(0),
   896  				NewBytesLimiterFactory(0),
   897  				NewGapBasedPartitioner(PartitionerMaxGapSize),
   898  				20,
   899  				true,
   900  				DefaultPostingOffsetInMemorySampling,
   901  				false,
   902  				false,
   903  				0,
   904  				WithLogger(logger),
   905  				WithFilterConfig(allowAllFilterConf),
   906  			)
   907  			testutil.Ok(t, err)
   908  			defer func() { testutil.Ok(t, bucketStore.Close()) }()
   909  
   910  			testutil.Ok(t, bucketStore.InitialSync(context.Background()))
   911  
   912  			// Check "stored" blocks.
   913  			ids := make([]ulid.ULID, 0, len(bucketStore.blocks))
   914  			for id := range bucketStore.blocks {
   915  				ids = append(ids, id)
   916  			}
   917  			sort.Slice(ids, func(i, j int) bool {
   918  				return ids[i].Compare(ids[j]) < 0
   919  			})
   920  			testutil.Equals(t, sc.expectedIDs, ids)
   921  
   922  			// Check Info endpoint.
   923  			resp, err := bucketStore.Info(context.Background(), &storepb.InfoRequest{})
   924  			testutil.Ok(t, err)
   925  
   926  			testutil.Equals(t, storepb.StoreType_STORE, resp.StoreType)
   927  			testutil.Equals(t, []labelpb.ZLabel(nil), resp.Labels)
   928  			testutil.Equals(t, sc.expectedAdvLabels, resp.LabelSets)
   929  
   930  			// Make sure we don't download files we did not expect to.
   931  			// Regression test: https://github.com/thanos-io/thanos/issues/1664
   932  
   933  			// Sort records. We load blocks concurrently so operations might be not ordered.
   934  			sort.Strings(rec.getRangeTouched)
   935  
   936  			// With binary header nothing should be downloaded fully.
   937  			testutil.Equals(t, []string(nil), rec.getTouched)
   938  			if reuseDisk != "" {
   939  				testutil.Equals(t, expectedTouchedBlockOps(all, sc.expectedIDs, cached), rec.getRangeTouched)
   940  				cached = sc.expectedIDs
   941  				return
   942  			}
   943  
   944  			testutil.Equals(t, expectedTouchedBlockOps(all, sc.expectedIDs, nil), rec.getRangeTouched)
   945  		})
   946  	}
   947  }
   948  
   949  func expectedTouchedBlockOps(all, expected, cached []ulid.ULID) []string {
   950  	var ops []string
   951  	for _, id := range all {
   952  		blockCached := false
   953  		for _, fid := range cached {
   954  			if id.Compare(fid) == 0 {
   955  				blockCached = true
   956  				break
   957  			}
   958  		}
   959  		if blockCached {
   960  			continue
   961  		}
   962  
   963  		found := false
   964  		for _, fid := range expected {
   965  			if id.Compare(fid) == 0 {
   966  				found = true
   967  				break
   968  			}
   969  		}
   970  
   971  		if found {
   972  			ops = append(ops,
   973  				// To create binary header we touch part of index few times.
   974  				path.Join(id.String(), block.IndexFilename), // Version.
   975  				path.Join(id.String(), block.IndexFilename), // TOC.
   976  				path.Join(id.String(), block.IndexFilename), // Symbols.
   977  				path.Join(id.String(), block.IndexFilename), // PostingOffsets.
   978  			)
   979  		}
   980  	}
   981  	sort.Strings(ops)
   982  	return ops
   983  }
   984  
   985  // Regression tests against: https://github.com/thanos-io/thanos/issues/1983.
   986  func TestReadIndexCache_LoadSeries(t *testing.T) {
   987  	bkt := objstore.NewInMemBucket()
   988  
   989  	s := newBucketStoreMetrics(nil)
   990  	b := &bucketBlock{
   991  		meta: &metadata.Meta{
   992  			BlockMeta: tsdb.BlockMeta{
   993  				ULID: ulid.MustNew(1, nil),
   994  			},
   995  		},
   996  		bkt:        bkt,
   997  		logger:     log.NewNopLogger(),
   998  		metrics:    s,
   999  		indexCache: noopCache{},
  1000  	}
  1001  
  1002  	buf := encoding.Encbuf{}
  1003  	buf.PutByte(0)
  1004  	buf.PutByte(0)
  1005  	buf.PutUvarint(10)
  1006  	buf.PutString("aaaaaaaaaa")
  1007  	buf.PutUvarint(10)
  1008  	buf.PutString("bbbbbbbbbb")
  1009  	buf.PutUvarint(10)
  1010  	buf.PutString("cccccccccc")
  1011  	testutil.Ok(t, bkt.Upload(context.Background(), filepath.Join(b.meta.ULID.String(), block.IndexFilename), bytes.NewReader(buf.Get())))
  1012  
  1013  	r := bucketIndexReader{
  1014  		block:        b,
  1015  		stats:        &queryStats{},
  1016  		loadedSeries: map[storage.SeriesRef][]byte{},
  1017  	}
  1018  
  1019  	// Success with no refetches.
  1020  	testutil.Ok(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2, 13, 24}, false, 2, 100, NewBytesLimiterFactory(0)(nil)))
  1021  	testutil.Equals(t, map[storage.SeriesRef][]byte{
  1022  		2:  []byte("aaaaaaaaaa"),
  1023  		13: []byte("bbbbbbbbbb"),
  1024  		24: []byte("cccccccccc"),
  1025  	}, r.loadedSeries)
  1026  	testutil.Equals(t, float64(0), promtest.ToFloat64(s.seriesRefetches))
  1027  
  1028  	// Success with 2 refetches.
  1029  	r.loadedSeries = map[storage.SeriesRef][]byte{}
  1030  	testutil.Ok(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2, 13, 24}, false, 2, 15, NewBytesLimiterFactory(0)(nil)))
  1031  	testutil.Equals(t, map[storage.SeriesRef][]byte{
  1032  		2:  []byte("aaaaaaaaaa"),
  1033  		13: []byte("bbbbbbbbbb"),
  1034  		24: []byte("cccccccccc"),
  1035  	}, r.loadedSeries)
  1036  	testutil.Equals(t, float64(2), promtest.ToFloat64(s.seriesRefetches))
  1037  
  1038  	// Success with refetch on first element.
  1039  	r.loadedSeries = map[storage.SeriesRef][]byte{}
  1040  	testutil.Ok(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2}, false, 2, 5, NewBytesLimiterFactory(0)(nil)))
  1041  	testutil.Equals(t, map[storage.SeriesRef][]byte{
  1042  		2: []byte("aaaaaaaaaa"),
  1043  	}, r.loadedSeries)
  1044  	testutil.Equals(t, float64(3), promtest.ToFloat64(s.seriesRefetches))
  1045  
  1046  	buf.Reset()
  1047  	buf.PutByte(0)
  1048  	buf.PutByte(0)
  1049  	buf.PutUvarint(10)
  1050  	buf.PutString("aaaaaaa")
  1051  	testutil.Ok(t, bkt.Upload(context.Background(), filepath.Join(b.meta.ULID.String(), block.IndexFilename), bytes.NewReader(buf.Get())))
  1052  
  1053  	// Fail, but no recursion at least.
  1054  	testutil.NotOk(t, r.loadSeries(context.TODO(), []storage.SeriesRef{2, 13, 24}, false, 1, 15, NewBytesLimiterFactory(0)(nil)))
  1055  }
  1056  
  1057  func TestBucketIndexReader_ExpandedPostings(t *testing.T) {
  1058  	tb := testutil.NewTB(t)
  1059  
  1060  	tmpDir := t.TempDir()
  1061  
  1062  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  1063  	testutil.Ok(tb, err)
  1064  	defer func() { testutil.Ok(tb, bkt.Close()) }()
  1065  
  1066  	id := uploadTestBlock(tb, tmpDir, bkt, 500)
  1067  
  1068  	r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, id, DefaultPostingOffsetInMemorySampling)
  1069  	testutil.Ok(tb, err)
  1070  
  1071  	benchmarkExpandedPostings(tb, bkt, id, r, 500)
  1072  }
  1073  
  1074  func BenchmarkBucketIndexReader_ExpandedPostings(b *testing.B) {
  1075  	tb := testutil.NewTB(b)
  1076  
  1077  	tmpDir := b.TempDir()
  1078  
  1079  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  1080  	testutil.Ok(tb, err)
  1081  	defer func() { testutil.Ok(tb, bkt.Close()) }()
  1082  
  1083  	id := uploadTestBlock(tb, tmpDir, bkt, 50e5)
  1084  	r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, id, DefaultPostingOffsetInMemorySampling)
  1085  	testutil.Ok(tb, err)
  1086  
  1087  	benchmarkExpandedPostings(tb, bkt, id, r, 50e5)
  1088  }
  1089  
  1090  func uploadTestBlock(t testing.TB, tmpDir string, bkt objstore.Bucket, series int) ulid.ULID {
  1091  	headOpts := tsdb.DefaultHeadOptions()
  1092  	headOpts.ChunkDirRoot = tmpDir
  1093  	headOpts.ChunkRange = 1000
  1094  	h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  1095  	testutil.Ok(t, err)
  1096  	defer func() {
  1097  		testutil.Ok(t, h.Close())
  1098  	}()
  1099  
  1100  	logger := log.NewNopLogger()
  1101  
  1102  	appendTestData(t, h.Appender(context.Background()), series)
  1103  
  1104  	testutil.Ok(t, os.MkdirAll(filepath.Join(tmpDir, "tmp"), os.ModePerm))
  1105  	id := createBlockFromHead(t, filepath.Join(tmpDir, "tmp"), h)
  1106  
  1107  	_, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(tmpDir, "tmp", id.String()), metadata.Thanos{
  1108  		Labels:     labels.Labels{{Name: "ext1", Value: "1"}}.Map(),
  1109  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  1110  		Source:     metadata.TestSource,
  1111  	}, nil)
  1112  	testutil.Ok(t, err)
  1113  	testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, "tmp", id.String()), metadata.NoneFunc))
  1114  	testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, "tmp", id.String()), metadata.NoneFunc))
  1115  
  1116  	return id
  1117  }
  1118  
  1119  func appendTestData(t testing.TB, app storage.Appender, series int) {
  1120  	addSeries := func(l labels.Labels) {
  1121  		_, err := app.Append(0, l, 0, 0)
  1122  		testutil.Ok(t, err)
  1123  	}
  1124  
  1125  	series = series / 5
  1126  	uniq := 0
  1127  	for n := 0; n < 10; n++ {
  1128  		for i := 0; i < series/10; i++ {
  1129  			addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "foo", "uniq", fmt.Sprintf("%08d", uniq)))
  1130  			// Have some series that won't be matched, to properly test inverted matches.
  1131  			addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "bar", "uniq", fmt.Sprintf("%08d", uniq+1)))
  1132  			addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", "0_"+strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "bar", "uniq", fmt.Sprintf("%08d", uniq+2)))
  1133  			addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", "1_"+strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "bar", "uniq", fmt.Sprintf("%08d", uniq+3)))
  1134  			addSeries(labels.FromStrings("i", strconv.Itoa(i)+storetestutil.LabelLongSuffix, "n", "2_"+strconv.Itoa(n)+storetestutil.LabelLongSuffix, "j", "foo", "uniq", fmt.Sprintf("%08d", uniq+4)))
  1135  			uniq += 5
  1136  		}
  1137  	}
  1138  	testutil.Ok(t, app.Commit())
  1139  }
  1140  
  1141  func createBlockFromHead(t testing.TB, dir string, head *tsdb.Head) ulid.ULID {
  1142  	compactor, err := tsdb.NewLeveledCompactor(context.Background(), nil, log.NewNopLogger(), []int64{1000000}, nil, nil)
  1143  	testutil.Ok(t, err)
  1144  	testutil.Ok(t, os.MkdirAll(dir, 0777))
  1145  
  1146  	// Add +1 millisecond to block maxt because block intervals are half-open: [b.MinTime, b.MaxTime).
  1147  	// Because of this block intervals are always +1 than the total samples it includes.
  1148  	ulid, err := compactor.Write(dir, head, head.MinTime(), head.MaxTime()+1, nil)
  1149  	testutil.Ok(t, err)
  1150  	return ulid
  1151  }
  1152  
  1153  // Very similar benchmark to ths: https://github.com/prometheus/prometheus/blob/1d1732bc25cc4b47f513cb98009a4eb91879f175/tsdb/querier_bench_test.go#L82,
  1154  // but with postings results check when run as test.
  1155  func benchmarkExpandedPostings(
  1156  	t testutil.TB,
  1157  	bkt objstore.BucketReader,
  1158  	id ulid.ULID,
  1159  	r indexheader.Reader,
  1160  	series int,
  1161  ) {
  1162  	n1 := labels.MustNewMatcher(labels.MatchEqual, "n", "1"+storetestutil.LabelLongSuffix)
  1163  	jFoo := labels.MustNewMatcher(labels.MatchEqual, "j", "foo")
  1164  	jNotFoo := labels.MustNewMatcher(labels.MatchNotEqual, "j", "foo")
  1165  	iStar := labels.MustNewMatcher(labels.MatchRegexp, "i", "^.*$")
  1166  	iPlus := labels.MustNewMatcher(labels.MatchRegexp, "i", "^.+$")
  1167  	i1Plus := labels.MustNewMatcher(labels.MatchRegexp, "i", "^1.+$")
  1168  	iEmptyRe := labels.MustNewMatcher(labels.MatchRegexp, "i", "^$")
  1169  	iNotEmpty := labels.MustNewMatcher(labels.MatchNotEqual, "i", "")
  1170  	iNot2 := labels.MustNewMatcher(labels.MatchNotEqual, "i", "2"+storetestutil.LabelLongSuffix)
  1171  	iNot2Star := labels.MustNewMatcher(labels.MatchNotRegexp, "i", "^2.*$")
  1172  	iRegexSet := labels.MustNewMatcher(labels.MatchRegexp, "i", "0"+storetestutil.LabelLongSuffix+"|1"+storetestutil.LabelLongSuffix+"|2"+storetestutil.LabelLongSuffix)
  1173  	bigValueSetSize := series / 10
  1174  	if bigValueSetSize > 50000 {
  1175  		bigValueSetSize = 50000
  1176  	}
  1177  	bigValueSet := make([]string, 0, bigValueSetSize)
  1178  	for i := 0; i < series; i += series / bigValueSetSize {
  1179  		bigValueSet = append(bigValueSet, fmt.Sprintf("%08d", i))
  1180  	}
  1181  	bigValueSetSize = len(bigValueSet)
  1182  	rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle(len(bigValueSet), func(i, j int) {
  1183  		bigValueSet[i], bigValueSet[j] = bigValueSet[j], bigValueSet[i]
  1184  	})
  1185  	iRegexBigValueSet := labels.MustNewMatcher(labels.MatchRegexp, "uniq", strings.Join(bigValueSet, "|"))
  1186  
  1187  	series = series / 5
  1188  	cases := []struct {
  1189  		name     string
  1190  		matchers []*labels.Matcher
  1191  
  1192  		expectedLen int
  1193  	}{
  1194  		{`n="1"`, []*labels.Matcher{n1}, int(float64(series) * 0.2)},
  1195  		{`n="1",j="foo"`, []*labels.Matcher{n1, jFoo}, int(float64(series) * 0.1)},
  1196  		{`j="foo",n="1"`, []*labels.Matcher{jFoo, n1}, int(float64(series) * 0.1)},
  1197  		{`n="1",j!="foo"`, []*labels.Matcher{n1, jNotFoo}, int(float64(series) * 0.1)},
  1198  		{`i=~".*"`, []*labels.Matcher{iStar}, 5 * series},
  1199  		{`i=~".+"`, []*labels.Matcher{iPlus}, 5 * series},
  1200  		{`i=~""`, []*labels.Matcher{iEmptyRe}, 0},
  1201  		{`i!=""`, []*labels.Matcher{iNotEmpty}, 5 * series},
  1202  		{`n="1",i=~".*",j="foo"`, []*labels.Matcher{n1, iStar, jFoo}, int(float64(series) * 0.1)},
  1203  		{`n="1",i=~".*",i!="2",j="foo"`, []*labels.Matcher{n1, iStar, iNot2, jFoo}, int(float64(series)/10 - 1)},
  1204  		{`n="1",i!=""`, []*labels.Matcher{n1, iNotEmpty}, int(float64(series) * 0.2)},
  1205  		{`n="1",i!="",j="foo"`, []*labels.Matcher{n1, iNotEmpty, jFoo}, int(float64(series) * 0.1)},
  1206  		{`n="1",i=~".+",j="foo"`, []*labels.Matcher{n1, iPlus, jFoo}, int(float64(series) * 0.1)},
  1207  		{`n="1",i=~"1.+",j="foo"`, []*labels.Matcher{n1, i1Plus, jFoo}, int(float64(series) * 0.011111)},
  1208  		{`n="1",i=~".+",i!="2",j="foo"`, []*labels.Matcher{n1, iPlus, iNot2, jFoo}, int(float64(series)/10 - 1)},
  1209  		{`n="1",i=~".+",i!~"2.*",j="foo"`, []*labels.Matcher{n1, iPlus, iNot2Star, jFoo}, int(1 + float64(series)*0.088888)},
  1210  		{`n="1",i=~".+",i=~".+",i=~".+",i=~".+",i=~".+",j="foo"`, []*labels.Matcher{n1, iPlus, iPlus, iPlus, iPlus, iPlus, jFoo}, int(float64(series) * 0.1)},
  1211  		{`i=~"0|1|2"`, []*labels.Matcher{iRegexSet}, 150}, // 50 series for "1", 50 for "2" and 50 for "3".
  1212  		{`uniq=~"9|random-shuffled-values|1"`, []*labels.Matcher{iRegexBigValueSet}, bigValueSetSize},
  1213  	}
  1214  
  1215  	for _, c := range cases {
  1216  		t.Run(c.name, func(t testutil.TB) {
  1217  			b := &bucketBlock{
  1218  				logger:            log.NewNopLogger(),
  1219  				metrics:           newBucketStoreMetrics(nil),
  1220  				indexHeaderReader: r,
  1221  				indexCache:        noopCache{},
  1222  				bkt:               bkt,
  1223  				meta:              &metadata.Meta{BlockMeta: tsdb.BlockMeta{ULID: id}},
  1224  				partitioner:       NewGapBasedPartitioner(PartitionerMaxGapSize),
  1225  			}
  1226  
  1227  			indexr := newBucketIndexReader(b)
  1228  
  1229  			t.ResetTimer()
  1230  			for i := 0; i < t.N(); i++ {
  1231  				p, err := indexr.ExpandedPostings(context.Background(), newSortedMatchers(c.matchers), NewBytesLimiterFactory(0)(nil))
  1232  				testutil.Ok(t, err)
  1233  				testutil.Equals(t, c.expectedLen, len(p))
  1234  			}
  1235  		})
  1236  	}
  1237  }
  1238  
  1239  func TestExpandedPostingsEmptyPostings(t *testing.T) {
  1240  	tmpDir := t.TempDir()
  1241  
  1242  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  1243  	testutil.Ok(t, err)
  1244  	defer func() { testutil.Ok(t, bkt.Close()) }()
  1245  
  1246  	id := uploadTestBlock(t, tmpDir, bkt, 100)
  1247  
  1248  	r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, id, DefaultPostingOffsetInMemorySampling)
  1249  	testutil.Ok(t, err)
  1250  	b := &bucketBlock{
  1251  		logger:            log.NewNopLogger(),
  1252  		metrics:           newBucketStoreMetrics(nil),
  1253  		indexHeaderReader: r,
  1254  		indexCache:        noopCache{},
  1255  		bkt:               bkt,
  1256  		meta:              &metadata.Meta{BlockMeta: tsdb.BlockMeta{ULID: id}},
  1257  		partitioner:       NewGapBasedPartitioner(PartitionerMaxGapSize),
  1258  	}
  1259  
  1260  	indexr := newBucketIndexReader(b)
  1261  	matcher1 := labels.MustNewMatcher(labels.MatchEqual, "j", "foo")
  1262  	// Match nothing.
  1263  	matcher2 := labels.MustNewMatcher(labels.MatchRegexp, "i", "500.*")
  1264  	ps, err := indexr.ExpandedPostings(context.Background(), newSortedMatchers([]*labels.Matcher{matcher1, matcher2}), NewBytesLimiterFactory(0)(nil))
  1265  	testutil.Ok(t, err)
  1266  	testutil.Equals(t, len(ps), 0)
  1267  	// Make sure even if a matcher doesn't match any postings, we still cache empty expanded postings.
  1268  	testutil.Equals(t, 1, indexr.stats.cachedPostingsCompressions)
  1269  }
  1270  
  1271  func TestBucketSeries(t *testing.T) {
  1272  	tb := testutil.NewTB(t)
  1273  	storetestutil.RunSeriesInterestingCases(tb, 200e3, 200e3, func(t testutil.TB, samplesPerSeries, series int) {
  1274  		benchBucketSeries(t, chunkenc.ValFloat, false, samplesPerSeries, series, 1)
  1275  	})
  1276  }
  1277  
  1278  func TestBucketHistogramSeries(t *testing.T) {
  1279  	tb := testutil.NewTB(t)
  1280  	storetestutil.RunSeriesInterestingCases(tb, 200e3, 200e3, func(t testutil.TB, samplesPerSeries, series int) {
  1281  		benchBucketSeries(t, chunkenc.ValHistogram, false, samplesPerSeries, series, 1)
  1282  	})
  1283  }
  1284  
  1285  func TestBucketSkipChunksSeries(t *testing.T) {
  1286  	tb := testutil.NewTB(t)
  1287  	storetestutil.RunSeriesInterestingCases(tb, 200e3, 200e3, func(t testutil.TB, samplesPerSeries, series int) {
  1288  		benchBucketSeries(t, chunkenc.ValFloat, true, samplesPerSeries, series, 1)
  1289  	})
  1290  }
  1291  
  1292  func BenchmarkBucketSeries(b *testing.B) {
  1293  	tb := testutil.NewTB(b)
  1294  	// 10e6 samples = ~1736 days with 15s scrape
  1295  	storetestutil.RunSeriesInterestingCases(tb, 10e6, 10e5, func(t testutil.TB, samplesPerSeries, series int) {
  1296  		benchBucketSeries(t, chunkenc.ValFloat, false, samplesPerSeries, series, 1/100e6, 1/10e4, 1)
  1297  	})
  1298  }
  1299  
  1300  func BenchmarkBucketSkipChunksSeries(b *testing.B) {
  1301  	tb := testutil.NewTB(b)
  1302  	// 10e6 samples = ~1736 days with 15s scrape
  1303  	storetestutil.RunSeriesInterestingCases(tb, 10e6, 10e5, func(t testutil.TB, samplesPerSeries, series int) {
  1304  		benchBucketSeries(t, chunkenc.ValFloat, true, samplesPerSeries, series, 1/100e6, 1/10e4, 1)
  1305  	})
  1306  }
  1307  
  1308  func benchBucketSeries(t testutil.TB, sampleType chunkenc.ValueType, skipChunk bool, samplesPerSeries, totalSeries int, requestedRatios ...float64) {
  1309  	const numOfBlocks = 4
  1310  
  1311  	tmpDir := t.TempDir()
  1312  
  1313  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  1314  	testutil.Ok(t, err)
  1315  	defer func() { testutil.Ok(t, bkt.Close()) }()
  1316  
  1317  	var (
  1318  		logger = log.NewNopLogger()
  1319  		series []*storepb.Series
  1320  		random = rand.New(rand.NewSource(120))
  1321  	)
  1322  
  1323  	extLset := labels.Labels{{Name: "ext1", Value: "1"}}
  1324  	thanosMeta := metadata.Thanos{
  1325  		Labels:     extLset.Map(),
  1326  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  1327  		Source:     metadata.TestSource,
  1328  	}
  1329  
  1330  	blockDir := filepath.Join(tmpDir, "tmp")
  1331  
  1332  	samplesPerSeriesPerBlock := samplesPerSeries / numOfBlocks
  1333  	if samplesPerSeriesPerBlock == 0 {
  1334  		samplesPerSeriesPerBlock = 1
  1335  	}
  1336  
  1337  	seriesPerBlock := totalSeries / numOfBlocks
  1338  	if seriesPerBlock == 0 {
  1339  		seriesPerBlock = 1
  1340  	}
  1341  
  1342  	// Create 4 blocks. Each will have seriesPerBlock number of series that have samplesPerSeriesPerBlock samples.
  1343  	// Timestamp will be counted for each new series and new sample, so each each series will have unique timestamp.
  1344  	// This allows to pick time range that will correspond to number of series picked 1:1.
  1345  	for bi := 0; bi < numOfBlocks; bi++ {
  1346  		head, _ := storetestutil.CreateHeadWithSeries(t, bi, storetestutil.HeadGenOptions{
  1347  			TSDBDir:          filepath.Join(tmpDir, fmt.Sprintf("%d", bi)),
  1348  			SamplesPerSeries: samplesPerSeriesPerBlock,
  1349  			Series:           seriesPerBlock,
  1350  			PrependLabels:    extLset,
  1351  			Random:           random,
  1352  			SkipChunks:       t.IsBenchmark() || skipChunk,
  1353  			SampleType:       sampleType,
  1354  		})
  1355  		id := createBlockFromHead(t, blockDir, head)
  1356  		testutil.Ok(t, head.Close())
  1357  
  1358  		// Histogram chunks are represented differently in memory and on disk. In order to
  1359  		// have a precise comparison, we need to use the on-disk representation as the expected value
  1360  		// instead of the in-memory one.
  1361  		diskBlock, err := tsdb.OpenBlock(logger, path.Join(blockDir, id.String()), nil)
  1362  		testutil.Ok(t, err)
  1363  		series = append(series, storetestutil.ReadSeriesFromBlock(t, diskBlock, extLset, skipChunk)...)
  1364  
  1365  		meta, err := metadata.InjectThanos(logger, filepath.Join(blockDir, id.String()), thanosMeta, nil)
  1366  		testutil.Ok(t, err)
  1367  
  1368  		testutil.Ok(t, meta.WriteToDir(logger, filepath.Join(blockDir, id.String())))
  1369  		testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(blockDir, id.String()), metadata.NoneFunc))
  1370  	}
  1371  
  1372  	ibkt := objstore.WithNoopInstr(bkt)
  1373  	f, err := block.NewRawMetaFetcher(logger, ibkt)
  1374  	testutil.Ok(t, err)
  1375  
  1376  	chunkPool, err := pool.NewBucketedBytes(chunkBytesPoolMinSize, chunkBytesPoolMaxSize, 2, 1e9) // 1GB.
  1377  	testutil.Ok(t, err)
  1378  
  1379  	st, err := NewBucketStore(
  1380  		ibkt,
  1381  		f,
  1382  		tmpDir,
  1383  		NewChunksLimiterFactory(0),
  1384  		NewSeriesLimiterFactory(0),
  1385  		NewBytesLimiterFactory(0),
  1386  		NewGapBasedPartitioner(PartitionerMaxGapSize),
  1387  		1,
  1388  		false,
  1389  		DefaultPostingOffsetInMemorySampling,
  1390  		false,
  1391  		false,
  1392  		0,
  1393  		WithLogger(logger),
  1394  		WithChunkPool(chunkPool),
  1395  	)
  1396  	testutil.Ok(t, err)
  1397  
  1398  	if !t.IsBenchmark() {
  1399  		st.chunkPool = &mockedPool{parent: st.chunkPool}
  1400  	}
  1401  
  1402  	testutil.Ok(t, st.SyncBlocks(context.Background()))
  1403  
  1404  	var bCases []*storetestutil.SeriesCase
  1405  	for _, p := range requestedRatios {
  1406  		expectedSamples := int(p * float64(totalSeries*samplesPerSeries))
  1407  		if expectedSamples == 0 {
  1408  			expectedSamples = 1
  1409  		}
  1410  		seriesCut := int(p * float64(numOfBlocks*seriesPerBlock))
  1411  		if seriesCut == 0 {
  1412  			seriesCut = 1
  1413  		} else if seriesCut == 1 {
  1414  			seriesCut = expectedSamples / samplesPerSeriesPerBlock
  1415  		}
  1416  
  1417  		matchersCase := []*labels.Matcher{
  1418  			labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  1419  			labels.MustNewMatcher(labels.MatchNotEqual, "foo", "bar"),
  1420  			labels.MustNewMatcher(labels.MatchEqual, "j", "0"),
  1421  			labels.MustNewMatcher(labels.MatchNotEqual, "j", "0"),
  1422  			labels.MustNewMatcher(labels.MatchRegexp, "j", "(0|1)"),
  1423  			labels.MustNewMatcher(labels.MatchRegexp, "j", "0|1"),
  1424  			labels.MustNewMatcher(labels.MatchNotRegexp, "j", "(0|1)"),
  1425  			labels.MustNewMatcher(labels.MatchNotRegexp, "j", "0|1"),
  1426  		}
  1427  
  1428  		for _, lm := range matchersCase {
  1429  			var expectedSeries []*storepb.Series
  1430  			m, err := storepb.PromMatchersToMatchers(lm)
  1431  			testutil.Ok(t, err)
  1432  
  1433  			// seriesCut does not cut chunks properly, but those are assured against for non benchmarks only, where we use 100% case only.
  1434  			for _, s := range series[:seriesCut] {
  1435  				for _, label := range s.Labels {
  1436  					if label.Name == lm.Name && lm.Matches(label.Value) {
  1437  						expectedSeries = append(expectedSeries, s)
  1438  						break
  1439  					}
  1440  				}
  1441  			}
  1442  			bCases = append(bCases, &storetestutil.SeriesCase{
  1443  				Name: fmt.Sprintf("%dof%d[%s]", expectedSamples, totalSeries*samplesPerSeries, lm.String()),
  1444  				Req: &storepb.SeriesRequest{
  1445  					MinTime:    0,
  1446  					MaxTime:    int64(expectedSamples) - 1,
  1447  					Matchers:   m,
  1448  					SkipChunks: skipChunk,
  1449  				},
  1450  				ExpectedSeries: expectedSeries,
  1451  			})
  1452  		}
  1453  	}
  1454  	storetestutil.TestServerSeries(t, st, bCases...)
  1455  
  1456  	if !t.IsBenchmark() {
  1457  		if !skipChunk {
  1458  			// TODO(bwplotka): This is wrong negative for large number of samples (1mln). Investigate.
  1459  			testutil.Equals(t, 0, int(st.chunkPool.(*mockedPool).balance.Load()))
  1460  			st.chunkPool.(*mockedPool).gets.Store(0)
  1461  		}
  1462  
  1463  		for _, b := range st.blocks {
  1464  			// NOTE(bwplotka): It is 4 x 1.0 for 100mln samples. Kind of make sense: long series.
  1465  			testutil.Equals(t, 0.0, promtest.ToFloat64(b.metrics.seriesRefetches))
  1466  		}
  1467  	}
  1468  }
  1469  
  1470  var _ = fakePool{}
  1471  
  1472  type fakePool struct{}
  1473  
  1474  func (m fakePool) Get(sz int) (*[]byte, error) {
  1475  	b := make([]byte, 0, sz)
  1476  	return &b, nil
  1477  }
  1478  
  1479  func (m fakePool) Put(_ *[]byte) {}
  1480  
  1481  type mockedPool struct {
  1482  	parent  pool.Bytes
  1483  	balance atomic.Uint64
  1484  	gets    atomic.Uint64
  1485  }
  1486  
  1487  func (m *mockedPool) Get(sz int) (*[]byte, error) {
  1488  	b, err := m.parent.Get(sz)
  1489  	if err != nil {
  1490  		return nil, err
  1491  	}
  1492  	m.balance.Add(uint64(cap(*b)))
  1493  	m.gets.Add(uint64(1))
  1494  	return b, nil
  1495  }
  1496  
  1497  func (m *mockedPool) Put(b *[]byte) {
  1498  	m.balance.Sub(uint64(cap(*b)))
  1499  	m.parent.Put(b)
  1500  }
  1501  
  1502  // Regression test against: https://github.com/thanos-io/thanos/issues/2147.
  1503  func TestBucketSeries_OneBlock_InMemIndexCacheSegfault(t *testing.T) {
  1504  	tmpDir := t.TempDir()
  1505  
  1506  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  1507  	testutil.Ok(t, err)
  1508  	defer func() { testutil.Ok(t, bkt.Close()) }()
  1509  
  1510  	logger := log.NewLogfmtLogger(os.Stderr)
  1511  	thanosMeta := metadata.Thanos{
  1512  		Labels:     labels.Labels{{Name: "ext1", Value: "1"}}.Map(),
  1513  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  1514  		Source:     metadata.TestSource,
  1515  	}
  1516  
  1517  	chunkPool, err := pool.NewBucketedBytes(chunkBytesPoolMinSize, chunkBytesPoolMaxSize, 2, 100e7)
  1518  	testutil.Ok(t, err)
  1519  
  1520  	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{
  1521  		MaxItemSize: 3000,
  1522  		// This is the exact size of cache needed for our *single request*.
  1523  		// This is limited in order to make sure we test evictions.
  1524  		MaxSize: 8889,
  1525  	})
  1526  	testutil.Ok(t, err)
  1527  
  1528  	var b1 *bucketBlock
  1529  
  1530  	const numSeries = 100
  1531  	headOpts := tsdb.DefaultHeadOptions()
  1532  	headOpts.ChunkDirRoot = tmpDir
  1533  	headOpts.ChunkRange = 1
  1534  
  1535  	// Create 4 blocks. Each will have numSeriesPerBlock number of series that have 1 sample only.
  1536  	// Timestamp will be counted for each new series, so each series will have unique timestamp.
  1537  	// This allows to pick time range that will correspond to number of series picked 1:1.
  1538  	{
  1539  		// Block 1.
  1540  		h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  1541  		testutil.Ok(t, err)
  1542  		defer func() { testutil.Ok(t, h.Close()) }()
  1543  
  1544  		app := h.Appender(context.Background())
  1545  
  1546  		for i := 0; i < numSeries; i++ {
  1547  			ts := int64(i)
  1548  			lbls := labels.FromStrings("foo", "bar", "b", "1", "i", fmt.Sprintf("%07d%s", ts, storetestutil.LabelLongSuffix))
  1549  
  1550  			_, err := app.Append(0, lbls, ts, 0)
  1551  			testutil.Ok(t, err)
  1552  		}
  1553  		testutil.Ok(t, app.Commit())
  1554  
  1555  		blockDir := filepath.Join(tmpDir, "tmp")
  1556  		id := createBlockFromHead(t, blockDir, h)
  1557  
  1558  		meta, err := metadata.InjectThanos(log.NewNopLogger(), filepath.Join(blockDir, id.String()), thanosMeta, nil)
  1559  		testutil.Ok(t, err)
  1560  		testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(blockDir, id.String()), metadata.NoneFunc))
  1561  
  1562  		b1 = &bucketBlock{
  1563  			indexCache:             indexCache,
  1564  			logger:                 logger,
  1565  			metrics:                newBucketStoreMetrics(nil),
  1566  			bkt:                    bkt,
  1567  			meta:                   meta,
  1568  			partitioner:            NewGapBasedPartitioner(PartitionerMaxGapSize),
  1569  			chunkObjs:              []string{filepath.Join(id.String(), "chunks", "000001")},
  1570  			chunkPool:              chunkPool,
  1571  			estimatedMaxSeriesSize: EstimatedMaxSeriesSize,
  1572  			estimatedMaxChunkSize:  EstimatedMaxChunkSize,
  1573  		}
  1574  		b1.indexHeaderReader, err = indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, b1.meta.ULID, DefaultPostingOffsetInMemorySampling)
  1575  		testutil.Ok(t, err)
  1576  	}
  1577  
  1578  	var b2 *bucketBlock
  1579  	{
  1580  		// Block 2, do not load this block yet.
  1581  		h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  1582  		testutil.Ok(t, err)
  1583  		defer func() { testutil.Ok(t, h.Close()) }()
  1584  
  1585  		app := h.Appender(context.Background())
  1586  
  1587  		for i := 0; i < numSeries; i++ {
  1588  			ts := int64(i)
  1589  			lbls := labels.FromStrings("foo", "bar", "b", "2", "i", fmt.Sprintf("%07d%s", ts, storetestutil.LabelLongSuffix))
  1590  
  1591  			_, err := app.Append(0, lbls, ts, 0)
  1592  			testutil.Ok(t, err)
  1593  		}
  1594  		testutil.Ok(t, app.Commit())
  1595  
  1596  		blockDir := filepath.Join(tmpDir, "tmp2")
  1597  		id := createBlockFromHead(t, blockDir, h)
  1598  
  1599  		meta, err := metadata.InjectThanos(log.NewNopLogger(), filepath.Join(blockDir, id.String()), thanosMeta, nil)
  1600  		testutil.Ok(t, err)
  1601  		testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(blockDir, id.String()), metadata.NoneFunc))
  1602  
  1603  		b2 = &bucketBlock{
  1604  			indexCache:             indexCache,
  1605  			logger:                 logger,
  1606  			metrics:                newBucketStoreMetrics(nil),
  1607  			bkt:                    bkt,
  1608  			meta:                   meta,
  1609  			partitioner:            NewGapBasedPartitioner(PartitionerMaxGapSize),
  1610  			chunkObjs:              []string{filepath.Join(id.String(), "chunks", "000001")},
  1611  			chunkPool:              chunkPool,
  1612  			estimatedMaxSeriesSize: EstimatedMaxSeriesSize,
  1613  			estimatedMaxChunkSize:  EstimatedMaxChunkSize,
  1614  		}
  1615  		b2.indexHeaderReader, err = indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, b2.meta.ULID, DefaultPostingOffsetInMemorySampling)
  1616  		testutil.Ok(t, err)
  1617  	}
  1618  
  1619  	store := &BucketStore{
  1620  		bkt:             objstore.WithNoopInstr(bkt),
  1621  		logger:          logger,
  1622  		indexCache:      indexCache,
  1623  		indexReaderPool: indexheader.NewReaderPool(log.NewNopLogger(), false, 0, indexheader.NewReaderPoolMetrics(nil)),
  1624  		metrics:         newBucketStoreMetrics(nil),
  1625  		blockSets: map[uint64]*bucketBlockSet{
  1626  			labels.Labels{{Name: "ext1", Value: "1"}}.Hash(): {blocks: [][]*bucketBlock{{b1, b2}}},
  1627  		},
  1628  		blocks: map[ulid.ULID]*bucketBlock{
  1629  			b1.meta.ULID: b1,
  1630  			b2.meta.ULID: b2,
  1631  		},
  1632  		queryGate:            gate.NewNoop(),
  1633  		chunksLimiterFactory: NewChunksLimiterFactory(0),
  1634  		seriesLimiterFactory: NewSeriesLimiterFactory(0),
  1635  		bytesLimiterFactory:  NewBytesLimiterFactory(0),
  1636  	}
  1637  
  1638  	t.Run("invoke series for one block. Fill the cache on the way.", func(t *testing.T) {
  1639  		srv := newStoreSeriesServer(context.Background())
  1640  		testutil.Ok(t, store.Series(&storepb.SeriesRequest{
  1641  			MinTime: 0,
  1642  			MaxTime: int64(numSeries) - 1,
  1643  			Matchers: []storepb.LabelMatcher{
  1644  				{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1645  				{Type: storepb.LabelMatcher_EQ, Name: "b", Value: "1"},
  1646  				// This bug shows only when we use lot's of symbols for matching.
  1647  				{Type: storepb.LabelMatcher_NEQ, Name: "i", Value: ""},
  1648  			},
  1649  		}, srv))
  1650  		testutil.Equals(t, 0, len(srv.Warnings))
  1651  		testutil.Equals(t, numSeries, len(srv.SeriesSet))
  1652  	})
  1653  	t.Run("invoke series for second block. This should revoke previous cache.", func(t *testing.T) {
  1654  		srv := newStoreSeriesServer(context.Background())
  1655  		testutil.Ok(t, store.Series(&storepb.SeriesRequest{
  1656  			MinTime: 0,
  1657  			MaxTime: int64(numSeries) - 1,
  1658  			Matchers: []storepb.LabelMatcher{
  1659  				{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1660  				{Type: storepb.LabelMatcher_EQ, Name: "b", Value: "2"},
  1661  				// This bug shows only when we use lot's of symbols for matching.
  1662  				{Type: storepb.LabelMatcher_NEQ, Name: "i", Value: ""},
  1663  			},
  1664  		}, srv))
  1665  		testutil.Equals(t, 0, len(srv.Warnings))
  1666  		testutil.Equals(t, numSeries, len(srv.SeriesSet))
  1667  	})
  1668  	t.Run("remove second block. Cache stays. Ask for first again.", func(t *testing.T) {
  1669  		testutil.Ok(t, store.removeBlock(b2.meta.ULID))
  1670  
  1671  		srv := newStoreSeriesServer(context.Background())
  1672  		testutil.Ok(t, store.Series(&storepb.SeriesRequest{
  1673  			MinTime: 0,
  1674  			MaxTime: int64(numSeries) - 1,
  1675  			Matchers: []storepb.LabelMatcher{
  1676  				{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1677  				{Type: storepb.LabelMatcher_EQ, Name: "b", Value: "1"},
  1678  				// This bug shows only when we use lot's of symbols for matching.
  1679  				{Type: storepb.LabelMatcher_NEQ, Name: "i", Value: ""},
  1680  			},
  1681  		}, srv))
  1682  		testutil.Equals(t, 0, len(srv.Warnings))
  1683  		testutil.Equals(t, numSeries, len(srv.SeriesSet))
  1684  	})
  1685  }
  1686  
  1687  func TestSeries_RequestAndResponseHints(t *testing.T) {
  1688  	tb, store, seriesSet1, seriesSet2, block1, block2, close := setupStoreForHintsTest(t)
  1689  	defer close()
  1690  
  1691  	testCases := []*storetestutil.SeriesCase{
  1692  		{
  1693  			Name: "querying a range containing 1 block should return 1 block in the response hints",
  1694  			Req: &storepb.SeriesRequest{
  1695  				MinTime: 0,
  1696  				MaxTime: 1,
  1697  				Matchers: []storepb.LabelMatcher{
  1698  					{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1699  				},
  1700  			},
  1701  			ExpectedSeries: seriesSet1,
  1702  			ExpectedHints: []hintspb.SeriesResponseHints{
  1703  				{
  1704  					QueriedBlocks: []hintspb.Block{
  1705  						{Id: block1.String()},
  1706  					},
  1707  				},
  1708  			},
  1709  		},
  1710  		{
  1711  			Name: "querying a range containing multiple blocks should return multiple blocks in the response hints",
  1712  			Req: &storepb.SeriesRequest{
  1713  				MinTime: 0,
  1714  				MaxTime: 3,
  1715  				Matchers: []storepb.LabelMatcher{
  1716  					{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1717  				},
  1718  			},
  1719  			ExpectedSeries: append(append([]*storepb.Series{}, seriesSet1...), seriesSet2...),
  1720  			ExpectedHints: []hintspb.SeriesResponseHints{
  1721  				{
  1722  					QueriedBlocks: []hintspb.Block{
  1723  						{Id: block1.String()},
  1724  						{Id: block2.String()},
  1725  					},
  1726  				},
  1727  			},
  1728  		},
  1729  		{
  1730  			Name: "querying a range containing multiple blocks but filtering a specific block should query only the requested block",
  1731  			Req: &storepb.SeriesRequest{
  1732  				MinTime: 0,
  1733  				MaxTime: 3,
  1734  				Matchers: []storepb.LabelMatcher{
  1735  					{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1736  				},
  1737  				Hints: mustMarshalAny(&hintspb.SeriesRequestHints{
  1738  					BlockMatchers: []storepb.LabelMatcher{
  1739  						{Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()},
  1740  					},
  1741  				}),
  1742  			},
  1743  			ExpectedSeries: seriesSet1,
  1744  			ExpectedHints: []hintspb.SeriesResponseHints{
  1745  				{
  1746  					QueriedBlocks: []hintspb.Block{
  1747  						{Id: block1.String()},
  1748  					},
  1749  				},
  1750  			},
  1751  		},
  1752  		{
  1753  			Name: "Query Stats Enabled",
  1754  			Req: &storepb.SeriesRequest{
  1755  				MinTime: 0,
  1756  				MaxTime: 3,
  1757  				Matchers: []storepb.LabelMatcher{
  1758  					{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1759  				},
  1760  				Hints: mustMarshalAny(&hintspb.SeriesRequestHints{
  1761  					BlockMatchers: []storepb.LabelMatcher{
  1762  						{Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()},
  1763  					},
  1764  					EnableQueryStats: true,
  1765  				}),
  1766  			},
  1767  			ExpectedSeries: seriesSet1,
  1768  			ExpectedHints: []hintspb.SeriesResponseHints{
  1769  				{
  1770  					QueriedBlocks: []hintspb.Block{
  1771  						{Id: block1.String()},
  1772  					},
  1773  					QueryStats: &hintspb.QueryStats{
  1774  						BlocksQueried:     1,
  1775  						PostingsTouched:   1,
  1776  						PostingsFetched:   1,
  1777  						SeriesTouched:     2,
  1778  						SeriesFetched:     2,
  1779  						ChunksTouched:     2,
  1780  						ChunksFetched:     2,
  1781  						MergedSeriesCount: 2,
  1782  						MergedChunksCount: 2,
  1783  					},
  1784  				},
  1785  			},
  1786  			HintsCompareFunc: func(t testutil.TB, expected, actual hintspb.SeriesResponseHints) {
  1787  				testutil.Equals(t, expected.QueriedBlocks, actual.QueriedBlocks)
  1788  				testutil.Equals(t, expected.QueryStats.BlocksQueried, actual.QueryStats.BlocksQueried)
  1789  				testutil.Equals(t, expected.QueryStats.PostingsTouched, actual.QueryStats.PostingsTouched)
  1790  				testutil.Equals(t, expected.QueryStats.PostingsFetched, actual.QueryStats.PostingsFetched)
  1791  				testutil.Equals(t, expected.QueryStats.SeriesTouched, actual.QueryStats.SeriesTouched)
  1792  				testutil.Equals(t, expected.QueryStats.SeriesFetched, actual.QueryStats.SeriesFetched)
  1793  				testutil.Equals(t, expected.QueryStats.ChunksTouched, actual.QueryStats.ChunksTouched)
  1794  				testutil.Equals(t, expected.QueryStats.ChunksFetched, actual.QueryStats.ChunksFetched)
  1795  				testutil.Equals(t, expected.QueryStats.MergedSeriesCount, actual.QueryStats.MergedSeriesCount)
  1796  				testutil.Equals(t, expected.QueryStats.MergedChunksCount, actual.QueryStats.MergedChunksCount)
  1797  			},
  1798  		},
  1799  	}
  1800  
  1801  	storetestutil.TestServerSeries(tb, store, testCases...)
  1802  }
  1803  
  1804  func TestSeries_ErrorUnmarshallingRequestHints(t *testing.T) {
  1805  	tb := testutil.NewTB(t)
  1806  
  1807  	tmpDir := t.TempDir()
  1808  
  1809  	bktDir := filepath.Join(tmpDir, "bkt")
  1810  	bkt, err := filesystem.NewBucket(bktDir)
  1811  	testutil.Ok(t, err)
  1812  	defer func() { testutil.Ok(t, bkt.Close()) }()
  1813  
  1814  	var (
  1815  		logger   = log.NewNopLogger()
  1816  		instrBkt = objstore.WithNoopInstr(bkt)
  1817  	)
  1818  
  1819  	// Instance a real bucket store we'll use to query the series.
  1820  	fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil)
  1821  	testutil.Ok(tb, err)
  1822  
  1823  	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{})
  1824  	testutil.Ok(tb, err)
  1825  
  1826  	store, err := NewBucketStore(
  1827  		instrBkt,
  1828  		fetcher,
  1829  		tmpDir,
  1830  		NewChunksLimiterFactory(10000/MaxSamplesPerChunk),
  1831  		NewSeriesLimiterFactory(0),
  1832  		NewBytesLimiterFactory(0),
  1833  		NewGapBasedPartitioner(PartitionerMaxGapSize),
  1834  		10,
  1835  		false,
  1836  		DefaultPostingOffsetInMemorySampling,
  1837  		true,
  1838  		false,
  1839  		0,
  1840  		WithLogger(logger),
  1841  		WithIndexCache(indexCache),
  1842  	)
  1843  	testutil.Ok(tb, err)
  1844  	defer func() { testutil.Ok(t, store.Close()) }()
  1845  
  1846  	testutil.Ok(tb, store.SyncBlocks(context.Background()))
  1847  
  1848  	// Create a request with invalid hints (uses response hints instead of request hints).
  1849  	req := &storepb.SeriesRequest{
  1850  		MinTime: 0,
  1851  		MaxTime: 3,
  1852  		Matchers: []storepb.LabelMatcher{
  1853  			{Type: storepb.LabelMatcher_EQ, Name: "foo", Value: "bar"},
  1854  		},
  1855  		Hints: mustMarshalAny(&hintspb.SeriesResponseHints{}),
  1856  	}
  1857  
  1858  	srv := newStoreSeriesServer(context.Background())
  1859  	err = store.Series(req, srv)
  1860  	testutil.NotOk(t, err)
  1861  	testutil.Equals(t, true, regexp.MustCompile(".*unmarshal series request hints.*").MatchString(err.Error()))
  1862  }
  1863  
  1864  func TestSeries_BlockWithMultipleChunks(t *testing.T) {
  1865  	tb := testutil.NewTB(t)
  1866  
  1867  	tmpDir := t.TempDir()
  1868  
  1869  	// Create a block with 1 series but an high number of samples,
  1870  	// so that they will span across multiple chunks.
  1871  	headOpts := tsdb.DefaultHeadOptions()
  1872  	headOpts.ChunkDirRoot = filepath.Join(tmpDir, "block")
  1873  	headOpts.ChunkRange = 10000000000
  1874  
  1875  	h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  1876  	testutil.Ok(t, err)
  1877  	defer func() { testutil.Ok(t, h.Close()) }()
  1878  
  1879  	series := labels.FromStrings("__name__", "test")
  1880  	for ts := int64(0); ts < 10000; ts++ {
  1881  		// Appending a single sample is very unoptimised, but guarantees each chunk is always MaxSamplesPerChunk
  1882  		// (except the last one, which could be smaller).
  1883  		app := h.Appender(context.Background())
  1884  		_, err := app.Append(0, series, ts, float64(ts))
  1885  		testutil.Ok(t, err)
  1886  		testutil.Ok(t, app.Commit())
  1887  	}
  1888  
  1889  	blk := createBlockFromHead(t, headOpts.ChunkDirRoot, h)
  1890  
  1891  	thanosMeta := metadata.Thanos{
  1892  		Labels:     labels.Labels{{Name: "ext1", Value: "1"}}.Map(),
  1893  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  1894  		Source:     metadata.TestSource,
  1895  	}
  1896  
  1897  	_, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(headOpts.ChunkDirRoot, blk.String()), thanosMeta, nil)
  1898  	testutil.Ok(t, err)
  1899  
  1900  	// Create a bucket and upload the block there.
  1901  	bktDir := filepath.Join(tmpDir, "bucket")
  1902  	bkt, err := filesystem.NewBucket(bktDir)
  1903  	testutil.Ok(t, err)
  1904  	defer func() { testutil.Ok(t, bkt.Close()) }()
  1905  
  1906  	instrBkt := objstore.WithNoopInstr(bkt)
  1907  	logger := log.NewNopLogger()
  1908  	testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(headOpts.ChunkDirRoot, blk.String()), metadata.NoneFunc))
  1909  
  1910  	// Instance a real bucket store we'll use to query the series.
  1911  	fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil)
  1912  	testutil.Ok(tb, err)
  1913  
  1914  	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{})
  1915  	testutil.Ok(tb, err)
  1916  
  1917  	store, err := NewBucketStore(
  1918  		instrBkt,
  1919  		fetcher,
  1920  		tmpDir,
  1921  		NewChunksLimiterFactory(100000/MaxSamplesPerChunk),
  1922  		NewSeriesLimiterFactory(0),
  1923  		NewBytesLimiterFactory(0),
  1924  		NewGapBasedPartitioner(PartitionerMaxGapSize),
  1925  		10,
  1926  		false,
  1927  		DefaultPostingOffsetInMemorySampling,
  1928  		true,
  1929  		false,
  1930  		0,
  1931  		WithLogger(logger),
  1932  		WithIndexCache(indexCache),
  1933  	)
  1934  	testutil.Ok(tb, err)
  1935  	testutil.Ok(tb, store.SyncBlocks(context.Background()))
  1936  
  1937  	tests := map[string]struct {
  1938  		reqMinTime      int64
  1939  		reqMaxTime      int64
  1940  		expectedSamples int
  1941  	}{
  1942  		"query the entire block": {
  1943  			reqMinTime:      math.MinInt64,
  1944  			reqMaxTime:      math.MaxInt64,
  1945  			expectedSamples: 10000,
  1946  		},
  1947  		"query the beginning of the block": {
  1948  			reqMinTime:      0,
  1949  			reqMaxTime:      100,
  1950  			expectedSamples: MaxSamplesPerChunk,
  1951  		},
  1952  		"query the middle of the block": {
  1953  			reqMinTime:      4000,
  1954  			reqMaxTime:      4050,
  1955  			expectedSamples: MaxSamplesPerChunk,
  1956  		},
  1957  		"query the end of the block": {
  1958  			reqMinTime:      9800,
  1959  			reqMaxTime:      10000,
  1960  			expectedSamples: (MaxSamplesPerChunk * 2) + (10000 % MaxSamplesPerChunk),
  1961  		},
  1962  	}
  1963  
  1964  	for testName, testData := range tests {
  1965  		t.Run(testName, func(t *testing.T) {
  1966  			req := &storepb.SeriesRequest{
  1967  				MinTime: testData.reqMinTime,
  1968  				MaxTime: testData.reqMaxTime,
  1969  				Matchers: []storepb.LabelMatcher{
  1970  					{Type: storepb.LabelMatcher_EQ, Name: "__name__", Value: "test"},
  1971  				},
  1972  			}
  1973  
  1974  			srv := newStoreSeriesServer(context.Background())
  1975  			err = store.Series(req, srv)
  1976  			testutil.Ok(t, err)
  1977  			testutil.Assert(t, len(srv.SeriesSet) == 1)
  1978  
  1979  			// Count the number of samples in the returned chunks.
  1980  			numSamples := 0
  1981  			for _, rawChunk := range srv.SeriesSet[0].Chunks {
  1982  				decodedChunk, err := chunkenc.FromData(chunkenc.EncXOR, rawChunk.Raw.Data)
  1983  				testutil.Ok(t, err)
  1984  
  1985  				numSamples += decodedChunk.NumSamples()
  1986  			}
  1987  
  1988  			testutil.Assert(t, testData.expectedSamples == numSamples, "expected: %d, actual: %d", testData.expectedSamples, numSamples)
  1989  		})
  1990  	}
  1991  }
  1992  
  1993  func TestSeries_SeriesSortedWithoutReplicaLabels(t *testing.T) {
  1994  	tests := map[string]struct {
  1995  		series         [][]labels.Labels
  1996  		replicaLabels  []string
  1997  		expectedSeries []labels.Labels
  1998  	}{
  1999  		"use TSDB label as replica label": {
  2000  			series: [][]labels.Labels{
  2001  				{
  2002  					labels.FromStrings("a", "1", "replica", "1", "z", "1"),
  2003  					labels.FromStrings("a", "1", "replica", "1", "z", "2"),
  2004  					labels.FromStrings("a", "1", "replica", "2", "z", "1"),
  2005  					labels.FromStrings("a", "1", "replica", "2", "z", "2"),
  2006  					labels.FromStrings("a", "2", "replica", "1", "z", "1"),
  2007  					labels.FromStrings("a", "2", "replica", "2", "z", "1"),
  2008  				},
  2009  				{
  2010  					labels.FromStrings("a", "1", "replica", "3", "z", "1"),
  2011  					labels.FromStrings("a", "1", "replica", "3", "z", "2"),
  2012  					labels.FromStrings("a", "2", "replica", "3", "z", "1"),
  2013  				},
  2014  			},
  2015  			replicaLabels: []string{"replica"},
  2016  			expectedSeries: []labels.Labels{
  2017  				labels.FromStrings("a", "1", "ext1", "0", "z", "1"),
  2018  				labels.FromStrings("a", "1", "ext1", "0", "z", "2"),
  2019  				labels.FromStrings("a", "1", "ext1", "1", "z", "1"),
  2020  				labels.FromStrings("a", "1", "ext1", "1", "z", "2"),
  2021  				labels.FromStrings("a", "2", "ext1", "0", "z", "1"),
  2022  				labels.FromStrings("a", "2", "ext1", "1", "z", "1"),
  2023  			},
  2024  		},
  2025  		"use external label as replica label": {
  2026  			series: [][]labels.Labels{
  2027  				{
  2028  					labels.FromStrings("a", "1", "replica", "1", "z", "1"),
  2029  					labels.FromStrings("a", "1", "replica", "1", "z", "2"),
  2030  					labels.FromStrings("a", "1", "replica", "2", "z", "1"),
  2031  					labels.FromStrings("a", "1", "replica", "2", "z", "2"),
  2032  				},
  2033  				{
  2034  					labels.FromStrings("a", "1", "replica", "1", "z", "1"),
  2035  					labels.FromStrings("a", "1", "replica", "1", "z", "2"),
  2036  				},
  2037  			},
  2038  			replicaLabels: []string{"ext1"},
  2039  			expectedSeries: []labels.Labels{
  2040  				labels.FromStrings("a", "1", "replica", "1", "z", "1"),
  2041  				labels.FromStrings("a", "1", "replica", "1", "z", "2"),
  2042  				labels.FromStrings("a", "1", "replica", "2", "z", "1"),
  2043  				labels.FromStrings("a", "1", "replica", "2", "z", "2"),
  2044  			},
  2045  		},
  2046  	}
  2047  
  2048  	for testName, testData := range tests {
  2049  		t.Run(testName, func(t *testing.T) {
  2050  			tb := testutil.NewTB(t)
  2051  
  2052  			tmpDir := t.TempDir()
  2053  
  2054  			bktDir := filepath.Join(tmpDir, "bucket")
  2055  			bkt, err := filesystem.NewBucket(bktDir)
  2056  			testutil.Ok(t, err)
  2057  			defer testutil.Ok(t, bkt.Close())
  2058  
  2059  			instrBkt := objstore.WithNoopInstr(bkt)
  2060  			logger := log.NewNopLogger()
  2061  
  2062  			for i, series := range testData.series {
  2063  				replicaVal := strconv.Itoa(i)
  2064  				head := uploadSeriesToBucket(t, bkt, replicaVal, filepath.Join(tmpDir, replicaVal), series)
  2065  				defer testutil.Ok(t, head.Close())
  2066  			}
  2067  
  2068  			// Instance a real bucket store we'll use to query the series.
  2069  			fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil)
  2070  			testutil.Ok(tb, err)
  2071  
  2072  			indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{})
  2073  			testutil.Ok(tb, err)
  2074  
  2075  			store, err := NewBucketStore(
  2076  				instrBkt,
  2077  				fetcher,
  2078  				tmpDir,
  2079  				NewChunksLimiterFactory(100000/MaxSamplesPerChunk),
  2080  				NewSeriesLimiterFactory(0),
  2081  				NewBytesLimiterFactory(0),
  2082  				NewGapBasedPartitioner(PartitionerMaxGapSize),
  2083  				10,
  2084  				false,
  2085  				DefaultPostingOffsetInMemorySampling,
  2086  				true,
  2087  				false,
  2088  				0,
  2089  				WithLogger(logger),
  2090  				WithIndexCache(indexCache),
  2091  			)
  2092  			testutil.Ok(tb, err)
  2093  			testutil.Ok(tb, store.SyncBlocks(context.Background()))
  2094  
  2095  			req := &storepb.SeriesRequest{
  2096  				MinTime: math.MinInt,
  2097  				MaxTime: math.MaxInt64,
  2098  				Matchers: []storepb.LabelMatcher{
  2099  					{Type: storepb.LabelMatcher_RE, Name: "a", Value: ".+"},
  2100  				},
  2101  				WithoutReplicaLabels: testData.replicaLabels,
  2102  			}
  2103  
  2104  			srv := newStoreSeriesServer(context.Background())
  2105  			err = store.Series(req, srv)
  2106  			testutil.Ok(t, err)
  2107  			testutil.Assert(t, len(srv.SeriesSet) == len(testData.expectedSeries))
  2108  
  2109  			var response []labels.Labels
  2110  			for _, respSeries := range srv.SeriesSet {
  2111  				promLabels := labelpb.ZLabelsToPromLabels(respSeries.Labels)
  2112  				response = append(response, promLabels)
  2113  			}
  2114  
  2115  			testutil.Equals(t, testData.expectedSeries, response)
  2116  		})
  2117  	}
  2118  }
  2119  
  2120  func uploadSeriesToBucket(t *testing.T, bkt *filesystem.Bucket, replica string, path string, series []labels.Labels) *tsdb.Head {
  2121  	headOpts := tsdb.DefaultHeadOptions()
  2122  	headOpts.ChunkDirRoot = filepath.Join(path, "block")
  2123  
  2124  	h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  2125  	testutil.Ok(t, err)
  2126  
  2127  	for _, s := range series {
  2128  		for ts := int64(0); ts < 100; ts++ {
  2129  			// Appending a single sample is very unoptimised, but guarantees each chunk is always MaxSamplesPerChunk
  2130  			// (except the last one, which could be smaller).
  2131  			app := h.Appender(context.Background())
  2132  			_, err := app.Append(0, s, ts, float64(ts))
  2133  			testutil.Ok(t, err)
  2134  			testutil.Ok(t, app.Commit())
  2135  		}
  2136  	}
  2137  
  2138  	blk := storetestutil.CreateBlockFromHead(t, headOpts.ChunkDirRoot, h)
  2139  
  2140  	thanosMeta := metadata.Thanos{
  2141  		Labels:     labels.Labels{{Name: "ext1", Value: replica}}.Map(),
  2142  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  2143  		Source:     metadata.TestSource,
  2144  	}
  2145  
  2146  	_, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(headOpts.ChunkDirRoot, blk.String()), thanosMeta, nil)
  2147  	testutil.Ok(t, err)
  2148  
  2149  	testutil.Ok(t, block.Upload(context.Background(), log.NewNopLogger(), bkt, filepath.Join(headOpts.ChunkDirRoot, blk.String()), metadata.NoneFunc))
  2150  	testutil.Ok(t, err)
  2151  
  2152  	return h
  2153  }
  2154  
  2155  func mustMarshalAny(pb proto.Message) *types.Any {
  2156  	out, err := types.MarshalAny(pb)
  2157  	if err != nil {
  2158  		panic(err)
  2159  	}
  2160  	return out
  2161  }
  2162  
  2163  func TestBigEndianPostingsCount(t *testing.T) {
  2164  	const count = 1000
  2165  	raw := make([]byte, count*4)
  2166  
  2167  	for ix := 0; ix < count; ix++ {
  2168  		binary.BigEndian.PutUint32(raw[4*ix:], rand.Uint32())
  2169  	}
  2170  
  2171  	p := newBigEndianPostings(raw)
  2172  	testutil.Equals(t, count, p.length())
  2173  
  2174  	c := 0
  2175  	for p.Next() {
  2176  		c++
  2177  	}
  2178  	testutil.Equals(t, count, c)
  2179  }
  2180  
  2181  func createBlockWithOneSeriesWithStep(t testutil.TB, dir string, lbls labels.Labels, blockIndex, totalSamples int, random *rand.Rand, step int64) ulid.ULID {
  2182  	headOpts := tsdb.DefaultHeadOptions()
  2183  	headOpts.ChunkDirRoot = dir
  2184  	headOpts.ChunkRange = int64(totalSamples) * step
  2185  	h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  2186  	testutil.Ok(t, err)
  2187  	defer func() { testutil.Ok(t, h.Close()) }()
  2188  
  2189  	app := h.Appender(context.Background())
  2190  
  2191  	ts := int64(blockIndex * totalSamples)
  2192  	ref, err := app.Append(0, lbls, ts, random.Float64())
  2193  	testutil.Ok(t, err)
  2194  	for i := 1; i < totalSamples; i++ {
  2195  		_, err := app.Append(ref, nil, ts+step*int64(i), random.Float64())
  2196  		testutil.Ok(t, err)
  2197  	}
  2198  	testutil.Ok(t, app.Commit())
  2199  
  2200  	return createBlockFromHead(t, dir, h)
  2201  }
  2202  
  2203  func setupStoreForHintsTest(t *testing.T) (testutil.TB, *BucketStore, []*storepb.Series, []*storepb.Series, ulid.ULID, ulid.ULID, func()) {
  2204  	tb := testutil.NewTB(t)
  2205  
  2206  	closers := []func(){}
  2207  
  2208  	tmpDir := t.TempDir()
  2209  
  2210  	bktDir := filepath.Join(tmpDir, "bkt")
  2211  	bkt, err := filesystem.NewBucket(bktDir)
  2212  	testutil.Ok(t, err)
  2213  	closers = append(closers, func() { testutil.Ok(t, bkt.Close()) })
  2214  
  2215  	var (
  2216  		logger   = log.NewNopLogger()
  2217  		instrBkt = objstore.WithNoopInstr(bkt)
  2218  		random   = rand.New(rand.NewSource(120))
  2219  	)
  2220  
  2221  	extLset := labels.Labels{{Name: "ext1", Value: "1"}}
  2222  	// Inject the Thanos meta to each block in the storage.
  2223  	thanosMeta := metadata.Thanos{
  2224  		Labels:     extLset.Map(),
  2225  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  2226  		Source:     metadata.TestSource,
  2227  	}
  2228  
  2229  	// Create TSDB blocks.
  2230  	head, seriesSet1 := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{
  2231  		TSDBDir:          filepath.Join(tmpDir, "0"),
  2232  		SamplesPerSeries: 1,
  2233  		Series:           2,
  2234  		PrependLabels:    extLset,
  2235  		Random:           random,
  2236  	})
  2237  	block1 := createBlockFromHead(t, bktDir, head)
  2238  	testutil.Ok(t, head.Close())
  2239  	head2, seriesSet2 := storetestutil.CreateHeadWithSeries(t, 1, storetestutil.HeadGenOptions{
  2240  		TSDBDir:          filepath.Join(tmpDir, "1"),
  2241  		SamplesPerSeries: 1,
  2242  		Series:           2,
  2243  		PrependLabels:    extLset,
  2244  		Random:           random,
  2245  	})
  2246  	block2 := createBlockFromHead(t, bktDir, head2)
  2247  	testutil.Ok(t, head2.Close())
  2248  
  2249  	for _, blockID := range []ulid.ULID{block1, block2} {
  2250  		_, err := metadata.InjectThanos(logger, filepath.Join(bktDir, blockID.String()), thanosMeta, nil)
  2251  		testutil.Ok(t, err)
  2252  	}
  2253  
  2254  	// Instance a real bucket store we'll use to query back the series.
  2255  	fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil)
  2256  	testutil.Ok(tb, err)
  2257  
  2258  	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{})
  2259  	testutil.Ok(tb, err)
  2260  
  2261  	store, err := NewBucketStore(
  2262  		instrBkt,
  2263  		fetcher,
  2264  		tmpDir,
  2265  		NewChunksLimiterFactory(10000/MaxSamplesPerChunk),
  2266  		NewSeriesLimiterFactory(0),
  2267  		NewBytesLimiterFactory(0),
  2268  		NewGapBasedPartitioner(PartitionerMaxGapSize),
  2269  		10,
  2270  		false,
  2271  		DefaultPostingOffsetInMemorySampling,
  2272  		true,
  2273  		false,
  2274  		0,
  2275  		WithLogger(logger),
  2276  		WithIndexCache(indexCache),
  2277  	)
  2278  	testutil.Ok(tb, err)
  2279  	testutil.Ok(tb, store.SyncBlocks(context.Background()))
  2280  
  2281  	closers = append(closers, func() { testutil.Ok(t, store.Close()) })
  2282  
  2283  	return tb, store, seriesSet1, seriesSet2, block1, block2, func() {
  2284  		for _, close := range closers {
  2285  			close()
  2286  		}
  2287  	}
  2288  }
  2289  
  2290  func TestLabelNamesAndValuesHints(t *testing.T) {
  2291  	_, store, seriesSet1, seriesSet2, block1, block2, close := setupStoreForHintsTest(t)
  2292  	defer close()
  2293  
  2294  	type labelNamesValuesCase struct {
  2295  		name string
  2296  
  2297  		labelNamesReq      *storepb.LabelNamesRequest
  2298  		expectedNames      []string
  2299  		expectedNamesHints hintspb.LabelNamesResponseHints
  2300  
  2301  		labelValuesReq      *storepb.LabelValuesRequest
  2302  		expectedValues      []string
  2303  		expectedValuesHints hintspb.LabelValuesResponseHints
  2304  	}
  2305  
  2306  	testCases := []labelNamesValuesCase{
  2307  		{
  2308  			name: "querying a range containing 1 block should return 1 block in the labels hints",
  2309  
  2310  			labelNamesReq: &storepb.LabelNamesRequest{
  2311  				Start: 0,
  2312  				End:   1,
  2313  			},
  2314  			expectedNames: labelNamesFromSeriesSet(seriesSet1),
  2315  			expectedNamesHints: hintspb.LabelNamesResponseHints{
  2316  				QueriedBlocks: []hintspb.Block{
  2317  					{Id: block1.String()},
  2318  				},
  2319  			},
  2320  
  2321  			labelValuesReq: &storepb.LabelValuesRequest{
  2322  				Label: "ext1",
  2323  				Start: 0,
  2324  				End:   1,
  2325  			},
  2326  			expectedValues: []string{"1"},
  2327  			expectedValuesHints: hintspb.LabelValuesResponseHints{
  2328  				QueriedBlocks: []hintspb.Block{
  2329  					{Id: block1.String()},
  2330  				},
  2331  			},
  2332  		},
  2333  		{
  2334  			name: "querying a range containing multiple blocks should return multiple blocks in the response hints",
  2335  
  2336  			labelNamesReq: &storepb.LabelNamesRequest{
  2337  				Start: 0,
  2338  				End:   3,
  2339  			},
  2340  			expectedNames: labelNamesFromSeriesSet(
  2341  				append(append([]*storepb.Series{}, seriesSet1...), seriesSet2...),
  2342  			),
  2343  			expectedNamesHints: hintspb.LabelNamesResponseHints{
  2344  				QueriedBlocks: []hintspb.Block{
  2345  					{Id: block1.String()},
  2346  					{Id: block2.String()},
  2347  				},
  2348  			},
  2349  
  2350  			labelValuesReq: &storepb.LabelValuesRequest{
  2351  				Label: "ext1",
  2352  				Start: 0,
  2353  				End:   3,
  2354  			},
  2355  			expectedValues: []string{"1"},
  2356  			expectedValuesHints: hintspb.LabelValuesResponseHints{
  2357  				QueriedBlocks: []hintspb.Block{
  2358  					{Id: block1.String()},
  2359  					{Id: block2.String()},
  2360  				},
  2361  			},
  2362  		}, {
  2363  			name: "querying a range containing multiple blocks but filtering a specific block should query only the requested block",
  2364  
  2365  			labelNamesReq: &storepb.LabelNamesRequest{
  2366  				Start: 0,
  2367  				End:   3,
  2368  				Hints: mustMarshalAny(&hintspb.LabelNamesRequestHints{
  2369  					BlockMatchers: []storepb.LabelMatcher{
  2370  						{Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()},
  2371  					},
  2372  				}),
  2373  			},
  2374  			expectedNames: labelNamesFromSeriesSet(seriesSet1),
  2375  			expectedNamesHints: hintspb.LabelNamesResponseHints{
  2376  				QueriedBlocks: []hintspb.Block{
  2377  					{Id: block1.String()},
  2378  				},
  2379  			},
  2380  
  2381  			labelValuesReq: &storepb.LabelValuesRequest{
  2382  				Label: "ext1",
  2383  				Start: 0,
  2384  				End:   3,
  2385  				Hints: mustMarshalAny(&hintspb.LabelValuesRequestHints{
  2386  					BlockMatchers: []storepb.LabelMatcher{
  2387  						{Type: storepb.LabelMatcher_EQ, Name: block.BlockIDLabel, Value: block1.String()},
  2388  					},
  2389  				}),
  2390  			},
  2391  			expectedValues: []string{"1"},
  2392  			expectedValuesHints: hintspb.LabelValuesResponseHints{
  2393  				QueriedBlocks: []hintspb.Block{
  2394  					{Id: block1.String()},
  2395  				},
  2396  			},
  2397  		},
  2398  	}
  2399  
  2400  	for _, tc := range testCases {
  2401  		t.Run(tc.name, func(t *testing.T) {
  2402  			namesResp, err := store.LabelNames(context.Background(), tc.labelNamesReq)
  2403  			testutil.Ok(t, err)
  2404  			testutil.Equals(t, tc.expectedNames, namesResp.Names)
  2405  
  2406  			var namesHints hintspb.LabelNamesResponseHints
  2407  			testutil.Ok(t, types.UnmarshalAny(namesResp.Hints, &namesHints))
  2408  			// The order is not determinate, so we are sorting them.
  2409  			sort.Slice(namesHints.QueriedBlocks, func(i, j int) bool {
  2410  				return namesHints.QueriedBlocks[i].Id < namesHints.QueriedBlocks[j].Id
  2411  			})
  2412  			testutil.Equals(t, tc.expectedNamesHints, namesHints)
  2413  
  2414  			valuesResp, err := store.LabelValues(context.Background(), tc.labelValuesReq)
  2415  			testutil.Ok(t, err)
  2416  			testutil.Equals(t, tc.expectedValues, valuesResp.Values)
  2417  
  2418  			var valuesHints hintspb.LabelValuesResponseHints
  2419  			testutil.Ok(t, types.UnmarshalAny(valuesResp.Hints, &valuesHints))
  2420  			// The order is not determinate, so we are sorting them.
  2421  			sort.Slice(valuesHints.QueriedBlocks, func(i, j int) bool {
  2422  				return valuesHints.QueriedBlocks[i].Id < valuesHints.QueriedBlocks[j].Id
  2423  			})
  2424  			testutil.Equals(t, tc.expectedValuesHints, valuesHints)
  2425  		})
  2426  	}
  2427  }
  2428  
  2429  func TestSeries_ChunksHaveHashRepresentation(t *testing.T) {
  2430  	tb := testutil.NewTB(t)
  2431  
  2432  	tmpDir := t.TempDir()
  2433  
  2434  	headOpts := tsdb.DefaultHeadOptions()
  2435  	headOpts.ChunkDirRoot = filepath.Join(tmpDir, "block")
  2436  
  2437  	h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  2438  	testutil.Ok(t, err)
  2439  	defer func() { testutil.Ok(t, h.Close()) }()
  2440  
  2441  	series := labels.FromStrings("__name__", "test")
  2442  	app := h.Appender(context.Background())
  2443  	for ts := int64(0); ts < 10_000; ts++ {
  2444  		_, err := app.Append(0, series, ts, float64(ts))
  2445  		testutil.Ok(t, err)
  2446  	}
  2447  	testutil.Ok(t, app.Commit())
  2448  
  2449  	blk := createBlockFromHead(t, headOpts.ChunkDirRoot, h)
  2450  
  2451  	thanosMeta := metadata.Thanos{
  2452  		Labels:     labels.Labels{{Name: "ext1", Value: "1"}}.Map(),
  2453  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  2454  		Source:     metadata.TestSource,
  2455  	}
  2456  
  2457  	_, err = metadata.InjectThanos(log.NewNopLogger(), filepath.Join(headOpts.ChunkDirRoot, blk.String()), thanosMeta, nil)
  2458  	testutil.Ok(t, err)
  2459  
  2460  	// Create a bucket and upload the block there.
  2461  	bktDir := filepath.Join(tmpDir, "bucket")
  2462  	bkt, err := filesystem.NewBucket(bktDir)
  2463  	testutil.Ok(t, err)
  2464  	defer func() { testutil.Ok(t, bkt.Close()) }()
  2465  
  2466  	instrBkt := objstore.WithNoopInstr(bkt)
  2467  	logger := log.NewNopLogger()
  2468  	testutil.Ok(t, block.Upload(context.Background(), logger, bkt, filepath.Join(headOpts.ChunkDirRoot, blk.String()), metadata.NoneFunc))
  2469  
  2470  	// Instance a real bucket store we'll use to query the series.
  2471  	fetcher, err := block.NewMetaFetcher(logger, 10, instrBkt, tmpDir, nil, nil)
  2472  	testutil.Ok(tb, err)
  2473  
  2474  	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.InMemoryIndexCacheConfig{})
  2475  	testutil.Ok(tb, err)
  2476  
  2477  	store, err := NewBucketStore(
  2478  		instrBkt,
  2479  		fetcher,
  2480  		tmpDir,
  2481  		NewChunksLimiterFactory(100000/MaxSamplesPerChunk),
  2482  		NewSeriesLimiterFactory(0),
  2483  		NewBytesLimiterFactory(0),
  2484  		NewGapBasedPartitioner(PartitionerMaxGapSize),
  2485  		10,
  2486  		false,
  2487  		DefaultPostingOffsetInMemorySampling,
  2488  		true,
  2489  		false,
  2490  		0,
  2491  		WithLogger(logger),
  2492  		WithIndexCache(indexCache),
  2493  	)
  2494  	testutil.Ok(tb, err)
  2495  	testutil.Ok(tb, store.SyncBlocks(context.Background()))
  2496  
  2497  	reqMinTime := math.MinInt64
  2498  	reqMaxTime := math.MaxInt64
  2499  
  2500  	testCases := []struct {
  2501  		name              string
  2502  		calculateChecksum bool
  2503  	}{
  2504  		{
  2505  			name:              "calculate checksum",
  2506  			calculateChecksum: true,
  2507  		},
  2508  	}
  2509  
  2510  	for _, tc := range testCases {
  2511  		t.Run(tc.name, func(t *testing.T) {
  2512  			req := &storepb.SeriesRequest{
  2513  				MinTime: int64(reqMinTime),
  2514  				MaxTime: int64(reqMaxTime),
  2515  				Matchers: []storepb.LabelMatcher{
  2516  					{Type: storepb.LabelMatcher_EQ, Name: "__name__", Value: "test"},
  2517  				},
  2518  			}
  2519  
  2520  			srv := newStoreSeriesServer(context.Background())
  2521  			err = store.Series(req, srv)
  2522  			testutil.Ok(t, err)
  2523  			testutil.Assert(t, len(srv.SeriesSet) == 1)
  2524  
  2525  			for _, rawChunk := range srv.SeriesSet[0].Chunks {
  2526  				hash := rawChunk.Raw.Hash
  2527  				decodedChunk, err := chunkenc.FromData(chunkenc.EncXOR, rawChunk.Raw.Data)
  2528  				testutil.Ok(t, err)
  2529  
  2530  				if tc.calculateChecksum {
  2531  					expectedHash := xxhash.Sum64(decodedChunk.Bytes())
  2532  					testutil.Equals(t, expectedHash, hash)
  2533  				} else {
  2534  					testutil.Equals(t, uint64(0), hash)
  2535  				}
  2536  			}
  2537  		})
  2538  	}
  2539  }
  2540  
  2541  func labelNamesFromSeriesSet(series []*storepb.Series) []string {
  2542  	labelsMap := map[string]struct{}{}
  2543  
  2544  	for _, s := range series {
  2545  		for _, label := range s.Labels {
  2546  			labelsMap[label.Name] = struct{}{}
  2547  		}
  2548  	}
  2549  
  2550  	labels := make([]string, 0, len(labelsMap))
  2551  	for k := range labelsMap {
  2552  		labels = append(labels, k)
  2553  	}
  2554  
  2555  	sort.Strings(labels)
  2556  	return labels
  2557  }
  2558  
  2559  func BenchmarkBucketBlock_readChunkRange(b *testing.B) {
  2560  	var (
  2561  		ctx    = context.Background()
  2562  		logger = log.NewNopLogger()
  2563  
  2564  		// Read chunks of different length. We're not using random to make the benchmark repeatable.
  2565  		readLengths = []int64{300, 500, 1000, 5000, 10000, 30000, 50000, 100000, 300000, 1500000}
  2566  	)
  2567  
  2568  	tmpDir := b.TempDir()
  2569  
  2570  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  2571  	testutil.Ok(b, err)
  2572  	b.Cleanup(func() {
  2573  		testutil.Ok(b, bkt.Close())
  2574  	})
  2575  
  2576  	// Create a block.
  2577  	blockID := createBlockWithOneSeriesWithStep(testutil.NewTB(b), tmpDir, labels.FromStrings("__name__", "test"), 0, 100000, rand.New(rand.NewSource(0)), 5000)
  2578  
  2579  	// Upload the block to the bucket.
  2580  	thanosMeta := metadata.Thanos{
  2581  		Labels:     labels.Labels{{Name: "ext1", Value: "1"}}.Map(),
  2582  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  2583  		Source:     metadata.TestSource,
  2584  	}
  2585  
  2586  	blockMeta, err := metadata.InjectThanos(logger, filepath.Join(tmpDir, blockID.String()), thanosMeta, nil)
  2587  	testutil.Ok(b, err)
  2588  
  2589  	testutil.Ok(b, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
  2590  
  2591  	// Create a chunk pool with buckets between 8B and 32KB.
  2592  	chunkPool, err := pool.NewBucketedBytes(8, 32*1024, 2, 1e10)
  2593  	testutil.Ok(b, err)
  2594  
  2595  	// Create a bucket block with only the dependencies we need for the benchmark.
  2596  	blk, err := newBucketBlock(context.Background(), logger, newBucketStoreMetrics(nil), blockMeta, bkt, tmpDir, nil, chunkPool, nil, nil, nil, nil)
  2597  	testutil.Ok(b, err)
  2598  
  2599  	b.ResetTimer()
  2600  
  2601  	for n := 0; n < b.N; n++ {
  2602  		offset := int64(0)
  2603  		length := readLengths[n%len(readLengths)]
  2604  
  2605  		_, err := blk.readChunkRange(ctx, 0, offset, length, byteRanges{{offset: 0, length: int(length)}})
  2606  		if err != nil {
  2607  			b.Fatal(err.Error())
  2608  		}
  2609  	}
  2610  }
  2611  
  2612  func BenchmarkBlockSeries(b *testing.B) {
  2613  	blk, blockMeta := prepareBucket(b, compact.ResolutionLevelRaw)
  2614  
  2615  	aggrs := []storepb.Aggr{storepb.Aggr_RAW}
  2616  	for _, concurrency := range []int{1, 2, 4, 8, 16, 32} {
  2617  		b.Run(fmt.Sprintf("concurrency: %d", concurrency), func(b *testing.B) {
  2618  			benchmarkBlockSeriesWithConcurrency(b, concurrency, blockMeta, blk, aggrs)
  2619  		})
  2620  	}
  2621  }
  2622  
  2623  func prepareBucket(b *testing.B, resolutionLevel compact.ResolutionLevel) (*bucketBlock, *metadata.Meta) {
  2624  	var (
  2625  		ctx    = context.Background()
  2626  		logger = log.NewNopLogger()
  2627  	)
  2628  
  2629  	tmpDir := b.TempDir()
  2630  
  2631  	bkt, err := filesystem.NewBucket(filepath.Join(tmpDir, "bkt"))
  2632  	testutil.Ok(b, err)
  2633  	b.Cleanup(func() {
  2634  		testutil.Ok(b, bkt.Close())
  2635  	})
  2636  
  2637  	// Create a block.
  2638  	head, _ := storetestutil.CreateHeadWithSeries(b, 0, storetestutil.HeadGenOptions{
  2639  		TSDBDir:          filepath.Join(tmpDir, "head"),
  2640  		SamplesPerSeries: 86400 / 15, // Simulate 1 day block with 15s scrape interval.
  2641  		ScrapeInterval:   15 * time.Second,
  2642  		Series:           1000,
  2643  		PrependLabels:    nil,
  2644  		Random:           rand.New(rand.NewSource(120)),
  2645  		SkipChunks:       true,
  2646  	})
  2647  	blockID := createBlockFromHead(b, tmpDir, head)
  2648  
  2649  	// Upload the block to the bucket.
  2650  	thanosMeta := metadata.Thanos{
  2651  		Labels:     labels.Labels{{Name: "ext1", Value: "1"}}.Map(),
  2652  		Downsample: metadata.ThanosDownsample{Resolution: 0},
  2653  		Source:     metadata.TestSource,
  2654  	}
  2655  
  2656  	blockMeta, err := metadata.InjectThanos(logger, filepath.Join(tmpDir, blockID.String()), thanosMeta, nil)
  2657  	testutil.Ok(b, err)
  2658  
  2659  	testutil.Ok(b, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
  2660  
  2661  	if resolutionLevel > 0 {
  2662  		// Downsample newly-created block.
  2663  		blockID, err = downsample.Downsample(logger, blockMeta, head, tmpDir, int64(resolutionLevel))
  2664  		testutil.Ok(b, err)
  2665  		blockMeta, err = metadata.ReadFromDir(filepath.Join(tmpDir, blockID.String()))
  2666  		testutil.Ok(b, err)
  2667  
  2668  		testutil.Ok(b, block.Upload(context.Background(), logger, bkt, filepath.Join(tmpDir, blockID.String()), metadata.NoneFunc))
  2669  	}
  2670  	testutil.Ok(b, head.Close())
  2671  
  2672  	// Create chunk pool and partitioner using the same production settings.
  2673  	chunkPool, err := NewDefaultChunkBytesPool(64 * 1024 * 1024 * 1024)
  2674  	testutil.Ok(b, err)
  2675  
  2676  	partitioner := NewGapBasedPartitioner(PartitionerMaxGapSize)
  2677  
  2678  	// Create an index header reader.
  2679  	indexHeaderReader, err := indexheader.NewBinaryReader(ctx, logger, bkt, tmpDir, blockMeta.ULID, DefaultPostingOffsetInMemorySampling)
  2680  	testutil.Ok(b, err)
  2681  	indexCache, err := storecache.NewInMemoryIndexCacheWithConfig(logger, nil, nil, storecache.DefaultInMemoryIndexCacheConfig)
  2682  	testutil.Ok(b, err)
  2683  
  2684  	// Create a bucket block with only the dependencies we need for the benchmark.
  2685  	blk, err := newBucketBlock(context.Background(), logger, newBucketStoreMetrics(nil), blockMeta, bkt, tmpDir, indexCache, chunkPool, indexHeaderReader, partitioner, nil, nil)
  2686  	testutil.Ok(b, err)
  2687  	return blk, blockMeta
  2688  }
  2689  
  2690  func benchmarkBlockSeriesWithConcurrency(b *testing.B, concurrency int, blockMeta *metadata.Meta, blk *bucketBlock, aggrs []storepb.Aggr) {
  2691  	// Run the same number of queries per goroutine.
  2692  	queriesPerWorker := b.N / concurrency
  2693  
  2694  	// No limits.
  2695  	chunksLimiter := NewChunksLimiterFactory(0)(nil)
  2696  	seriesLimiter := NewSeriesLimiterFactory(0)(nil)
  2697  	ctx := context.Background()
  2698  
  2699  	// Run multiple workers to execute the queries.
  2700  	wg := sync.WaitGroup{}
  2701  	wg.Add(concurrency)
  2702  
  2703  	for w := 0; w < concurrency; w++ {
  2704  		go func() {
  2705  			defer wg.Done()
  2706  
  2707  			for n := 0; n < queriesPerWorker; n++ {
  2708  				// Each query touches a subset of series. To make it reproducible and make sure
  2709  				// we just don't query consecutive series (as is in the real world), we do create
  2710  				// a label matcher which looks for a short integer within the label value.
  2711  				labelMatcher := fmt.Sprintf(".*%d.*", n%20)
  2712  
  2713  				req := &storepb.SeriesRequest{
  2714  					MinTime: blockMeta.MinTime,
  2715  					MaxTime: blockMeta.MaxTime,
  2716  					Matchers: []storepb.LabelMatcher{
  2717  						{Type: storepb.LabelMatcher_RE, Name: "i", Value: labelMatcher},
  2718  					},
  2719  					SkipChunks: false,
  2720  					Aggregates: aggrs,
  2721  				}
  2722  
  2723  				matchers, err := storepb.MatchersToPromMatchers(req.Matchers...)
  2724  				// TODO FIXME! testutil.Ok calls b.Fatalf under the hood, which
  2725  				// must be called only from the goroutine running the Benchmark function.
  2726  				testutil.Ok(b, err)
  2727  				sortedMatchers := newSortedMatchers(matchers)
  2728  
  2729  				dummyHistogram := prometheus.NewHistogram(prometheus.HistogramOpts{})
  2730  				blockClient := newBlockSeriesClient(
  2731  					ctx,
  2732  					nil,
  2733  					blk,
  2734  					req,
  2735  					chunksLimiter,
  2736  					NewBytesLimiterFactory(0)(nil),
  2737  					nil,
  2738  					false,
  2739  					SeriesBatchSize,
  2740  					dummyHistogram,
  2741  					nil,
  2742  				)
  2743  				testutil.Ok(b, blockClient.ExpandPostings(sortedMatchers, seriesLimiter))
  2744  				defer blockClient.Close()
  2745  
  2746  				// Ensure at least 1 series has been returned (as expected).
  2747  				_, err = blockClient.Recv()
  2748  				testutil.Ok(b, err)
  2749  			}
  2750  		}()
  2751  	}
  2752  
  2753  	wg.Wait()
  2754  }
  2755  
  2756  func BenchmarkDownsampledBlockSeries(b *testing.B) {
  2757  	blk, blockMeta := prepareBucket(b, compact.ResolutionLevel5m)
  2758  	aggrs := []storepb.Aggr{}
  2759  	for i := 1; i < int(storepb.Aggr_COUNTER); i++ {
  2760  		aggrs = append(aggrs, storepb.Aggr(i))
  2761  		for _, concurrency := range []int{1, 2, 4, 8, 16, 32} {
  2762  			b.Run(fmt.Sprintf("aggregates: %v, concurrency: %d", aggrs, concurrency), func(b *testing.B) {
  2763  				benchmarkBlockSeriesWithConcurrency(b, concurrency, blockMeta, blk, aggrs)
  2764  			})
  2765  		}
  2766  	}
  2767  }
  2768  
  2769  func TestExpandPostingsWithContextCancel(t *testing.T) {
  2770  	p := index.NewListPostings([]storage.SeriesRef{1, 2, 3, 4, 5, 6, 7, 8})
  2771  	ctx, cancel := context.WithCancel(context.Background())
  2772  
  2773  	cancel()
  2774  	res, err := ExpandPostingsWithContext(ctx, p)
  2775  	testutil.NotOk(t, err)
  2776  	testutil.Equals(t, context.Canceled, err)
  2777  	testutil.Equals(t, []storage.SeriesRef(nil), res)
  2778  }
  2779  
  2780  func TestMatchersToPostingGroup(t *testing.T) {
  2781  	ctx := context.Background()
  2782  	for _, tc := range []struct {
  2783  		name        string
  2784  		matchers    []*labels.Matcher
  2785  		labelValues map[string][]string
  2786  		expected    []*postingGroup
  2787  	}{
  2788  		{
  2789  			name: "single equal matcher",
  2790  			matchers: []*labels.Matcher{
  2791  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2792  			},
  2793  			labelValues: map[string][]string{
  2794  				"foo": {"bar", "baz"},
  2795  			},
  2796  			expected: []*postingGroup{
  2797  				{
  2798  					name:    "foo",
  2799  					addAll:  false,
  2800  					addKeys: []string{"bar"},
  2801  				},
  2802  			},
  2803  		},
  2804  		{
  2805  			name: "deduplicate two equal matchers",
  2806  			matchers: []*labels.Matcher{
  2807  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2808  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2809  			},
  2810  			labelValues: map[string][]string{
  2811  				"foo": {"bar", "baz"},
  2812  			},
  2813  			expected: []*postingGroup{
  2814  				{
  2815  					name:    "foo",
  2816  					addAll:  false,
  2817  					addKeys: []string{"bar"},
  2818  				},
  2819  			},
  2820  		},
  2821  		{
  2822  			name: "deduplicate multiple equal matchers",
  2823  			matchers: []*labels.Matcher{
  2824  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2825  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2826  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2827  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2828  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2829  			},
  2830  			labelValues: map[string][]string{
  2831  				"foo": {"bar", "baz"},
  2832  			},
  2833  			expected: []*postingGroup{
  2834  				{
  2835  					name:    "foo",
  2836  					addAll:  false,
  2837  					addKeys: []string{"bar"},
  2838  				},
  2839  			},
  2840  		},
  2841  		{
  2842  			name: "two equal matchers with different label name, merge",
  2843  			matchers: []*labels.Matcher{
  2844  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2845  				labels.MustNewMatcher(labels.MatchEqual, "bar", "baz"),
  2846  			},
  2847  			labelValues: map[string][]string{
  2848  				"foo": {"bar", "baz"},
  2849  				"bar": {"baz"},
  2850  			},
  2851  			expected: []*postingGroup{
  2852  				{
  2853  					name:    "bar",
  2854  					addAll:  false,
  2855  					addKeys: []string{"baz"},
  2856  				},
  2857  				{
  2858  					name:    "foo",
  2859  					addAll:  false,
  2860  					addKeys: []string{"bar"},
  2861  				},
  2862  			},
  2863  		},
  2864  		{
  2865  			name: "two different equal matchers with same label name, intersect",
  2866  			matchers: []*labels.Matcher{
  2867  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2868  				labels.MustNewMatcher(labels.MatchEqual, "foo", "baz"),
  2869  			},
  2870  			labelValues: map[string][]string{
  2871  				"foo": {"bar", "baz"},
  2872  			},
  2873  			expected: nil,
  2874  		},
  2875  		{
  2876  			name: "Intersect equal and unequal matcher, no hit",
  2877  			matchers: []*labels.Matcher{
  2878  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2879  				labels.MustNewMatcher(labels.MatchNotEqual, "foo", "bar"),
  2880  			},
  2881  			labelValues: map[string][]string{
  2882  				"foo": {"bar", "baz"},
  2883  			},
  2884  			expected: nil,
  2885  		},
  2886  		{
  2887  			name: "Intersect equal and unequal matcher, has result",
  2888  			matchers: []*labels.Matcher{
  2889  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2890  				labels.MustNewMatcher(labels.MatchNotEqual, "foo", "baz"),
  2891  			},
  2892  			labelValues: map[string][]string{
  2893  				"foo": {"bar", "baz"},
  2894  			},
  2895  			expected: []*postingGroup{
  2896  				{
  2897  					name:    "foo",
  2898  					addAll:  false,
  2899  					addKeys: []string{"bar"},
  2900  				},
  2901  			},
  2902  		},
  2903  		{
  2904  			name: "Intersect equal and regex matcher, no result",
  2905  			matchers: []*labels.Matcher{
  2906  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2907  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "x.*"),
  2908  			},
  2909  			labelValues: map[string][]string{
  2910  				"foo": {"bar", "baz"},
  2911  			},
  2912  		},
  2913  		{
  2914  			name: "Intersect equal and regex matcher, have result",
  2915  			matchers: []*labels.Matcher{
  2916  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2917  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "b.*"),
  2918  			},
  2919  			labelValues: map[string][]string{
  2920  				"foo": {"bar", "baz"},
  2921  			},
  2922  			expected: []*postingGroup{
  2923  				{
  2924  					name:    "foo",
  2925  					addAll:  false,
  2926  					addKeys: []string{"bar"},
  2927  				},
  2928  			},
  2929  		},
  2930  		{
  2931  			name: "Intersect equal and unequal inverse matcher",
  2932  			matchers: []*labels.Matcher{
  2933  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2934  				labels.MustNewMatcher(labels.MatchNotEqual, "foo", ""),
  2935  			},
  2936  			labelValues: map[string][]string{
  2937  				"foo": {"bar", "baz"},
  2938  			},
  2939  			expected: []*postingGroup{
  2940  				{
  2941  					name:    "foo",
  2942  					addAll:  false,
  2943  					addKeys: []string{"bar"},
  2944  				},
  2945  			},
  2946  		},
  2947  		{
  2948  			name: "Intersect equal and regexp matching non empty matcher",
  2949  			matchers: []*labels.Matcher{
  2950  				labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  2951  				labels.MustNewMatcher(labels.MatchRegexp, "foo", ".+"),
  2952  			},
  2953  			labelValues: map[string][]string{
  2954  				"foo": {"bar", "baz"},
  2955  			},
  2956  			expected: []*postingGroup{
  2957  				{
  2958  					name:    "foo",
  2959  					addAll:  false,
  2960  					addKeys: []string{"bar"},
  2961  				},
  2962  			},
  2963  		},
  2964  		{
  2965  			name: "Intersect two regex matchers",
  2966  			matchers: []*labels.Matcher{
  2967  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar|baz"),
  2968  				labels.MustNewMatcher(labels.MatchRegexp, "foo", "bar|buzz"),
  2969  			},
  2970  			labelValues: map[string][]string{
  2971  				"foo": {"bar", "baz", "buzz"},
  2972  			},
  2973  			expected: []*postingGroup{
  2974  				{
  2975  					name:    "foo",
  2976  					addAll:  false,
  2977  					addKeys: []string{"bar"},
  2978  				},
  2979  			},
  2980  		},
  2981  		{
  2982  			name: "Merge inverse matchers",
  2983  			matchers: []*labels.Matcher{
  2984  				labels.MustNewMatcher(labels.MatchNotEqual, "foo", "bar"),
  2985  				labels.MustNewMatcher(labels.MatchNotEqual, "foo", "baz"),
  2986  			},
  2987  			labelValues: map[string][]string{
  2988  				"foo": {"buzz", "bar", "baz"},
  2989  			},
  2990  			expected: []*postingGroup{
  2991  				{
  2992  					name:       "foo",
  2993  					addAll:     true,
  2994  					removeKeys: []string{"bar", "baz"},
  2995  				},
  2996  			},
  2997  		},
  2998  		{
  2999  			name: "Dedup match all regex matchers",
  3000  			matchers: []*labels.Matcher{
  3001  				labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"),
  3002  				labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"),
  3003  				labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"),
  3004  				labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"),
  3005  				labels.MustNewMatcher(labels.MatchRegexp, labels.MetricName, ".*"),
  3006  			},
  3007  			labelValues: map[string][]string{
  3008  				labels.MetricName: {"up", "go_info"},
  3009  			},
  3010  			expected: []*postingGroup{
  3011  				{
  3012  					name:   labels.MetricName,
  3013  					addAll: true,
  3014  				},
  3015  			},
  3016  		},
  3017  		{
  3018  			name: "Multiple posting groups",
  3019  			matchers: []*labels.Matcher{
  3020  				labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, "up"),
  3021  				labels.MustNewMatcher(labels.MatchRegexp, "job", ".*"),
  3022  				labels.MustNewMatcher(labels.MatchNotEqual, "cluster", ""),
  3023  			},
  3024  			labelValues: map[string][]string{
  3025  				labels.MetricName: {"up", "go_info"},
  3026  				"cluster":         {"us-east-1", "us-west-2"},
  3027  				"job":             {"prometheus", "thanos"},
  3028  			},
  3029  			expected: []*postingGroup{
  3030  				{
  3031  					name:    labels.MetricName,
  3032  					addAll:  false,
  3033  					addKeys: []string{"up"},
  3034  				},
  3035  				{
  3036  					name:    "cluster",
  3037  					addAll:  false,
  3038  					addKeys: []string{"us-east-1", "us-west-2"},
  3039  				},
  3040  				{
  3041  					name:   "job",
  3042  					addAll: true,
  3043  				},
  3044  			},
  3045  		},
  3046  		{
  3047  			name: "Multiple unequal empty",
  3048  			matchers: []*labels.Matcher{
  3049  				labels.MustNewMatcher(labels.MatchNotEqual, labels.MetricName, ""),
  3050  				labels.MustNewMatcher(labels.MatchNotEqual, labels.MetricName, ""),
  3051  				labels.MustNewMatcher(labels.MatchNotEqual, "job", ""),
  3052  				labels.MustNewMatcher(labels.MatchNotEqual, "job", ""),
  3053  				labels.MustNewMatcher(labels.MatchNotEqual, "cluster", ""),
  3054  				labels.MustNewMatcher(labels.MatchNotEqual, "cluster", ""),
  3055  			},
  3056  			labelValues: map[string][]string{
  3057  				labels.MetricName: {"up", "go_info"},
  3058  				"cluster":         {"us-east-1", "us-west-2"},
  3059  				"job":             {"prometheus", "thanos"},
  3060  			},
  3061  			expected: []*postingGroup{
  3062  				{
  3063  					name:    labels.MetricName,
  3064  					addAll:  false,
  3065  					addKeys: []string{"go_info", "up"},
  3066  				},
  3067  				{
  3068  					name:    "cluster",
  3069  					addAll:  false,
  3070  					addKeys: []string{"us-east-1", "us-west-2"},
  3071  				},
  3072  				{
  3073  					name:    "job",
  3074  					addAll:  false,
  3075  					addKeys: []string{"prometheus", "thanos"},
  3076  				},
  3077  			},
  3078  		},
  3079  	} {
  3080  		t.Run(tc.name, func(t *testing.T) {
  3081  			actual, err := matchersToPostingGroups(ctx, func(name string) ([]string, error) {
  3082  				sort.Strings(tc.labelValues[name])
  3083  				return tc.labelValues[name], nil
  3084  			}, tc.matchers)
  3085  			testutil.Ok(t, err)
  3086  			testutil.Equals(t, tc.expected, actual)
  3087  		})
  3088  	}
  3089  }
  3090  
  3091  func TestPostingGroupMerge(t *testing.T) {
  3092  	for _, tc := range []struct {
  3093  		name     string
  3094  		group1   *postingGroup
  3095  		group2   *postingGroup
  3096  		expected *postingGroup
  3097  	}{
  3098  		{
  3099  			name:     "empty group2",
  3100  			group1:   &postingGroup{},
  3101  			group2:   nil,
  3102  			expected: &postingGroup{},
  3103  		},
  3104  		{
  3105  			name:     "group different names, return group1",
  3106  			group1:   &postingGroup{name: "bar"},
  3107  			group2:   &postingGroup{name: "foo"},
  3108  			expected: nil,
  3109  		},
  3110  		{
  3111  			name:     "both same addKey",
  3112  			group1:   &postingGroup{addKeys: []string{"foo"}},
  3113  			group2:   &postingGroup{addKeys: []string{"foo"}},
  3114  			expected: &postingGroup{addKeys: []string{"foo"}},
  3115  		},
  3116  		{
  3117  			name:     "both same addKeys, but different order",
  3118  			group1:   &postingGroup{addKeys: []string{"foo", "bar"}},
  3119  			group2:   &postingGroup{addKeys: []string{"bar", "foo"}},
  3120  			expected: &postingGroup{addKeys: []string{"bar", "foo"}},
  3121  		},
  3122  		{
  3123  			name:     "different addKeys",
  3124  			group1:   &postingGroup{addKeys: []string{"foo"}},
  3125  			group2:   &postingGroup{addKeys: []string{"bar"}},
  3126  			expected: &postingGroup{addKeys: []string{}},
  3127  		},
  3128  		{
  3129  			name:     "intersect common add keys",
  3130  			group1:   &postingGroup{addKeys: []string{"foo", "bar"}},
  3131  			group2:   &postingGroup{addKeys: []string{"bar", "baz"}},
  3132  			expected: &postingGroup{addKeys: []string{"bar"}},
  3133  		},
  3134  		{
  3135  			name:     "both addAll, no remove keys",
  3136  			group1:   &postingGroup{addAll: true},
  3137  			group2:   &postingGroup{addAll: true},
  3138  			expected: &postingGroup{addAll: true},
  3139  		},
  3140  		{
  3141  			name:     "both addAll, one remove keys",
  3142  			group1:   &postingGroup{addAll: true},
  3143  			group2:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3144  			expected: &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3145  		},
  3146  		{
  3147  			name:     "both addAll, same remove keys",
  3148  			group1:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3149  			group2:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3150  			expected: &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3151  		},
  3152  		{
  3153  			name:     "both addAll, merge different remove keys",
  3154  			group1:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3155  			group2:   &postingGroup{addAll: true, removeKeys: []string{"bar"}},
  3156  			expected: &postingGroup{addAll: true, removeKeys: []string{"bar", "foo"}},
  3157  		},
  3158  		{
  3159  			name:     "both addAll, multiple remove keys",
  3160  			group1:   &postingGroup{addAll: true, removeKeys: []string{"foo", "zoo"}},
  3161  			group2:   &postingGroup{addAll: true, removeKeys: []string{"a", "bar"}},
  3162  			expected: &postingGroup{addAll: true, removeKeys: []string{"a", "bar", "foo", "zoo"}},
  3163  		},
  3164  		{
  3165  			name:     "both addAll, multiple remove keys 2",
  3166  			group1:   &postingGroup{addAll: true, removeKeys: []string{"a", "bar"}},
  3167  			group2:   &postingGroup{addAll: true, removeKeys: []string{"foo", "zoo"}},
  3168  			expected: &postingGroup{addAll: true, removeKeys: []string{"a", "bar", "foo", "zoo"}},
  3169  		},
  3170  		{
  3171  			name:     "one add and one remove, only keep addKeys",
  3172  			group1:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3173  			group2:   &postingGroup{addKeys: []string{""}},
  3174  			expected: &postingGroup{addKeys: []string{""}},
  3175  		},
  3176  		{
  3177  			name:     "one add and one remove, subtract common keys to add and remove",
  3178  			group1:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3179  			group2:   &postingGroup{addKeys: []string{"", "foo"}},
  3180  			expected: &postingGroup{addKeys: []string{""}},
  3181  		},
  3182  		{
  3183  			name:     "same add and remove key",
  3184  			group1:   &postingGroup{addAll: true, removeKeys: []string{"foo"}},
  3185  			group2:   &postingGroup{addKeys: []string{"foo"}},
  3186  			expected: &postingGroup{addKeys: []string{}},
  3187  		},
  3188  		{
  3189  			name:     "same add and remove key, multiple keys",
  3190  			group1:   &postingGroup{addAll: true, removeKeys: []string{"2"}},
  3191  			group2:   &postingGroup{addKeys: []string{"1", "2", "3", "4", "5", "6"}},
  3192  			expected: &postingGroup{addKeys: []string{"1", "3", "4", "5", "6"}},
  3193  		},
  3194  		{
  3195  			name:     "addAll and non addAll posting group merge with empty keys",
  3196  			group1:   &postingGroup{addAll: true, removeKeys: nil},
  3197  			group2:   &postingGroup{addKeys: nil},
  3198  			expected: &postingGroup{addKeys: nil},
  3199  		},
  3200  	} {
  3201  		t.Run(tc.name, func(t *testing.T) {
  3202  			if tc.group1 != nil {
  3203  				slices.Sort(tc.group1.addKeys)
  3204  				slices.Sort(tc.group1.removeKeys)
  3205  			}
  3206  			if tc.group2 != nil {
  3207  				slices.Sort(tc.group2.addKeys)
  3208  				slices.Sort(tc.group2.removeKeys)
  3209  			}
  3210  			res := tc.group1.merge(tc.group2)
  3211  			testutil.Equals(t, tc.expected, res)
  3212  		})
  3213  	}
  3214  }
  3215  
  3216  // TestExpandedPostings is a test whether there is a race between multiple ExpandPostings() calls.
  3217  func TestExpandedPostingsRace(t *testing.T) {
  3218  	const blockCount = 10
  3219  
  3220  	tmpDir := t.TempDir()
  3221  	t.Cleanup(func() {
  3222  		testutil.Ok(t, os.RemoveAll(tmpDir))
  3223  	})
  3224  
  3225  	bkt := objstore.NewInMemBucket()
  3226  	t.Cleanup(func() {
  3227  		testutil.Ok(t, bkt.Close())
  3228  	})
  3229  
  3230  	// Create a block.
  3231  	head, _ := storetestutil.CreateHeadWithSeries(t, 0, storetestutil.HeadGenOptions{
  3232  		TSDBDir:          filepath.Join(tmpDir, "head"),
  3233  		SamplesPerSeries: 10,
  3234  		ScrapeInterval:   15 * time.Second,
  3235  		Series:           1000,
  3236  		PrependLabels:    nil,
  3237  		Random:           rand.New(rand.NewSource(120)),
  3238  		SkipChunks:       true,
  3239  	})
  3240  	blockID := createBlockFromHead(t, tmpDir, head)
  3241  
  3242  	bucketBlocks := make([]*bucketBlock, 0, blockCount)
  3243  
  3244  	for i := 0; i < blockCount; i++ {
  3245  		ul := ulid.MustNew(uint64(i), rand.New(rand.NewSource(444)))
  3246  
  3247  		// Upload the block to the bucket.
  3248  		thanosMeta := metadata.Thanos{
  3249  			Labels:     labels.Labels{{Name: "ext1", Value: fmt.Sprintf("%d", i)}}.Map(),
  3250  			Downsample: metadata.ThanosDownsample{Resolution: 0},
  3251  			Source:     metadata.TestSource,
  3252  		}
  3253  		m, err := metadata.ReadFromDir(filepath.Join(tmpDir, blockID.String()))
  3254  		testutil.Ok(t, err)
  3255  
  3256  		m.Thanos = thanosMeta
  3257  		m.BlockMeta.ULID = ul
  3258  
  3259  		e2eutil.Copy(t, filepath.Join(tmpDir, blockID.String()), filepath.Join(tmpDir, ul.String()))
  3260  		testutil.Ok(t, m.WriteToDir(log.NewLogfmtLogger(os.Stderr), filepath.Join(tmpDir, ul.String())))
  3261  		testutil.Ok(t, err)
  3262  		testutil.Ok(t, block.Upload(context.Background(), log.NewLogfmtLogger(os.Stderr), bkt, filepath.Join(tmpDir, ul.String()), metadata.NoneFunc))
  3263  
  3264  		r, err := indexheader.NewBinaryReader(context.Background(), log.NewNopLogger(), bkt, tmpDir, ul, DefaultPostingOffsetInMemorySampling)
  3265  		testutil.Ok(t, err)
  3266  
  3267  		blk, err := newBucketBlock(
  3268  			context.Background(),
  3269  			log.NewLogfmtLogger(os.Stderr),
  3270  			newBucketStoreMetrics(nil),
  3271  			m,
  3272  			bkt,
  3273  			filepath.Join(tmpDir, ul.String()),
  3274  			noopCache{},
  3275  			nil,
  3276  			r,
  3277  			NewGapBasedPartitioner(PartitionerMaxGapSize),
  3278  			nil,
  3279  			nil,
  3280  		)
  3281  		testutil.Ok(t, err)
  3282  
  3283  		bucketBlocks = append(bucketBlocks, blk)
  3284  	}
  3285  
  3286  	tm, cancel := context.WithTimeout(context.Background(), 40*time.Second)
  3287  	t.Cleanup(cancel)
  3288  
  3289  	l := sync.Mutex{}
  3290  	previousRefs := make(map[int][]storage.SeriesRef)
  3291  
  3292  	for {
  3293  		if tm.Err() != nil {
  3294  			break
  3295  		}
  3296  
  3297  		m := []*labels.Matcher{
  3298  			labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  3299  			labels.MustNewMatcher(labels.MatchRegexp, "j", ".+"),
  3300  			labels.MustNewMatcher(labels.MatchRegexp, "i", ".+"),
  3301  			labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  3302  			labels.MustNewMatcher(labels.MatchRegexp, "j", ".+"),
  3303  			labels.MustNewMatcher(labels.MatchRegexp, "i", ".+"),
  3304  			labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"),
  3305  		}
  3306  
  3307  		wg := &sync.WaitGroup{}
  3308  		for i, bb := range bucketBlocks {
  3309  			wg.Add(1)
  3310  			i := i
  3311  			bb := bb
  3312  			go func(i int, bb *bucketBlock) {
  3313  				refs, err := bb.indexReader().ExpandedPostings(context.Background(), m, NewBytesLimiterFactory(0)(nil))
  3314  				testutil.Ok(t, err)
  3315  				defer wg.Done()
  3316  
  3317  				l.Lock()
  3318  				defer l.Unlock()
  3319  				if previousRefs[i] != nil {
  3320  					testutil.Equals(t, previousRefs[i], refs)
  3321  				} else {
  3322  					previousRefs[i] = refs
  3323  				}
  3324  			}(i, bb)
  3325  		}
  3326  		wg.Wait()
  3327  	}
  3328  }
  3329  
  3330  func TestBucketIndexReader_decodeCachedPostingsErrors(t *testing.T) {
  3331  	bir := bucketIndexReader{stats: &queryStats{}}
  3332  	t.Run("should return error on broken cached postings without snappy prefix", func(t *testing.T) {
  3333  		_, _, err := bir.decodeCachedPostings([]byte("foo"))
  3334  		testutil.NotOk(t, err)
  3335  	})
  3336  	t.Run("should return error on broken cached postings with snappy prefix", func(t *testing.T) {
  3337  		_, _, err := bir.decodeCachedPostings(append([]byte(codecHeaderSnappy), []byte("foo")...))
  3338  		testutil.NotOk(t, err)
  3339  	})
  3340  }
  3341  
  3342  func TestBucketStoreDedupOnBlockSeriesSet(t *testing.T) {
  3343  	logger := log.NewNopLogger()
  3344  	tmpDir := t.TempDir()
  3345  	bktDir := filepath.Join(tmpDir, "bkt")
  3346  	auxDir := filepath.Join(tmpDir, "aux")
  3347  	metaDir := filepath.Join(tmpDir, "meta")
  3348  	extLset := labels.FromStrings("region", "eu-west")
  3349  
  3350  	testutil.Ok(t, os.MkdirAll(metaDir, os.ModePerm))
  3351  	testutil.Ok(t, os.MkdirAll(auxDir, os.ModePerm))
  3352  
  3353  	bkt, err := filesystem.NewBucket(bktDir)
  3354  	testutil.Ok(t, err)
  3355  	t.Cleanup(func() { testutil.Ok(t, bkt.Close()) })
  3356  
  3357  	for i := 0; i < 2; i++ {
  3358  		headOpts := tsdb.DefaultHeadOptions()
  3359  		headOpts.ChunkDirRoot = tmpDir
  3360  		headOpts.ChunkRange = 1000
  3361  		h, err := tsdb.NewHead(nil, nil, nil, nil, headOpts, nil)
  3362  		testutil.Ok(t, err)
  3363  		t.Cleanup(func() { testutil.Ok(t, h.Close()) })
  3364  
  3365  		app := h.Appender(context.Background())
  3366  		_, err = app.Append(0, labels.FromStrings("replica", "a", "z", "1"), 0, 1)
  3367  		testutil.Ok(t, err)
  3368  		_, err = app.Append(0, labels.FromStrings("replica", "a", "z", "2"), 0, 1)
  3369  		testutil.Ok(t, err)
  3370  		_, err = app.Append(0, labels.FromStrings("replica", "b", "z", "1"), 0, 1)
  3371  		testutil.Ok(t, err)
  3372  		_, err = app.Append(0, labels.FromStrings("replica", "b", "z", "2"), 0, 1)
  3373  		testutil.Ok(t, err)
  3374  		testutil.Ok(t, app.Commit())
  3375  
  3376  		id := createBlockFromHead(t, auxDir, h)
  3377  
  3378  		auxBlockDir := filepath.Join(auxDir, id.String())
  3379  		_, err = metadata.InjectThanos(log.NewNopLogger(), auxBlockDir, metadata.Thanos{
  3380  			Labels:     extLset.Map(),
  3381  			Downsample: metadata.ThanosDownsample{Resolution: 0},
  3382  			Source:     metadata.TestSource,
  3383  		}, nil)
  3384  		testutil.Ok(t, err)
  3385  
  3386  		testutil.Ok(t, block.Upload(context.Background(), logger, bkt, auxBlockDir, metadata.NoneFunc))
  3387  		testutil.Ok(t, block.Upload(context.Background(), logger, bkt, auxBlockDir, metadata.NoneFunc))
  3388  	}
  3389  
  3390  	chunkPool, err := NewDefaultChunkBytesPool(2e5)
  3391  	testutil.Ok(t, err)
  3392  
  3393  	metaFetcher, err := block.NewMetaFetcher(logger, 20, objstore.WithNoopInstr(bkt), metaDir, nil, []block.MetadataFilter{
  3394  		block.NewTimePartitionMetaFilter(allowAllFilterConf.MinTime, allowAllFilterConf.MaxTime),
  3395  	})
  3396  	testutil.Ok(t, err)
  3397  
  3398  	bucketStore, err := NewBucketStore(
  3399  		objstore.WithNoopInstr(bkt),
  3400  		metaFetcher,
  3401  		"",
  3402  		NewChunksLimiterFactory(10e6),
  3403  		NewSeriesLimiterFactory(10e6),
  3404  		NewBytesLimiterFactory(10e6),
  3405  		NewGapBasedPartitioner(PartitionerMaxGapSize),
  3406  		20,
  3407  		true,
  3408  		DefaultPostingOffsetInMemorySampling,
  3409  		false,
  3410  		false,
  3411  		1*time.Minute,
  3412  		WithChunkPool(chunkPool),
  3413  		WithFilterConfig(allowAllFilterConf),
  3414  	)
  3415  	testutil.Ok(t, err)
  3416  	t.Cleanup(func() { testutil.Ok(t, bucketStore.Close()) })
  3417  
  3418  	testutil.Ok(t, bucketStore.SyncBlocks(context.Background()))
  3419  
  3420  	srv := newStoreSeriesServer(context.Background())
  3421  	testutil.Ok(t, bucketStore.Series(&storepb.SeriesRequest{
  3422  		WithoutReplicaLabels: []string{"replica"},
  3423  		MinTime:              timestamp.FromTime(minTime),
  3424  		MaxTime:              timestamp.FromTime(maxTime),
  3425  		Matchers: []storepb.LabelMatcher{
  3426  			{Type: storepb.LabelMatcher_NEQ, Name: "z", Value: ""},
  3427  		},
  3428  	}, srv))
  3429  
  3430  	testutil.Equals(t, true, slices.IsSortedFunc(srv.SeriesSet, func(x, y storepb.Series) bool {
  3431  		return labels.Compare(x.PromLabels(), y.PromLabels()) < 0
  3432  	}))
  3433  	testutil.Equals(t, 2, len(srv.SeriesSet))
  3434  }