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

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package store
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"math"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/go-kit/log"
    15  	"github.com/go-kit/log/level"
    16  	"github.com/opentracing/opentracing-go"
    17  	"github.com/pkg/errors"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	"github.com/prometheus/client_golang/prometheus/promauto"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  	"golang.org/x/sync/errgroup"
    22  	"google.golang.org/grpc"
    23  	"google.golang.org/grpc/codes"
    24  	"google.golang.org/grpc/metadata"
    25  	"google.golang.org/grpc/status"
    26  
    27  	"github.com/thanos-io/thanos/pkg/component"
    28  	"github.com/thanos-io/thanos/pkg/info/infopb"
    29  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    30  	"github.com/thanos-io/thanos/pkg/store/storepb"
    31  	"github.com/thanos-io/thanos/pkg/strutil"
    32  	"github.com/thanos-io/thanos/pkg/tenancy"
    33  	"github.com/thanos-io/thanos/pkg/tracing"
    34  )
    35  
    36  type ctxKey int
    37  
    38  // UninitializedTSDBTime is the TSDB start time of an uninitialized TSDB instance.
    39  const UninitializedTSDBTime = math.MaxInt64
    40  
    41  // StoreMatcherKey is the context key for the store's allow list.
    42  const StoreMatcherKey = ctxKey(0)
    43  
    44  // ErrorNoStoresMatched is returned if the query does not match any data.
    45  // This can happen with Query servers trees and external labels.
    46  var ErrorNoStoresMatched = errors.New("No StoreAPIs matched for this query")
    47  
    48  // Client holds meta information about a store.
    49  type Client interface {
    50  	// StoreClient to access the store.
    51  	storepb.StoreClient
    52  
    53  	// LabelSets that each apply to some data exposed by the backing store.
    54  	LabelSets() []labels.Labels
    55  
    56  	// TimeRange returns minimum and maximum time range of data in the store.
    57  	TimeRange() (mint int64, maxt int64)
    58  
    59  	// TSDBInfos returns metadata about each TSDB backed by the client.
    60  	TSDBInfos() []infopb.TSDBInfo
    61  
    62  	// SupportsSharding returns true if sharding is supported by the underlying store.
    63  	SupportsSharding() bool
    64  
    65  	// SupportsWithoutReplicaLabels returns true if trimming replica labels
    66  	// and sorted response is supported by the underlying store.
    67  	SupportsWithoutReplicaLabels() bool
    68  
    69  	// String returns the string representation of the store client.
    70  	String() string
    71  
    72  	// Addr returns address of the store client. If second parameter is true, the client
    73  	// represents a local client (server-as-client) and has no remote address.
    74  	Addr() (addr string, isLocalClient bool)
    75  }
    76  
    77  // ProxyStore implements the store API that proxies request to all given underlying stores.
    78  type ProxyStore struct {
    79  	logger         log.Logger
    80  	stores         func() []Client
    81  	component      component.StoreAPI
    82  	selectorLabels labels.Labels
    83  	buffers        sync.Pool
    84  
    85  	responseTimeout   time.Duration
    86  	metrics           *proxyStoreMetrics
    87  	retrievalStrategy RetrievalStrategy
    88  	debugLogging      bool
    89  }
    90  
    91  type proxyStoreMetrics struct {
    92  	emptyStreamResponses prometheus.Counter
    93  }
    94  
    95  func newProxyStoreMetrics(reg prometheus.Registerer) *proxyStoreMetrics {
    96  	var m proxyStoreMetrics
    97  
    98  	m.emptyStreamResponses = promauto.With(reg).NewCounter(prometheus.CounterOpts{
    99  		Name: "thanos_proxy_store_empty_stream_responses_total",
   100  		Help: "Total number of empty responses received.",
   101  	})
   102  
   103  	return &m
   104  }
   105  
   106  func RegisterStoreServer(storeSrv storepb.StoreServer, logger log.Logger) func(*grpc.Server) {
   107  	return func(s *grpc.Server) {
   108  		storepb.RegisterStoreServer(s, NewRecoverableStoreServer(logger, storeSrv))
   109  	}
   110  }
   111  
   112  // BucketStoreOption are functions that configure BucketStore.
   113  type ProxyStoreOption func(s *ProxyStore)
   114  
   115  // WithProxyStoreDebugLogging enables debug logging.
   116  func WithProxyStoreDebugLogging() ProxyStoreOption {
   117  	return func(s *ProxyStore) {
   118  		s.debugLogging = true
   119  	}
   120  }
   121  
   122  // NewProxyStore returns a new ProxyStore that uses the given clients that implements storeAPI to fan-in all series to the client.
   123  // Note that there is no deduplication support. Deduplication should be done on the highest level (just before PromQL).
   124  func NewProxyStore(
   125  	logger log.Logger,
   126  	reg prometheus.Registerer,
   127  	stores func() []Client,
   128  	component component.StoreAPI,
   129  	selectorLabels labels.Labels,
   130  	responseTimeout time.Duration,
   131  	retrievalStrategy RetrievalStrategy,
   132  	options ...ProxyStoreOption,
   133  ) *ProxyStore {
   134  	if logger == nil {
   135  		logger = log.NewNopLogger()
   136  	}
   137  
   138  	metrics := newProxyStoreMetrics(reg)
   139  	s := &ProxyStore{
   140  		logger:         logger,
   141  		stores:         stores,
   142  		component:      component,
   143  		selectorLabels: selectorLabels,
   144  		buffers: sync.Pool{New: func() interface{} {
   145  			b := make([]byte, 0, initialBufSize)
   146  			return &b
   147  		}},
   148  		responseTimeout:   responseTimeout,
   149  		metrics:           metrics,
   150  		retrievalStrategy: retrievalStrategy,
   151  	}
   152  
   153  	for _, option := range options {
   154  		option(s)
   155  	}
   156  
   157  	return s
   158  }
   159  
   160  // Info returns store information about the external labels this store have.
   161  func (s *ProxyStore) Info(_ context.Context, _ *storepb.InfoRequest) (*storepb.InfoResponse, error) {
   162  	res := &storepb.InfoResponse{
   163  		StoreType: s.component.ToProto(),
   164  		Labels:    labelpb.ZLabelsFromPromLabels(s.selectorLabels),
   165  	}
   166  
   167  	minTime := int64(math.MaxInt64)
   168  	maxTime := int64(0)
   169  	stores := s.stores()
   170  
   171  	// Edge case: we have no data if there are no stores.
   172  	if len(stores) == 0 {
   173  		res.MaxTime = 0
   174  		res.MinTime = 0
   175  
   176  		return res, nil
   177  	}
   178  
   179  	for _, s := range stores {
   180  		mint, maxt := s.TimeRange()
   181  		if mint < minTime {
   182  			minTime = mint
   183  		}
   184  		if maxt > maxTime {
   185  			maxTime = maxt
   186  		}
   187  	}
   188  
   189  	res.MaxTime = maxTime
   190  	res.MinTime = minTime
   191  
   192  	labelSets := make(map[uint64]labelpb.ZLabelSet, len(stores))
   193  	for _, st := range stores {
   194  		for _, lset := range st.LabelSets() {
   195  			mergedLabelSet := labelpb.ExtendSortedLabels(lset, s.selectorLabels)
   196  			labelSets[mergedLabelSet.Hash()] = labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(mergedLabelSet)}
   197  		}
   198  	}
   199  
   200  	res.LabelSets = make([]labelpb.ZLabelSet, 0, len(labelSets))
   201  	for _, v := range labelSets {
   202  		res.LabelSets = append(res.LabelSets, v)
   203  	}
   204  
   205  	// We always want to enforce announcing the subset of data that
   206  	// selector-labels represents. If no label-sets are announced by the
   207  	// store-proxy's discovered stores, then we still want to enforce
   208  	// announcing this subset by announcing the selector as the label-set.
   209  	if len(res.LabelSets) == 0 && len(res.Labels) > 0 {
   210  		res.LabelSets = append(res.LabelSets, labelpb.ZLabelSet{Labels: res.Labels})
   211  	}
   212  
   213  	return res, nil
   214  }
   215  
   216  func (s *ProxyStore) LabelSet() []labelpb.ZLabelSet {
   217  	stores := s.stores()
   218  	if len(stores) == 0 {
   219  		return []labelpb.ZLabelSet{}
   220  	}
   221  
   222  	mergedLabelSets := make(map[uint64]labelpb.ZLabelSet, len(stores))
   223  	for _, st := range stores {
   224  		for _, lset := range st.LabelSets() {
   225  			mergedLabelSet := labelpb.ExtendSortedLabels(lset, s.selectorLabels)
   226  			mergedLabelSets[mergedLabelSet.Hash()] = labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(mergedLabelSet)}
   227  		}
   228  	}
   229  
   230  	labelSets := make([]labelpb.ZLabelSet, 0, len(mergedLabelSets))
   231  	for _, v := range mergedLabelSets {
   232  		labelSets = append(labelSets, v)
   233  	}
   234  
   235  	// We always want to enforce announcing the subset of data that
   236  	// selector-labels represents. If no label-sets are announced by the
   237  	// store-proxy's discovered stores, then we still want to enforce
   238  	// announcing this subset by announcing the selector as the label-set.
   239  	selectorLabels := labelpb.ZLabelsFromPromLabels(s.selectorLabels)
   240  	if len(labelSets) == 0 && len(selectorLabels) > 0 {
   241  		labelSets = append(labelSets, labelpb.ZLabelSet{Labels: selectorLabels})
   242  	}
   243  
   244  	return labelSets
   245  }
   246  
   247  func (s *ProxyStore) TimeRange() (int64, int64) {
   248  	stores := s.stores()
   249  	if len(stores) == 0 {
   250  		return math.MinInt64, math.MaxInt64
   251  	}
   252  
   253  	var minTime, maxTime int64 = math.MaxInt64, math.MinInt64
   254  	for _, s := range stores {
   255  		storeMinTime, storeMaxTime := s.TimeRange()
   256  		if storeMinTime < minTime {
   257  			minTime = storeMinTime
   258  		}
   259  		if storeMaxTime > maxTime {
   260  			maxTime = storeMaxTime
   261  		}
   262  	}
   263  
   264  	return minTime, maxTime
   265  }
   266  
   267  func (s *ProxyStore) TSDBInfos() []infopb.TSDBInfo {
   268  	infos := make([]infopb.TSDBInfo, 0)
   269  	for _, store := range s.stores() {
   270  		infos = append(infos, store.TSDBInfos()...)
   271  	}
   272  	return infos
   273  }
   274  
   275  func (s *ProxyStore) Series(originalRequest *storepb.SeriesRequest, srv storepb.Store_SeriesServer) error {
   276  	// TODO(bwplotka): This should be part of request logger, otherwise it does not make much sense. Also, could be
   277  	// tiggered by tracing span to reduce cognitive load.
   278  	reqLogger := log.With(s.logger, "component", "proxy", "request", originalRequest.String())
   279  
   280  	match, matchers, err := matchesExternalLabels(originalRequest.Matchers, s.selectorLabels)
   281  	if err != nil {
   282  		return status.Error(codes.InvalidArgument, err.Error())
   283  	}
   284  	if !match {
   285  		return nil
   286  	}
   287  	if len(matchers) == 0 {
   288  		return status.Error(codes.InvalidArgument, errors.New("no matchers specified (excluding selector labels)").Error())
   289  	}
   290  	storeMatchers, _ := storepb.PromMatchersToMatchers(matchers...) // Error would be returned by matchesExternalLabels, so skip check.
   291  
   292  	storeDebugMsgs := []string{}
   293  	r := &storepb.SeriesRequest{
   294  		MinTime:                 originalRequest.MinTime,
   295  		MaxTime:                 originalRequest.MaxTime,
   296  		Matchers:                storeMatchers,
   297  		Aggregates:              originalRequest.Aggregates,
   298  		MaxResolutionWindow:     originalRequest.MaxResolutionWindow,
   299  		SkipChunks:              originalRequest.SkipChunks,
   300  		QueryHints:              originalRequest.QueryHints,
   301  		PartialResponseDisabled: originalRequest.PartialResponseDisabled,
   302  		PartialResponseStrategy: originalRequest.PartialResponseStrategy,
   303  		ShardInfo:               originalRequest.ShardInfo,
   304  		WithoutReplicaLabels:    originalRequest.WithoutReplicaLabels,
   305  	}
   306  
   307  	// We may arrive here either via the promql engine
   308  	// or as a result of a grpc call in layered queries
   309  	ctx := srv.Context()
   310  	tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(ctx)
   311  	if !foundTenant {
   312  		if ctx.Value(tenancy.TenantKey) != nil {
   313  			tenant = ctx.Value(tenancy.TenantKey).(string)
   314  		}
   315  	}
   316  
   317  	ctx = metadata.AppendToOutgoingContext(ctx, tenancy.DefaultTenantHeader, tenant)
   318  	level.Debug(s.logger).Log("msg", "Tenant info in Series()", "tenant", tenant)
   319  
   320  	stores := []Client{}
   321  	for _, st := range s.stores() {
   322  		// We might be able to skip the store if its meta information indicates it cannot have series matching our query.
   323  		if ok, reason := storeMatches(ctx, st, s.debugLogging, originalRequest.MinTime, originalRequest.MaxTime, matchers...); !ok {
   324  			if s.debugLogging {
   325  				storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("store %s filtered out: %v", st, reason))
   326  			}
   327  			continue
   328  		}
   329  
   330  		stores = append(stores, st)
   331  	}
   332  
   333  	if len(stores) == 0 {
   334  		level.Debug(reqLogger).Log("err", ErrorNoStoresMatched, "stores", strings.Join(storeDebugMsgs, ";"))
   335  		return nil
   336  	}
   337  
   338  	storeResponses := make([]respSet, 0, len(stores))
   339  
   340  	for _, st := range stores {
   341  		st := st
   342  		if s.debugLogging {
   343  			storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("store %s queried", st))
   344  		}
   345  
   346  		respSet, err := newAsyncRespSet(ctx, st, r, s.responseTimeout, s.retrievalStrategy, &s.buffers, r.ShardInfo, reqLogger, s.metrics.emptyStreamResponses)
   347  		if err != nil {
   348  			level.Error(reqLogger).Log("err", err)
   349  
   350  			if !r.PartialResponseDisabled || r.PartialResponseStrategy == storepb.PartialResponseStrategy_WARN {
   351  				if err := srv.Send(storepb.NewWarnSeriesResponse(err)); err != nil {
   352  					return err
   353  				}
   354  				continue
   355  			} else {
   356  				return err
   357  			}
   358  		}
   359  
   360  		storeResponses = append(storeResponses, respSet)
   361  		defer respSet.Close()
   362  	}
   363  
   364  	level.Debug(reqLogger).Log("msg", "Series: started fanout streams", "status", strings.Join(storeDebugMsgs, ";"))
   365  
   366  	respHeap := NewDedupResponseHeap(NewProxyResponseHeap(storeResponses...))
   367  	for respHeap.Next() {
   368  		resp := respHeap.At()
   369  
   370  		if resp.GetWarning() != "" && (r.PartialResponseDisabled || r.PartialResponseStrategy == storepb.PartialResponseStrategy_ABORT) {
   371  			return status.Error(codes.Aborted, resp.GetWarning())
   372  		}
   373  
   374  		if err := srv.Send(resp); err != nil {
   375  			return status.Error(codes.Unknown, errors.Wrap(err, "send series response").Error())
   376  		}
   377  	}
   378  
   379  	return nil
   380  }
   381  
   382  // storeMatches returns boolean if the given store may hold data for the given label matchers, time ranges and debug store matches gathered from context.
   383  func storeMatches(ctx context.Context, s Client, debugLogging bool, mint, maxt int64, matchers ...*labels.Matcher) (ok bool, reason string) {
   384  	var storeDebugMatcher [][]*labels.Matcher
   385  	if ctxVal := ctx.Value(StoreMatcherKey); ctxVal != nil {
   386  		if value, ok := ctxVal.([][]*labels.Matcher); ok {
   387  			storeDebugMatcher = value
   388  		}
   389  	}
   390  
   391  	storeMinTime, storeMaxTime := s.TimeRange()
   392  	if mint > storeMaxTime || maxt < storeMinTime {
   393  		if debugLogging {
   394  			reason = fmt.Sprintf("does not have data within this time period: [%v,%v]. Store time ranges: [%v,%v]", mint, maxt, storeMinTime, storeMaxTime)
   395  		}
   396  		return false, reason
   397  	}
   398  
   399  	if ok, reason := storeMatchDebugMetadata(s, storeDebugMatcher); !ok {
   400  		return false, reason
   401  	}
   402  
   403  	extLset := s.LabelSets()
   404  	if !labelSetsMatch(matchers, extLset...) {
   405  		if debugLogging {
   406  			reason = fmt.Sprintf("external labels %v does not match request label matchers: %v", extLset, matchers)
   407  		}
   408  		return false, reason
   409  	}
   410  	return true, ""
   411  }
   412  
   413  // storeMatchDebugMetadata return true if the store's address match the storeDebugMatchers.
   414  func storeMatchDebugMetadata(s Client, storeDebugMatchers [][]*labels.Matcher) (ok bool, reason string) {
   415  	if len(storeDebugMatchers) == 0 {
   416  		return true, ""
   417  	}
   418  
   419  	addr, isLocal := s.Addr()
   420  	if isLocal {
   421  		return false, "the store is not remote, cannot match __address__"
   422  	}
   423  
   424  	match := false
   425  	for _, sm := range storeDebugMatchers {
   426  		match = match || labelSetsMatch(sm, labels.FromStrings("__address__", addr))
   427  	}
   428  	if !match {
   429  		return false, fmt.Sprintf("__address__ %v does not match debug store metadata matchers: %v", addr, storeDebugMatchers)
   430  	}
   431  	return true, ""
   432  }
   433  
   434  // labelSetsMatch returns false if all label-set do not match the matchers (aka: OR is between all label-sets).
   435  func labelSetsMatch(matchers []*labels.Matcher, lset ...labels.Labels) bool {
   436  	if len(lset) == 0 {
   437  		return true
   438  	}
   439  
   440  	for _, ls := range lset {
   441  		notMatched := false
   442  		for _, m := range matchers {
   443  			if lv := ls.Get(m.Name); ls.Has(m.Name) && !m.Matches(lv) {
   444  				notMatched = true
   445  				break
   446  			}
   447  		}
   448  		if !notMatched {
   449  			return true
   450  		}
   451  	}
   452  	return false
   453  }
   454  
   455  // LabelNames returns all known label names.
   456  func (s *ProxyStore) LabelNames(ctx context.Context, r *storepb.LabelNamesRequest) (
   457  	*storepb.LabelNamesResponse, error,
   458  ) {
   459  	var (
   460  		warnings       []string
   461  		names          [][]string
   462  		mtx            sync.Mutex
   463  		g, gctx        = errgroup.WithContext(ctx)
   464  		storeDebugMsgs []string
   465  	)
   466  
   467  	// We may arrive here either via the promql engine
   468  	// or as a result of a grpc call in layered queries
   469  	tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(gctx)
   470  	if !foundTenant {
   471  		level.Debug(s.logger).Log("msg", "using tenant from context instead of metadata")
   472  		if gctx.Value(tenancy.TenantKey) != nil {
   473  			tenant = gctx.Value(tenancy.TenantKey).(string)
   474  		}
   475  	}
   476  
   477  	gctx = metadata.AppendToOutgoingContext(gctx, tenancy.DefaultTenantHeader, tenant)
   478  	level.Debug(s.logger).Log("msg", "Tenant info in LabelNames()", "tenant", tenant)
   479  
   480  	for _, st := range s.stores() {
   481  		st := st
   482  
   483  		// We might be able to skip the store if its meta information indicates it cannot have series matching our query.
   484  		if ok, reason := storeMatches(gctx, st, s.debugLogging, r.Start, r.End); !ok {
   485  			if s.debugLogging {
   486  				storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s filtered out due to %v", st, reason))
   487  			}
   488  			continue
   489  		}
   490  		if s.debugLogging {
   491  			storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s queried", st))
   492  		}
   493  
   494  		g.Go(func() error {
   495  			resp, err := st.LabelNames(gctx, &storepb.LabelNamesRequest{
   496  				PartialResponseDisabled: r.PartialResponseDisabled,
   497  				Start:                   r.Start,
   498  				End:                     r.End,
   499  				Matchers:                r.Matchers,
   500  			})
   501  			if err != nil {
   502  				err = errors.Wrapf(err, "fetch label names from store %s", st)
   503  				if r.PartialResponseDisabled {
   504  					return err
   505  				}
   506  
   507  				mtx.Lock()
   508  				warnings = append(warnings, err.Error())
   509  				mtx.Unlock()
   510  				return nil
   511  			}
   512  
   513  			mtx.Lock()
   514  			warnings = append(warnings, resp.Warnings...)
   515  			names = append(names, resp.Names)
   516  			mtx.Unlock()
   517  
   518  			return nil
   519  		})
   520  	}
   521  
   522  	if err := g.Wait(); err != nil {
   523  		return nil, err
   524  	}
   525  
   526  	level.Debug(s.logger).Log("msg", strings.Join(storeDebugMsgs, ";"))
   527  	return &storepb.LabelNamesResponse{
   528  		Names:    strutil.MergeUnsortedSlices(names...),
   529  		Warnings: warnings,
   530  	}, nil
   531  }
   532  
   533  // LabelValues returns all known label values for a given label name.
   534  func (s *ProxyStore) LabelValues(ctx context.Context, r *storepb.LabelValuesRequest) (
   535  	*storepb.LabelValuesResponse, error,
   536  ) {
   537  	var (
   538  		warnings       []string
   539  		all            [][]string
   540  		mtx            sync.Mutex
   541  		g, gctx        = errgroup.WithContext(ctx)
   542  		storeDebugMsgs []string
   543  		span           opentracing.Span
   544  	)
   545  
   546  	// We may arrive here either via the promql engine
   547  	// or as a result of a grpc call in layered queries
   548  	tenant, foundTenant := tenancy.GetTenantFromGRPCMetadata(gctx)
   549  	if !foundTenant {
   550  		level.Debug(s.logger).Log("msg", "using tenant from context instead of metadata")
   551  		if gctx.Value(tenancy.TenantKey) != nil {
   552  			tenant = gctx.Value(tenancy.TenantKey).(string)
   553  		}
   554  	}
   555  
   556  	gctx = metadata.AppendToOutgoingContext(gctx, tenancy.DefaultTenantHeader, tenant)
   557  	level.Debug(s.logger).Log("msg", "Tenant info in LabelValues()", "tenant", tenant)
   558  
   559  	for _, st := range s.stores() {
   560  		st := st
   561  
   562  		storeAddr, isLocalStore := st.Addr()
   563  		storeID := labelpb.PromLabelSetsToString(st.LabelSets())
   564  		if storeID == "" {
   565  			storeID = "Store Gateway"
   566  		}
   567  		span, gctx = tracing.StartSpan(gctx, "proxy.label_values", tracing.Tags{
   568  			"store.id":       storeID,
   569  			"store.addr":     storeAddr,
   570  			"store.is_local": isLocalStore,
   571  		})
   572  		defer span.Finish()
   573  
   574  		// We might be able to skip the store if its meta information indicates it cannot have series matching our query.
   575  		if ok, reason := storeMatches(gctx, st, s.debugLogging, r.Start, r.End); !ok {
   576  			if s.debugLogging {
   577  				storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s filtered out due to %v", st, reason))
   578  			}
   579  			continue
   580  		}
   581  		if s.debugLogging {
   582  			storeDebugMsgs = append(storeDebugMsgs, fmt.Sprintf("Store %s queried", st))
   583  		}
   584  
   585  		g.Go(func() error {
   586  			resp, err := st.LabelValues(gctx, &storepb.LabelValuesRequest{
   587  				Label:                   r.Label,
   588  				PartialResponseDisabled: r.PartialResponseDisabled,
   589  				Start:                   r.Start,
   590  				End:                     r.End,
   591  				Matchers:                r.Matchers,
   592  			})
   593  			if err != nil {
   594  				msg := "fetch label values from store %s"
   595  				err = errors.Wrapf(err, msg, st)
   596  				if r.PartialResponseDisabled {
   597  					return err
   598  				}
   599  
   600  				mtx.Lock()
   601  				warnings = append(warnings, errors.Wrapf(err, msg, st).Error())
   602  				mtx.Unlock()
   603  				return nil
   604  			}
   605  
   606  			mtx.Lock()
   607  			warnings = append(warnings, resp.Warnings...)
   608  			all = append(all, resp.Values)
   609  			mtx.Unlock()
   610  
   611  			return nil
   612  		})
   613  	}
   614  
   615  	if err := g.Wait(); err != nil {
   616  		return nil, err
   617  	}
   618  
   619  	level.Debug(s.logger).Log("msg", strings.Join(storeDebugMsgs, ";"))
   620  	return &storepb.LabelValuesResponse{
   621  		Values:   strutil.MergeUnsortedSlices(all...),
   622  		Warnings: warnings,
   623  	}, nil
   624  }