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 }