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

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package main
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/alecthomas/units"
    13  	extflag "github.com/efficientgo/tools/extkingpin"
    14  	"github.com/go-kit/log"
    15  	"github.com/go-kit/log/level"
    16  	grpclogging "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
    17  	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/tags"
    18  	"github.com/oklog/run"
    19  	"github.com/opentracing/opentracing-go"
    20  	"github.com/pkg/errors"
    21  	"github.com/prometheus/client_golang/prometheus"
    22  	commonmodel "github.com/prometheus/common/model"
    23  	"github.com/prometheus/common/route"
    24  
    25  	"github.com/thanos-io/objstore"
    26  	"github.com/thanos-io/objstore/client"
    27  	objstoretracing "github.com/thanos-io/objstore/tracing/opentracing"
    28  
    29  	blocksAPI "github.com/thanos-io/thanos/pkg/api/blocks"
    30  	"github.com/thanos-io/thanos/pkg/block"
    31  	"github.com/thanos-io/thanos/pkg/block/metadata"
    32  	"github.com/thanos-io/thanos/pkg/component"
    33  	hidden "github.com/thanos-io/thanos/pkg/extflag"
    34  	"github.com/thanos-io/thanos/pkg/extkingpin"
    35  	"github.com/thanos-io/thanos/pkg/extprom"
    36  	extpromhttp "github.com/thanos-io/thanos/pkg/extprom/http"
    37  	"github.com/thanos-io/thanos/pkg/gate"
    38  	"github.com/thanos-io/thanos/pkg/info"
    39  	"github.com/thanos-io/thanos/pkg/info/infopb"
    40  	"github.com/thanos-io/thanos/pkg/logging"
    41  	"github.com/thanos-io/thanos/pkg/model"
    42  	"github.com/thanos-io/thanos/pkg/prober"
    43  	"github.com/thanos-io/thanos/pkg/runutil"
    44  	grpcserver "github.com/thanos-io/thanos/pkg/server/grpc"
    45  	httpserver "github.com/thanos-io/thanos/pkg/server/http"
    46  	"github.com/thanos-io/thanos/pkg/store"
    47  	storecache "github.com/thanos-io/thanos/pkg/store/cache"
    48  	"github.com/thanos-io/thanos/pkg/store/labelpb"
    49  	"github.com/thanos-io/thanos/pkg/tls"
    50  	"github.com/thanos-io/thanos/pkg/ui"
    51  )
    52  
    53  const (
    54  	retryTimeoutDuration  = 30
    55  	retryIntervalDuration = 10
    56  )
    57  
    58  type storeConfig struct {
    59  	indexCacheConfigs           extflag.PathOrContent
    60  	objStoreConfig              extflag.PathOrContent
    61  	dataDir                     string
    62  	cacheIndexHeader            bool
    63  	grpcConfig                  grpcConfig
    64  	httpConfig                  httpConfig
    65  	indexCacheSizeBytes         units.Base2Bytes
    66  	chunkPoolSize               units.Base2Bytes
    67  	estimatedMaxSeriesSize      uint64
    68  	estimatedMaxChunkSize       uint64
    69  	seriesBatchSize             int
    70  	storeRateLimits             store.SeriesSelectLimits
    71  	maxDownloadedBytes          units.Base2Bytes
    72  	maxConcurrency              int
    73  	component                   component.StoreAPI
    74  	debugLogging                bool
    75  	syncInterval                time.Duration
    76  	blockSyncConcurrency        int
    77  	blockMetaFetchConcurrency   int
    78  	filterConf                  *store.FilterConfig
    79  	selectorRelabelConf         extflag.PathOrContent
    80  	advertiseCompatibilityLabel bool
    81  	consistencyDelay            commonmodel.Duration
    82  	ignoreDeletionMarksDelay    commonmodel.Duration
    83  	disableWeb                  bool
    84  	webConfig                   webConfig
    85  	label                       string
    86  	postingOffsetsInMemSampling int
    87  	cachingBucketConfig         extflag.PathOrContent
    88  	reqLogConfig                *extflag.PathOrContent
    89  	lazyIndexReaderEnabled      bool
    90  	lazyIndexReaderIdleTimeout  time.Duration
    91  }
    92  
    93  func (sc *storeConfig) registerFlag(cmd extkingpin.FlagClause) {
    94  	sc.httpConfig = *sc.httpConfig.registerFlag(cmd)
    95  	sc.grpcConfig = *sc.grpcConfig.registerFlag(cmd)
    96  	sc.storeRateLimits.RegisterFlags(cmd)
    97  
    98  	cmd.Flag("data-dir", "Local data directory used for caching purposes (index-header, in-mem cache items and meta.jsons). If removed, no data will be lost, just store will have to rebuild the cache. NOTE: Putting raw blocks here will not cause the store to read them. For such use cases use Prometheus + sidecar. Ignored if --no-cache-index-header option is specified.").
    99  		Default("./data").StringVar(&sc.dataDir)
   100  
   101  	cmd.Flag("cache-index-header", "Cache TSDB index-headers on disk to reduce startup time. When set to true, Thanos Store will download index headers from remote object storage on startup and create a header file on disk. Use --data-dir to set the directory in which index headers will be downloaded.").
   102  		Default("true").BoolVar(&sc.cacheIndexHeader)
   103  
   104  	cmd.Flag("index-cache-size", "Maximum size of items held in the in-memory index cache. Ignored if --index-cache.config or --index-cache.config-file option is specified.").
   105  		Default("250MB").BytesVar(&sc.indexCacheSizeBytes)
   106  
   107  	sc.indexCacheConfigs = *extflag.RegisterPathOrContent(cmd, "index-cache.config",
   108  		"YAML file that contains index cache configuration. See format details: https://thanos.io/tip/components/store.md/#index-cache",
   109  		extflag.WithEnvSubstitution(),
   110  	)
   111  
   112  	sc.cachingBucketConfig = *extflag.RegisterPathOrContent(hidden.HiddenCmdClause(cmd), "store.caching-bucket.config",
   113  		"YAML that contains configuration for caching bucket. Experimental feature, with high risk of changes. See format details: https://thanos.io/tip/components/store.md/#caching-bucket",
   114  		extflag.WithEnvSubstitution(),
   115  	)
   116  
   117  	cmd.Flag("chunk-pool-size", "Maximum size of concurrently allocatable bytes reserved strictly to reuse for chunks in memory.").
   118  		Default("2GB").BytesVar(&sc.chunkPoolSize)
   119  
   120  	cmd.Flag("store.grpc.touched-series-limit", "DEPRECATED: use store.limits.request-series.").Default("0").Uint64Var(&sc.storeRateLimits.SeriesPerRequest)
   121  	cmd.Flag("store.grpc.series-sample-limit", "DEPRECATED: use store.limits.request-samples.").Default("0").Uint64Var(&sc.storeRateLimits.SamplesPerRequest)
   122  
   123  	cmd.Flag("store.grpc.downloaded-bytes-limit",
   124  		"Maximum amount of downloaded (either fetched or touched) bytes in a single Series/LabelNames/LabelValues call. The Series call fails if this limit is exceeded. 0 means no limit.").
   125  		Default("0").BytesVar(&sc.maxDownloadedBytes)
   126  
   127  	cmd.Flag("store.grpc.series-max-concurrency", "Maximum number of concurrent Series calls.").Default("20").IntVar(&sc.maxConcurrency)
   128  
   129  	sc.component = component.Store
   130  
   131  	sc.objStoreConfig = *extkingpin.RegisterCommonObjStoreFlags(cmd, "", true)
   132  
   133  	cmd.Flag("sync-block-duration", "Repeat interval for syncing the blocks between local and remote view.").
   134  		Default("3m").DurationVar(&sc.syncInterval)
   135  
   136  	cmd.Flag("block-sync-concurrency", "Number of goroutines to use when constructing index-cache.json blocks from object storage. Must be equal or greater than 1.").
   137  		Default("20").IntVar(&sc.blockSyncConcurrency)
   138  
   139  	cmd.Flag("block-meta-fetch-concurrency", "Number of goroutines to use when fetching block metadata from object storage.").
   140  		Default("32").IntVar(&sc.blockMetaFetchConcurrency)
   141  
   142  	cmd.Flag("debug.series-batch-size", "The batch size when fetching series from TSDB blocks. Setting the number too high can lead to slower retrieval, while setting it too low can lead to throttling caused by too many calls made to object storage.").
   143  		Hidden().Default(strconv.Itoa(store.SeriesBatchSize)).IntVar(&sc.seriesBatchSize)
   144  
   145  	cmd.Flag("debug.estimated-max-series-size", "Estimated max series size. Setting a value might result in over fetching data while a small value might result in data refetch. Default value is 64KB.").
   146  		Hidden().Default(strconv.Itoa(store.EstimatedMaxSeriesSize)).Uint64Var(&sc.estimatedMaxSeriesSize)
   147  
   148  	cmd.Flag("debug.estimated-max-chunk-size", "Estimated max chunk size. Setting a value might result in over fetching data while a small value might result in data refetch. Default value is 16KiB.").
   149  		Hidden().Default(strconv.Itoa(store.EstimatedMaxChunkSize)).Uint64Var(&sc.estimatedMaxChunkSize)
   150  
   151  	sc.filterConf = &store.FilterConfig{}
   152  
   153  	cmd.Flag("min-time", "Start of time range limit to serve. Thanos Store will serve only metrics, which happened later than this value. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y.").
   154  		Default("0000-01-01T00:00:00Z").SetValue(&sc.filterConf.MinTime)
   155  
   156  	cmd.Flag("max-time", "End of time range limit to serve. Thanos Store will serve only blocks, which happened earlier than this value. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y.").
   157  		Default("9999-12-31T23:59:59Z").SetValue(&sc.filterConf.MaxTime)
   158  
   159  	cmd.Flag("debug.advertise-compatibility-label", "If true, Store Gateway in addition to other labels, will advertise special \"@thanos_compatibility_store_type=store\" label set. This makes store Gateway compatible with Querier before 0.8.0").
   160  		Hidden().Default("true").BoolVar(&sc.advertiseCompatibilityLabel)
   161  
   162  	sc.selectorRelabelConf = *extkingpin.RegisterSelectorRelabelFlags(cmd)
   163  
   164  	cmd.Flag("store.index-header-posting-offsets-in-mem-sampling", "Controls what is the ratio of postings offsets store will hold in memory. "+
   165  		"Larger value will keep less offsets, which will increase CPU cycles needed for query touching those postings. It's meant for setups that want low baseline memory pressure and where less traffic is expected. "+
   166  		"On the contrary, smaller value will increase baseline memory usage, but improve latency slightly. 1 will keep all in memory. Default value is the same as in Prometheus which gives a good balance.").
   167  		Hidden().Default(fmt.Sprintf("%v", store.DefaultPostingOffsetInMemorySampling)).IntVar(&sc.postingOffsetsInMemSampling)
   168  
   169  	cmd.Flag("consistency-delay", "Minimum age of all blocks before they are being read. Set it to safe value (e.g 30m) if your object storage is eventually consistent. GCS and S3 are (roughly) strongly consistent.").
   170  		Default("0s").SetValue(&sc.consistencyDelay)
   171  
   172  	cmd.Flag("ignore-deletion-marks-delay", "Duration after which the blocks marked for deletion will be filtered out while fetching blocks. "+
   173  		"The idea of ignore-deletion-marks-delay is to ignore blocks that are marked for deletion with some delay. This ensures store can still serve blocks that are meant to be deleted but do not have a replacement yet. "+
   174  		"If delete-delay duration is provided to compactor or bucket verify component, it will upload deletion-mark.json file to mark after what duration the block should be deleted rather than deleting the block straight away. "+
   175  		"If delete-delay is non-zero for compactor or bucket verify component, ignore-deletion-marks-delay should be set to (delete-delay)/2 so that blocks marked for deletion are filtered out while fetching blocks before being deleted from bucket. "+
   176  		"Default is 24h, half of the default value for --delete-delay on compactor.").
   177  		Default("24h").SetValue(&sc.ignoreDeletionMarksDelay)
   178  
   179  	cmd.Flag("store.enable-index-header-lazy-reader", "If true, Store Gateway will lazy memory map index-header only once the block is required by a query.").
   180  		Default("false").BoolVar(&sc.lazyIndexReaderEnabled)
   181  
   182  	cmd.Flag("store.index-header-lazy-reader-idle-timeout", "If index-header lazy reader is enabled and this idle timeout setting is > 0, memory map-ed index-headers will be automatically released after 'idle timeout' inactivity.").
   183  		Hidden().Default("5m").DurationVar(&sc.lazyIndexReaderIdleTimeout)
   184  
   185  	cmd.Flag("web.disable", "Disable Block Viewer UI.").Default("false").BoolVar(&sc.disableWeb)
   186  
   187  	cmd.Flag("web.external-prefix", "Static prefix for all HTML links and redirect URLs in the bucket web UI interface. Actual endpoints are still served on / or the web.route-prefix. This allows thanos bucket web UI to be served behind a reverse proxy that strips a URL sub-path.").
   188  		Default("").StringVar(&sc.webConfig.externalPrefix)
   189  
   190  	cmd.Flag("web.prefix-header", "Name of HTTP request header used for dynamic prefixing of UI links and redirects. This option is ignored if web.external-prefix argument is set. Security risk: enable this option only if a reverse proxy in front of thanos is resetting the header. The --web.prefix-header=X-Forwarded-Prefix option can be useful, for example, if Thanos UI is served via Traefik reverse proxy with PathPrefixStrip option enabled, which sends the stripped prefix value in X-Forwarded-Prefix header. This allows thanos UI to be served on a sub-path.").
   191  		Default("").StringVar(&sc.webConfig.prefixHeaderName)
   192  
   193  	cmd.Flag("web.disable-cors", "Whether to disable CORS headers to be set by Thanos. By default Thanos sets CORS headers to be allowed by all.").
   194  		Default("false").BoolVar(&sc.webConfig.disableCORS)
   195  
   196  	cmd.Flag("bucket-web-label", "External block label to use as group title in the bucket web UI").StringVar(&sc.label)
   197  
   198  	sc.reqLogConfig = extkingpin.RegisterRequestLoggingFlags(cmd)
   199  }
   200  
   201  // registerStore registers a store command.
   202  func registerStore(app *extkingpin.App) {
   203  	cmd := app.Command(component.Store.String(), "Store node giving access to blocks in a bucket provider. Now supported GCS, S3, Azure, Swift, Tencent COS and Aliyun OSS.")
   204  
   205  	conf := &storeConfig{}
   206  	conf.registerFlag(cmd)
   207  
   208  	cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, debugLogging bool) error {
   209  		if conf.filterConf.MinTime.PrometheusTimestamp() > conf.filterConf.MaxTime.PrometheusTimestamp() {
   210  			return errors.Errorf("invalid argument: --min-time '%s' can't be greater than --max-time '%s'",
   211  				conf.filterConf.MinTime, conf.filterConf.MaxTime)
   212  		}
   213  
   214  		httpLogOpts, err := logging.ParseHTTPOptions("", conf.reqLogConfig)
   215  		if err != nil {
   216  			return errors.Wrap(err, "error while parsing config for request logging")
   217  		}
   218  
   219  		tagOpts, grpcLogOpts, err := logging.ParsegRPCOptions("", conf.reqLogConfig)
   220  		if err != nil {
   221  			return errors.Wrap(err, "error while parsing config for request logging")
   222  		}
   223  
   224  		conf.debugLogging = debugLogging
   225  
   226  		return runStore(g,
   227  			logger,
   228  			reg,
   229  			tracer,
   230  			httpLogOpts,
   231  			grpcLogOpts,
   232  			tagOpts,
   233  			*conf,
   234  			getFlagsMap(cmd.Flags()),
   235  		)
   236  	})
   237  }
   238  
   239  // runStore starts a daemon that serves queries to cluster peers using data from an object store.
   240  func runStore(
   241  	g *run.Group,
   242  	logger log.Logger,
   243  	reg *prometheus.Registry,
   244  	tracer opentracing.Tracer,
   245  	httpLogOpts []logging.Option,
   246  	grpcLogOpts []grpclogging.Option,
   247  	tagOpts []tags.Option,
   248  	conf storeConfig,
   249  	flagsMap map[string]string,
   250  ) error {
   251  	dataDir := conf.dataDir
   252  	if !conf.cacheIndexHeader {
   253  		dataDir = ""
   254  	}
   255  
   256  	grpcProbe := prober.NewGRPC()
   257  	httpProbe := prober.NewHTTP()
   258  	statusProber := prober.Combine(
   259  		httpProbe,
   260  		grpcProbe,
   261  		prober.NewInstrumentation(conf.component, logger, extprom.WrapRegistererWithPrefix("thanos_", reg)),
   262  	)
   263  
   264  	srv := httpserver.New(logger, reg, conf.component, httpProbe,
   265  		httpserver.WithListen(conf.httpConfig.bindAddress),
   266  		httpserver.WithGracePeriod(time.Duration(conf.httpConfig.gracePeriod)),
   267  		httpserver.WithTLSConfig(conf.httpConfig.tlsConfig),
   268  		httpserver.WithEnableH2C(true), // For groupcache.
   269  	)
   270  
   271  	g.Add(func() error {
   272  		statusProber.Healthy()
   273  
   274  		return srv.ListenAndServe()
   275  	}, func(err error) {
   276  		statusProber.NotReady(err)
   277  		defer statusProber.NotHealthy(err)
   278  
   279  		srv.Shutdown(err)
   280  	})
   281  
   282  	confContentYaml, err := conf.objStoreConfig.Content()
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	bkt, err := client.NewBucket(logger, confContentYaml, conf.component.String())
   288  	if err != nil {
   289  		return err
   290  	}
   291  	insBkt := objstoretracing.WrapWithTraces(objstore.WrapWithMetrics(bkt, extprom.WrapRegistererWithPrefix("thanos_", reg), bkt.Name()))
   292  
   293  	cachingBucketConfigYaml, err := conf.cachingBucketConfig.Content()
   294  	if err != nil {
   295  		return errors.Wrap(err, "get caching bucket configuration")
   296  	}
   297  
   298  	r := route.New()
   299  
   300  	if len(cachingBucketConfigYaml) > 0 {
   301  		insBkt, err = storecache.NewCachingBucketFromYaml(cachingBucketConfigYaml, insBkt, logger, reg, r)
   302  		if err != nil {
   303  			return errors.Wrap(err, "create caching bucket")
   304  		}
   305  	}
   306  
   307  	relabelContentYaml, err := conf.selectorRelabelConf.Content()
   308  	if err != nil {
   309  		return errors.Wrap(err, "get content of relabel configuration")
   310  	}
   311  
   312  	relabelConfig, err := block.ParseRelabelConfig(relabelContentYaml, block.SelectorSupportedRelabelActions)
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	indexCacheContentYaml, err := conf.indexCacheConfigs.Content()
   318  	if err != nil {
   319  		return errors.Wrap(err, "get content of index cache configuration")
   320  	}
   321  
   322  	// Create the index cache loading its config from config file, while keeping
   323  	// backward compatibility with the pre-config file era.
   324  	var indexCache storecache.IndexCache
   325  	if len(indexCacheContentYaml) > 0 {
   326  		indexCache, err = storecache.NewIndexCache(logger, indexCacheContentYaml, reg)
   327  	} else {
   328  		indexCache, err = storecache.NewInMemoryIndexCacheWithConfig(logger, nil, reg, storecache.InMemoryIndexCacheConfig{
   329  			MaxSize:     model.Bytes(conf.indexCacheSizeBytes),
   330  			MaxItemSize: storecache.DefaultInMemoryIndexCacheConfig.MaxItemSize,
   331  		})
   332  	}
   333  	if err != nil {
   334  		return errors.Wrap(err, "create index cache")
   335  	}
   336  
   337  	ignoreDeletionMarkFilter := block.NewIgnoreDeletionMarkFilter(logger, insBkt, time.Duration(conf.ignoreDeletionMarksDelay), conf.blockMetaFetchConcurrency)
   338  	metaFetcher, err := block.NewMetaFetcher(logger, conf.blockMetaFetchConcurrency, insBkt, dataDir, extprom.WrapRegistererWithPrefix("thanos_", reg),
   339  		[]block.MetadataFilter{
   340  			block.NewTimePartitionMetaFilter(conf.filterConf.MinTime, conf.filterConf.MaxTime),
   341  			block.NewLabelShardedMetaFilter(relabelConfig),
   342  			block.NewConsistencyDelayMetaFilter(logger, time.Duration(conf.consistencyDelay), extprom.WrapRegistererWithPrefix("thanos_", reg)),
   343  			ignoreDeletionMarkFilter,
   344  			block.NewDeduplicateFilter(conf.blockMetaFetchConcurrency),
   345  		})
   346  	if err != nil {
   347  		return errors.Wrap(err, "meta fetcher")
   348  	}
   349  
   350  	// Limit the concurrency on queries against the Thanos store.
   351  	if conf.maxConcurrency < 0 {
   352  		return errors.Errorf("max concurrency value cannot be lower than 0 (got %v)", conf.maxConcurrency)
   353  	}
   354  
   355  	queriesGate := gate.New(extprom.WrapRegistererWithPrefix("thanos_bucket_store_series_", reg), int(conf.maxConcurrency), gate.Queries)
   356  
   357  	chunkPool, err := store.NewDefaultChunkBytesPool(uint64(conf.chunkPoolSize))
   358  	if err != nil {
   359  		return errors.Wrap(err, "create chunk pool")
   360  	}
   361  
   362  	options := []store.BucketStoreOption{
   363  		store.WithLogger(logger),
   364  		store.WithRegistry(reg),
   365  		store.WithIndexCache(indexCache),
   366  		store.WithQueryGate(queriesGate),
   367  		store.WithChunkPool(chunkPool),
   368  		store.WithFilterConfig(conf.filterConf),
   369  		store.WithChunkHashCalculation(true),
   370  		store.WithSeriesBatchSize(conf.seriesBatchSize),
   371  		store.WithBlockEstimatedMaxSeriesFunc(func(m metadata.Meta) uint64 {
   372  			if m.Thanos.IndexStats.SeriesMaxSize > 0 &&
   373  				uint64(m.Thanos.IndexStats.SeriesMaxSize) < conf.estimatedMaxSeriesSize {
   374  				return uint64(m.Thanos.IndexStats.SeriesMaxSize)
   375  			}
   376  			return conf.estimatedMaxSeriesSize
   377  		}),
   378  		store.WithBlockEstimatedMaxChunkFunc(func(m metadata.Meta) uint64 {
   379  			if m.Thanos.IndexStats.ChunkMaxSize > 0 &&
   380  				uint64(m.Thanos.IndexStats.ChunkMaxSize) < conf.estimatedMaxChunkSize {
   381  				return uint64(m.Thanos.IndexStats.ChunkMaxSize)
   382  			}
   383  			return conf.estimatedMaxChunkSize
   384  		}),
   385  	}
   386  
   387  	if conf.debugLogging {
   388  		options = append(options, store.WithDebugLogging())
   389  	}
   390  
   391  	bs, err := store.NewBucketStore(
   392  		insBkt,
   393  		metaFetcher,
   394  		dataDir,
   395  		store.NewChunksLimiterFactory(conf.storeRateLimits.SamplesPerRequest/store.MaxSamplesPerChunk), // The samples limit is an approximation based on the max number of samples per chunk.
   396  		store.NewSeriesLimiterFactory(conf.storeRateLimits.SeriesPerRequest),
   397  		store.NewBytesLimiterFactory(conf.maxDownloadedBytes),
   398  		store.NewGapBasedPartitioner(store.PartitionerMaxGapSize),
   399  		conf.blockSyncConcurrency,
   400  		conf.advertiseCompatibilityLabel,
   401  		conf.postingOffsetsInMemSampling,
   402  		false,
   403  		conf.lazyIndexReaderEnabled,
   404  		conf.lazyIndexReaderIdleTimeout,
   405  		options...,
   406  	)
   407  	if err != nil {
   408  		return errors.Wrap(err, "create object storage store")
   409  	}
   410  
   411  	// bucketStoreReady signals when bucket store is ready.
   412  	bucketStoreReady := make(chan struct{})
   413  	{
   414  		ctx, cancel := context.WithCancel(context.Background())
   415  		g.Add(func() error {
   416  			defer runutil.CloseWithLogOnErr(logger, insBkt, "bucket client")
   417  
   418  			level.Info(logger).Log("msg", "initializing bucket store")
   419  			begin := time.Now()
   420  
   421  			// This will stop retrying after set timeout duration.
   422  			initialSyncCtx, cancel := context.WithTimeout(ctx, retryTimeoutDuration*time.Second)
   423  			defer cancel()
   424  
   425  			// Retry in case of error.
   426  			err := runutil.Retry(retryIntervalDuration*time.Second, initialSyncCtx.Done(), func() error {
   427  				return bs.InitialSync(ctx)
   428  			})
   429  
   430  			if err != nil {
   431  				close(bucketStoreReady)
   432  				return errors.Wrap(err, "bucket store initial sync")
   433  			}
   434  
   435  			level.Info(logger).Log("msg", "bucket store ready", "init_duration", time.Since(begin).String())
   436  			close(bucketStoreReady)
   437  
   438  			err = runutil.Repeat(conf.syncInterval, ctx.Done(), func() error {
   439  				if err := bs.SyncBlocks(ctx); err != nil {
   440  					level.Warn(logger).Log("msg", "syncing blocks failed", "err", err)
   441  				}
   442  				return nil
   443  			})
   444  
   445  			runutil.CloseWithLogOnErr(logger, bs, "bucket store")
   446  			return err
   447  		}, func(error) {
   448  			cancel()
   449  		})
   450  	}
   451  
   452  	infoSrv := info.NewInfoServer(
   453  		component.Store.String(),
   454  		info.WithLabelSetFunc(func() []labelpb.ZLabelSet {
   455  			return bs.LabelSet()
   456  		}),
   457  		info.WithStoreInfoFunc(func() *infopb.StoreInfo {
   458  			if httpProbe.IsReady() {
   459  				mint, maxt := bs.TimeRange()
   460  				return &infopb.StoreInfo{
   461  					MinTime:                      mint,
   462  					MaxTime:                      maxt,
   463  					SupportsSharding:             true,
   464  					SupportsWithoutReplicaLabels: true,
   465  					TsdbInfos:                    bs.TSDBInfos(),
   466  				}
   467  			}
   468  			return nil
   469  		}),
   470  	)
   471  
   472  	// Start query (proxy) gRPC StoreAPI.
   473  	{
   474  		tlsCfg, err := tls.NewServerConfig(log.With(logger, "protocol", "gRPC"), conf.grpcConfig.tlsSrvCert, conf.grpcConfig.tlsSrvKey, conf.grpcConfig.tlsSrvClientCA)
   475  		if err != nil {
   476  			return errors.Wrap(err, "setup gRPC server")
   477  		}
   478  
   479  		storeServer := store.NewInstrumentedStoreServer(reg, bs)
   480  		s := grpcserver.New(logger, reg, tracer, grpcLogOpts, tagOpts, conf.component, grpcProbe,
   481  			grpcserver.WithServer(store.RegisterStoreServer(storeServer, logger)),
   482  			grpcserver.WithServer(info.RegisterInfoServer(infoSrv)),
   483  			grpcserver.WithListen(conf.grpcConfig.bindAddress),
   484  			grpcserver.WithGracePeriod(conf.grpcConfig.gracePeriod),
   485  			grpcserver.WithMaxConnAge(conf.grpcConfig.maxConnectionAge),
   486  			grpcserver.WithTLSConfig(tlsCfg),
   487  		)
   488  
   489  		g.Add(func() error {
   490  			<-bucketStoreReady
   491  			statusProber.Ready()
   492  			return s.ListenAndServe()
   493  		}, func(err error) {
   494  			statusProber.NotReady(err)
   495  			s.Shutdown(err)
   496  		})
   497  	}
   498  
   499  	// Add bucket UI for loaded blocks.
   500  	{
   501  		ins := extpromhttp.NewInstrumentationMiddleware(reg, nil)
   502  
   503  		if !conf.disableWeb {
   504  			compactorView := ui.NewBucketUI(logger, conf.webConfig.externalPrefix, conf.webConfig.prefixHeaderName, conf.component)
   505  			compactorView.Register(r, ins)
   506  
   507  			// Configure Request Logging for HTTP calls.
   508  			logMiddleware := logging.NewHTTPServerMiddleware(logger, httpLogOpts...)
   509  			api := blocksAPI.NewBlocksAPI(logger, conf.webConfig.disableCORS, conf.label, flagsMap, insBkt)
   510  			api.Register(r.WithPrefix("/api/v1"), tracer, logger, ins, logMiddleware)
   511  
   512  			metaFetcher.UpdateOnChange(func(blocks []metadata.Meta, err error) {
   513  				api.SetLoaded(blocks, err)
   514  			})
   515  		}
   516  
   517  		srv.Handle("/", r)
   518  	}
   519  
   520  	level.Info(logger).Log("msg", "starting store node")
   521  	return nil
   522  }