github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/storage/tsdb/caching_bucket.go (about)

     1  package tsdb
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"path/filepath"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/go-kit/log"
    12  	"github.com/golang/snappy"
    13  	"github.com/oklog/ulid"
    14  	"github.com/pkg/errors"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/thanos-io/thanos/pkg/block"
    17  	"github.com/thanos-io/thanos/pkg/block/metadata"
    18  	"github.com/thanos-io/thanos/pkg/cache"
    19  	"github.com/thanos-io/thanos/pkg/cacheutil"
    20  	"github.com/thanos-io/thanos/pkg/objstore"
    21  	storecache "github.com/thanos-io/thanos/pkg/store/cache"
    22  )
    23  
    24  const (
    25  	CacheBackendMemcached = "memcached"
    26  )
    27  
    28  type CacheBackend struct {
    29  	Backend   string                `yaml:"backend"`
    30  	Memcached MemcachedClientConfig `yaml:"memcached"`
    31  }
    32  
    33  // Validate the config.
    34  func (cfg *CacheBackend) Validate() error {
    35  	if cfg.Backend != "" && cfg.Backend != CacheBackendMemcached {
    36  		return fmt.Errorf("unsupported cache backend: %s", cfg.Backend)
    37  	}
    38  
    39  	if cfg.Backend == CacheBackendMemcached {
    40  		if err := cfg.Memcached.Validate(); err != nil {
    41  			return err
    42  		}
    43  	}
    44  
    45  	return nil
    46  }
    47  
    48  type ChunksCacheConfig struct {
    49  	CacheBackend `yaml:",inline"`
    50  
    51  	SubrangeSize        int64         `yaml:"subrange_size"`
    52  	MaxGetRangeRequests int           `yaml:"max_get_range_requests"`
    53  	AttributesTTL       time.Duration `yaml:"attributes_ttl"`
    54  	SubrangeTTL         time.Duration `yaml:"subrange_ttl"`
    55  }
    56  
    57  func (cfg *ChunksCacheConfig) RegisterFlagsWithPrefix(f *flag.FlagSet, prefix string) {
    58  	f.StringVar(&cfg.Backend, prefix+"backend", "", fmt.Sprintf("Backend for chunks cache, if not empty. Supported values: %s.", CacheBackendMemcached))
    59  
    60  	cfg.Memcached.RegisterFlagsWithPrefix(f, prefix+"memcached.")
    61  
    62  	f.Int64Var(&cfg.SubrangeSize, prefix+"subrange-size", 16000, "Size of each subrange that bucket object is split into for better caching.")
    63  	f.IntVar(&cfg.MaxGetRangeRequests, prefix+"max-get-range-requests", 3, "Maximum number of sub-GetRange requests that a single GetRange request can be split into when fetching chunks. Zero or negative value = unlimited number of sub-requests.")
    64  	f.DurationVar(&cfg.AttributesTTL, prefix+"attributes-ttl", 168*time.Hour, "TTL for caching object attributes for chunks.")
    65  	f.DurationVar(&cfg.SubrangeTTL, prefix+"subrange-ttl", 24*time.Hour, "TTL for caching individual chunks subranges.")
    66  }
    67  
    68  func (cfg *ChunksCacheConfig) Validate() error {
    69  	return cfg.CacheBackend.Validate()
    70  }
    71  
    72  type MetadataCacheConfig struct {
    73  	CacheBackend `yaml:",inline"`
    74  
    75  	TenantsListTTL          time.Duration `yaml:"tenants_list_ttl"`
    76  	TenantBlocksListTTL     time.Duration `yaml:"tenant_blocks_list_ttl"`
    77  	ChunksListTTL           time.Duration `yaml:"chunks_list_ttl"`
    78  	MetafileExistsTTL       time.Duration `yaml:"metafile_exists_ttl"`
    79  	MetafileDoesntExistTTL  time.Duration `yaml:"metafile_doesnt_exist_ttl"`
    80  	MetafileContentTTL      time.Duration `yaml:"metafile_content_ttl"`
    81  	MetafileMaxSize         int           `yaml:"metafile_max_size_bytes"`
    82  	MetafileAttributesTTL   time.Duration `yaml:"metafile_attributes_ttl"`
    83  	BlockIndexAttributesTTL time.Duration `yaml:"block_index_attributes_ttl"`
    84  	BucketIndexContentTTL   time.Duration `yaml:"bucket_index_content_ttl"`
    85  	BucketIndexMaxSize      int           `yaml:"bucket_index_max_size_bytes"`
    86  }
    87  
    88  func (cfg *MetadataCacheConfig) RegisterFlagsWithPrefix(f *flag.FlagSet, prefix string) {
    89  	f.StringVar(&cfg.Backend, prefix+"backend", "", fmt.Sprintf("Backend for metadata cache, if not empty. Supported values: %s.", CacheBackendMemcached))
    90  
    91  	cfg.Memcached.RegisterFlagsWithPrefix(f, prefix+"memcached.")
    92  
    93  	f.DurationVar(&cfg.TenantsListTTL, prefix+"tenants-list-ttl", 15*time.Minute, "How long to cache list of tenants in the bucket.")
    94  	f.DurationVar(&cfg.TenantBlocksListTTL, prefix+"tenant-blocks-list-ttl", 5*time.Minute, "How long to cache list of blocks for each tenant.")
    95  	f.DurationVar(&cfg.ChunksListTTL, prefix+"chunks-list-ttl", 24*time.Hour, "How long to cache list of chunks for a block.")
    96  	f.DurationVar(&cfg.MetafileExistsTTL, prefix+"metafile-exists-ttl", 2*time.Hour, "How long to cache information that block metafile exists. Also used for user deletion mark file.")
    97  	f.DurationVar(&cfg.MetafileDoesntExistTTL, prefix+"metafile-doesnt-exist-ttl", 5*time.Minute, "How long to cache information that block metafile doesn't exist. Also used for user deletion mark file.")
    98  	f.DurationVar(&cfg.MetafileContentTTL, prefix+"metafile-content-ttl", 24*time.Hour, "How long to cache content of the metafile.")
    99  	f.IntVar(&cfg.MetafileMaxSize, prefix+"metafile-max-size-bytes", 1*1024*1024, "Maximum size of metafile content to cache in bytes. Caching will be skipped if the content exceeds this size. This is useful to avoid network round trip for large content if the configured caching backend has an hard limit on cached items size (in this case, you should set this limit to the same limit in the caching backend).")
   100  	f.DurationVar(&cfg.MetafileAttributesTTL, prefix+"metafile-attributes-ttl", 168*time.Hour, "How long to cache attributes of the block metafile.")
   101  	f.DurationVar(&cfg.BlockIndexAttributesTTL, prefix+"block-index-attributes-ttl", 168*time.Hour, "How long to cache attributes of the block index.")
   102  	f.DurationVar(&cfg.BucketIndexContentTTL, prefix+"bucket-index-content-ttl", 5*time.Minute, "How long to cache content of the bucket index.")
   103  	f.IntVar(&cfg.BucketIndexMaxSize, prefix+"bucket-index-max-size-bytes", 1*1024*1024, "Maximum size of bucket index content to cache in bytes. Caching will be skipped if the content exceeds this size. This is useful to avoid network round trip for large content if the configured caching backend has an hard limit on cached items size (in this case, you should set this limit to the same limit in the caching backend).")
   104  }
   105  
   106  func (cfg *MetadataCacheConfig) Validate() error {
   107  	return cfg.CacheBackend.Validate()
   108  }
   109  
   110  func CreateCachingBucket(chunksConfig ChunksCacheConfig, metadataConfig MetadataCacheConfig, bkt objstore.Bucket, logger log.Logger, reg prometheus.Registerer) (objstore.Bucket, error) {
   111  	cfg := storecache.NewCachingBucketConfig()
   112  	cachingConfigured := false
   113  
   114  	chunksCache, err := createCache("chunks-cache", chunksConfig.Backend, chunksConfig.Memcached, logger, reg)
   115  	if err != nil {
   116  		return nil, errors.Wrapf(err, "chunks-cache")
   117  	}
   118  	if chunksCache != nil {
   119  		cachingConfigured = true
   120  		chunksCache = cache.NewTracingCache(chunksCache)
   121  		cfg.CacheGetRange("chunks", chunksCache, isTSDBChunkFile, chunksConfig.SubrangeSize, chunksConfig.AttributesTTL, chunksConfig.SubrangeTTL, chunksConfig.MaxGetRangeRequests)
   122  	}
   123  
   124  	metadataCache, err := createCache("metadata-cache", metadataConfig.Backend, metadataConfig.Memcached, logger, reg)
   125  	if err != nil {
   126  		return nil, errors.Wrapf(err, "metadata-cache")
   127  	}
   128  	if metadataCache != nil {
   129  		cachingConfigured = true
   130  		metadataCache = cache.NewTracingCache(metadataCache)
   131  
   132  		cfg.CacheExists("metafile", metadataCache, isMetaFile, metadataConfig.MetafileExistsTTL, metadataConfig.MetafileDoesntExistTTL)
   133  		cfg.CacheGet("metafile", metadataCache, isMetaFile, metadataConfig.MetafileMaxSize, metadataConfig.MetafileContentTTL, metadataConfig.MetafileExistsTTL, metadataConfig.MetafileDoesntExistTTL)
   134  		cfg.CacheAttributes("metafile", metadataCache, isMetaFile, metadataConfig.MetafileAttributesTTL)
   135  		cfg.CacheAttributes("block-index", metadataCache, isBlockIndexFile, metadataConfig.BlockIndexAttributesTTL)
   136  		cfg.CacheGet("bucket-index", metadataCache, isBucketIndexFile, metadataConfig.BucketIndexMaxSize, metadataConfig.BucketIndexContentTTL /* do not cache exist / not exist: */, 0, 0)
   137  
   138  		codec := snappyIterCodec{storecache.JSONIterCodec{}}
   139  		cfg.CacheIter("tenants-iter", metadataCache, isTenantsDir, metadataConfig.TenantsListTTL, codec)
   140  		cfg.CacheIter("tenant-blocks-iter", metadataCache, isTenantBlocksDir, metadataConfig.TenantBlocksListTTL, codec)
   141  		cfg.CacheIter("chunks-iter", metadataCache, isChunksDir, metadataConfig.ChunksListTTL, codec)
   142  	}
   143  
   144  	if !cachingConfigured {
   145  		// No caching is configured.
   146  		return bkt, nil
   147  	}
   148  
   149  	return storecache.NewCachingBucket(bkt, cfg, logger, reg)
   150  }
   151  
   152  func createCache(cacheName string, backend string, memcached MemcachedClientConfig, logger log.Logger, reg prometheus.Registerer) (cache.Cache, error) {
   153  	switch backend {
   154  	case "":
   155  		// No caching.
   156  		return nil, nil
   157  
   158  	case CacheBackendMemcached:
   159  		var client cacheutil.MemcachedClient
   160  		client, err := cacheutil.NewMemcachedClientWithConfig(logger, cacheName, memcached.ToMemcachedClientConfig(), reg)
   161  		if err != nil {
   162  			return nil, errors.Wrapf(err, "failed to create memcached client")
   163  		}
   164  		return cache.NewMemcachedCache(cacheName, logger, client, reg), nil
   165  
   166  	default:
   167  		return nil, errors.Errorf("unsupported cache type for cache %s: %s", cacheName, backend)
   168  	}
   169  }
   170  
   171  var chunksMatcher = regexp.MustCompile(`^.*/chunks/\d+$`)
   172  
   173  func isTSDBChunkFile(name string) bool { return chunksMatcher.MatchString(name) }
   174  
   175  func isMetaFile(name string) bool {
   176  	return strings.HasSuffix(name, "/"+metadata.MetaFilename) || strings.HasSuffix(name, "/"+metadata.DeletionMarkFilename) || strings.HasSuffix(name, "/"+TenantDeletionMarkPath)
   177  }
   178  
   179  func isBlockIndexFile(name string) bool {
   180  	// Ensure the path ends with "<block id>/<index filename>".
   181  	if !strings.HasSuffix(name, "/"+block.IndexFilename) {
   182  		return false
   183  	}
   184  
   185  	_, err := ulid.Parse(filepath.Base(filepath.Dir(name)))
   186  	return err == nil
   187  }
   188  
   189  func isBucketIndexFile(name string) bool {
   190  	// TODO can't reference bucketindex because of a circular dependency. To be fixed.
   191  	return strings.HasSuffix(name, "/bucket-index.json.gz")
   192  }
   193  
   194  func isTenantsDir(name string) bool {
   195  	return name == ""
   196  }
   197  
   198  var tenantDirMatcher = regexp.MustCompile("^[^/]+/?$")
   199  
   200  func isTenantBlocksDir(name string) bool {
   201  	return tenantDirMatcher.MatchString(name)
   202  }
   203  
   204  func isChunksDir(name string) bool {
   205  	return strings.HasSuffix(name, "/chunks")
   206  }
   207  
   208  type snappyIterCodec struct {
   209  	storecache.IterCodec
   210  }
   211  
   212  func (i snappyIterCodec) Encode(files []string) ([]byte, error) {
   213  	b, err := i.IterCodec.Encode(files)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	return snappy.Encode(nil, b), nil
   218  }
   219  
   220  func (i snappyIterCodec) Decode(cachedData []byte) ([]string, error) {
   221  	b, err := snappy.Decode(nil, cachedData)
   222  	if err != nil {
   223  		return nil, errors.Wrap(err, "snappyIterCodec")
   224  	}
   225  	return i.IterCodec.Decode(b)
   226  }