github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/config.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package queryfrontend
     5  
     6  import (
     7  	"strings"
     8  	"time"
     9  
    10  	extflag "github.com/efficientgo/tools/extkingpin"
    11  	"github.com/go-kit/log"
    12  	"github.com/go-kit/log/level"
    13  	"github.com/pkg/errors"
    14  	prommodel "github.com/prometheus/common/model"
    15  	"gopkg.in/yaml.v2"
    16  
    17  	cortexcache "github.com/thanos-io/thanos/internal/cortex/chunk/cache"
    18  	"github.com/thanos-io/thanos/internal/cortex/frontend/transport"
    19  	"github.com/thanos-io/thanos/internal/cortex/querier"
    20  	"github.com/thanos-io/thanos/internal/cortex/querier/queryrange"
    21  	"github.com/thanos-io/thanos/internal/cortex/util/flagext"
    22  	cortexvalidation "github.com/thanos-io/thanos/internal/cortex/util/validation"
    23  	"github.com/thanos-io/thanos/pkg/cacheutil"
    24  	"github.com/thanos-io/thanos/pkg/model"
    25  )
    26  
    27  type ResponseCacheProvider string
    28  
    29  const (
    30  	INMEMORY  ResponseCacheProvider = "IN-MEMORY"
    31  	MEMCACHED ResponseCacheProvider = "MEMCACHED"
    32  	REDIS     ResponseCacheProvider = "REDIS"
    33  )
    34  
    35  var (
    36  	defaultMemcachedConfig = MemcachedResponseCacheConfig{
    37  		Memcached: cacheutil.MemcachedClientConfig{
    38  			Timeout:                   500 * time.Millisecond,
    39  			MaxIdleConnections:        100,
    40  			MaxAsyncConcurrency:       10,
    41  			MaxAsyncBufferSize:        10000,
    42  			MaxGetMultiConcurrency:    100,
    43  			MaxGetMultiBatchSize:      0,
    44  			MaxItemSize:               model.Bytes(1024 * 1024),
    45  			DNSProviderUpdateInterval: 10 * time.Second,
    46  		},
    47  		Expiration: 24 * time.Hour,
    48  	}
    49  	// DefaultRedisConfig is default redis config for queryfrontend.
    50  	DefaultRedisConfig = RedisResponseCacheConfig{
    51  		Redis:      cacheutil.DefaultRedisClientConfig,
    52  		Expiration: 24 * time.Hour,
    53  	}
    54  )
    55  
    56  // InMemoryResponseCacheConfig holds the configs for the in-memory cache provider.
    57  type InMemoryResponseCacheConfig struct {
    58  	// MaxSize represents overall maximum number of bytes cache can contain.
    59  	MaxSize string `yaml:"max_size"`
    60  	// MaxSizeItems represents the maximum number of entries in the cache.
    61  	MaxSizeItems int `yaml:"max_size_items"`
    62  	// Validity represents the expiry duration for the cache.
    63  	Validity time.Duration `yaml:"validity"`
    64  }
    65  
    66  // MemcachedResponseCacheConfig holds the configs for the memcache cache provider.
    67  type MemcachedResponseCacheConfig struct {
    68  	Memcached cacheutil.MemcachedClientConfig `yaml:",inline"`
    69  	// Expiration sets a global expiration limit for all cached items.
    70  	Expiration time.Duration `yaml:"expiration"`
    71  }
    72  
    73  // RedisResponseCacheConfig holds the configs for the redis cache provider.
    74  type RedisResponseCacheConfig struct {
    75  	Redis cacheutil.RedisClientConfig `yaml:",inline"`
    76  	// Expiration sets a global expiration limit for all cached items.
    77  	Expiration time.Duration `yaml:"expiration"`
    78  }
    79  
    80  // CacheProviderConfig is the initial CacheProviderConfig struct holder before parsing it into a specific cache provider.
    81  // Based on the config type the config is then parsed into a specific cache provider.
    82  type CacheProviderConfig struct {
    83  	Type   ResponseCacheProvider `yaml:"type"`
    84  	Config interface{}           `yaml:"config"`
    85  }
    86  
    87  // NewCacheConfig is a parser that converts a Thanos cache config yaml into a cortex cache config struct.
    88  func NewCacheConfig(logger log.Logger, confContentYaml []byte) (*cortexcache.Config, error) {
    89  	cacheConfig := &CacheProviderConfig{}
    90  	if err := yaml.UnmarshalStrict(confContentYaml, cacheConfig); err != nil {
    91  		return nil, errors.Wrap(err, "parsing config YAML file")
    92  	}
    93  
    94  	backendConfig, err := yaml.Marshal(cacheConfig.Config)
    95  	if err != nil {
    96  		return nil, errors.Wrap(err, "marshal content of cache backend configuration")
    97  	}
    98  
    99  	switch strings.ToUpper(string(cacheConfig.Type)) {
   100  	case string(INMEMORY):
   101  		var config InMemoryResponseCacheConfig
   102  		if err := yaml.Unmarshal(backendConfig, &config); err != nil {
   103  			return nil, err
   104  		}
   105  
   106  		return &cortexcache.Config{
   107  			EnableFifoCache: true,
   108  			Fifocache: cortexcache.FifoCacheConfig{
   109  				MaxSizeBytes: config.MaxSize,
   110  				MaxSizeItems: config.MaxSizeItems,
   111  				Validity:     config.Validity,
   112  			},
   113  		}, nil
   114  	case string(MEMCACHED):
   115  		config := defaultMemcachedConfig
   116  		if err := yaml.UnmarshalStrict(backendConfig, &config); err != nil {
   117  			return nil, err
   118  		}
   119  		if config.Expiration == 0 {
   120  			level.Warn(logger).Log("msg", "memcached cache valid time set to 0, so using a default of 24 hours expiration time")
   121  			config.Expiration = 24 * time.Hour
   122  		}
   123  
   124  		if config.Memcached.DNSProviderUpdateInterval <= 0 {
   125  			level.Warn(logger).Log("msg", "memcached dns provider update interval time set to invalid value, defaulting to 10s")
   126  			config.Memcached.DNSProviderUpdateInterval = 10 * time.Second
   127  		}
   128  
   129  		if config.Memcached.MaxAsyncConcurrency <= 0 {
   130  			level.Warn(logger).Log("msg", "memcached max async concurrency must be positive, defaulting to 10")
   131  			config.Memcached.MaxAsyncConcurrency = 10
   132  		}
   133  
   134  		return &cortexcache.Config{
   135  			Memcache: cortexcache.MemcachedConfig{
   136  				Expiration:  config.Expiration,
   137  				Parallelism: config.Memcached.MaxGetMultiConcurrency,
   138  				BatchSize:   config.Memcached.MaxGetMultiBatchSize,
   139  			},
   140  			MemcacheClient: cortexcache.MemcachedClientConfig{
   141  				Timeout:        config.Memcached.Timeout,
   142  				MaxIdleConns:   config.Memcached.MaxIdleConnections,
   143  				Addresses:      strings.Join(config.Memcached.Addresses, ","),
   144  				UpdateInterval: config.Memcached.DNSProviderUpdateInterval,
   145  				MaxItemSize:    int(config.Memcached.MaxItemSize),
   146  			},
   147  			Background: cortexcache.BackgroundConfig{
   148  				WriteBackBuffer:     config.Memcached.MaxAsyncBufferSize,
   149  				WriteBackGoroutines: config.Memcached.MaxAsyncConcurrency,
   150  			},
   151  		}, nil
   152  	case string(REDIS):
   153  		config := DefaultRedisConfig
   154  		if err := yaml.UnmarshalStrict(backendConfig, &config); err != nil {
   155  			return nil, err
   156  		}
   157  		if config.Expiration <= 0 {
   158  			level.Warn(logger).Log("msg", "redis cache valid time set to 0, so using a default of 24 hours expiration time")
   159  			config.Expiration = 24 * time.Hour
   160  		}
   161  		return &cortexcache.Config{
   162  			Redis: cortexcache.RedisConfig{
   163  				Endpoint:   config.Redis.Addr,
   164  				Timeout:    config.Redis.ReadTimeout,
   165  				MasterName: config.Redis.MasterName,
   166  				Expiration: config.Expiration,
   167  				DB:         config.Redis.DB,
   168  				Password:   flagext.Secret{Value: config.Redis.Password},
   169  			},
   170  			Background: cortexcache.BackgroundConfig{
   171  				WriteBackBuffer:     config.Redis.MaxSetMultiConcurrency * config.Redis.SetMultiBatchSize,
   172  				WriteBackGoroutines: config.Redis.MaxSetMultiConcurrency,
   173  			},
   174  		}, nil
   175  	default:
   176  		return nil, errors.Errorf("response cache with type %s is not supported", cacheConfig.Type)
   177  	}
   178  }
   179  
   180  // DownstreamTripperConfig stores the http.Transport configuration for query-frontend's HTTP downstream tripper.
   181  type DownstreamTripperConfig struct {
   182  	IdleConnTimeout       prommodel.Duration `yaml:"idle_conn_timeout"`
   183  	ResponseHeaderTimeout prommodel.Duration `yaml:"response_header_timeout"`
   184  	TLSHandshakeTimeout   prommodel.Duration `yaml:"tls_handshake_timeout"`
   185  	ExpectContinueTimeout prommodel.Duration `yaml:"expect_continue_timeout"`
   186  	MaxIdleConns          *int               `yaml:"max_idle_conns"`
   187  	MaxIdleConnsPerHost   *int               `yaml:"max_idle_conns_per_host"`
   188  	MaxConnsPerHost       *int               `yaml:"max_conns_per_host"`
   189  
   190  	CachePathOrContent extflag.PathOrContent
   191  }
   192  
   193  // Config holds the query frontend configs.
   194  type Config struct {
   195  	QueryRangeConfig
   196  	LabelsConfig
   197  	DownstreamTripperConfig
   198  
   199  	CortexHandlerConfig    *transport.HandlerConfig
   200  	CompressResponses      bool
   201  	CacheCompression       string
   202  	RequestLoggingDecision string
   203  	DownstreamURL          string
   204  	ForwardHeaders         []string
   205  	NumShards              int
   206  }
   207  
   208  // QueryRangeConfig holds the config for query range tripperware.
   209  type QueryRangeConfig struct {
   210  	// PartialResponseStrategy is the default strategy used
   211  	// when parsing thanos query request.
   212  	PartialResponseStrategy bool
   213  
   214  	ResultsCacheConfig *queryrange.ResultsCacheConfig
   215  	CachePathOrContent extflag.PathOrContent
   216  
   217  	AlignRangeWithStep     bool
   218  	RequestDownsampled     bool
   219  	SplitQueriesByInterval time.Duration
   220  	MinQuerySplitInterval  time.Duration
   221  	MaxQuerySplitInterval  time.Duration
   222  	HorizontalShards       int64
   223  	MaxRetries             int
   224  	Limits                 *cortexvalidation.Limits
   225  }
   226  
   227  // LabelsConfig holds the config for labels tripperware.
   228  type LabelsConfig struct {
   229  	// PartialResponseStrategy is the default strategy used
   230  	// when parsing thanos query request.
   231  	PartialResponseStrategy bool
   232  	DefaultTimeRange        time.Duration
   233  
   234  	ResultsCacheConfig *queryrange.ResultsCacheConfig
   235  	CachePathOrContent extflag.PathOrContent
   236  
   237  	SplitQueriesByInterval time.Duration
   238  	MaxRetries             int
   239  
   240  	Limits *cortexvalidation.Limits
   241  }
   242  
   243  // Validate a fully initialized config.
   244  func (cfg *Config) Validate() error {
   245  	if cfg.QueryRangeConfig.ResultsCacheConfig != nil {
   246  		if cfg.QueryRangeConfig.SplitQueriesByInterval <= 0 && !cfg.isDynamicSplitSet() {
   247  			return errors.New("split queries or split threshold interval should be greater than 0 when caching is enabled")
   248  		}
   249  		if err := cfg.QueryRangeConfig.ResultsCacheConfig.Validate(querier.Config{}); err != nil {
   250  			return errors.Wrap(err, "invalid ResultsCache config for query_range tripperware")
   251  		}
   252  	}
   253  
   254  	if cfg.isDynamicSplitSet() && cfg.isStaticSplitSet() {
   255  		return errors.New("split queries interval and dynamic query split interval cannot be set at the same time")
   256  	}
   257  
   258  	if cfg.isDynamicSplitSet() {
   259  
   260  		if err := cfg.validateDynamicSplitParams(); err != nil {
   261  			return err
   262  		}
   263  	}
   264  
   265  	if cfg.LabelsConfig.ResultsCacheConfig != nil {
   266  		if cfg.LabelsConfig.SplitQueriesByInterval <= 0 {
   267  			return errors.New("split queries interval should be greater than 0  when caching is enabled")
   268  		}
   269  		if err := cfg.LabelsConfig.ResultsCacheConfig.Validate(querier.Config{}); err != nil {
   270  			return errors.Wrap(err, "invalid ResultsCache config for labels tripperware")
   271  		}
   272  	}
   273  
   274  	if cfg.LabelsConfig.DefaultTimeRange == 0 {
   275  		return errors.New("labels.default-time-range cannot be set to 0")
   276  	}
   277  
   278  	if cfg.DownstreamURL == "" {
   279  		return errors.New("downstream URL should be configured")
   280  	}
   281  
   282  	return nil
   283  }
   284  
   285  func (cfg *Config) validateDynamicSplitParams() error {
   286  	if cfg.QueryRangeConfig.HorizontalShards <= 0 {
   287  		return errors.New("min horizontal shards should be greater than 0 when query split threshold is enabled")
   288  	}
   289  
   290  	if cfg.QueryRangeConfig.MaxQuerySplitInterval <= 0 {
   291  		return errors.New("max query split interval should be greater than 0 when query split threshold is enabled")
   292  	}
   293  
   294  	if cfg.QueryRangeConfig.MinQuerySplitInterval <= 0 {
   295  		return errors.New("min query split interval should be greater than 0 when query split threshold is enabled")
   296  	}
   297  	return nil
   298  }
   299  
   300  func (cfg *Config) isStaticSplitSet() bool {
   301  	return cfg.QueryRangeConfig.SplitQueriesByInterval != 0
   302  }
   303  
   304  func (cfg *Config) isDynamicSplitSet() bool {
   305  	return cfg.QueryRangeConfig.MinQuerySplitInterval > 0 ||
   306  		cfg.QueryRangeConfig.HorizontalShards > 0 ||
   307  		cfg.QueryRangeConfig.MaxQuerySplitInterval > 0
   308  }