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 }