github.com/thanos-io/thanos@v0.32.5/pkg/store/cache/memcached.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package storecache 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/go-kit/log" 11 "github.com/go-kit/log/level" 12 "github.com/oklog/ulid" 13 "github.com/prometheus/client_golang/prometheus" 14 "github.com/prometheus/prometheus/model/labels" 15 "github.com/prometheus/prometheus/storage" 16 17 "github.com/thanos-io/thanos/pkg/cacheutil" 18 ) 19 20 const ( 21 memcachedDefaultTTL = 24 * time.Hour 22 ) 23 24 const ( 25 compressionSchemeStreamedSnappy = "dss" 26 ) 27 28 // RemoteIndexCache is a memcached-based index cache. 29 type RemoteIndexCache struct { 30 logger log.Logger 31 memcached cacheutil.RemoteCacheClient 32 33 compressionScheme string 34 35 // Metrics. 36 postingRequests prometheus.Counter 37 seriesRequests prometheus.Counter 38 expandedPostingRequests prometheus.Counter 39 postingHits prometheus.Counter 40 seriesHits prometheus.Counter 41 expandedPostingHits prometheus.Counter 42 postingDataSizeBytes prometheus.Observer 43 expandedPostingDataSizeBytes prometheus.Observer 44 seriesDataSizeBytes prometheus.Observer 45 } 46 47 // NewRemoteIndexCache makes a new RemoteIndexCache. 48 func NewRemoteIndexCache(logger log.Logger, cacheClient cacheutil.RemoteCacheClient, commonMetrics *commonMetrics, reg prometheus.Registerer) (*RemoteIndexCache, error) { 49 c := &RemoteIndexCache{ 50 logger: logger, 51 memcached: cacheClient, 52 compressionScheme: compressionSchemeStreamedSnappy, // Hardcode it for now. Expose it once we support different types of compressions. 53 } 54 55 if commonMetrics == nil { 56 commonMetrics = newCommonMetrics(reg) 57 } 58 59 c.postingRequests = commonMetrics.requestTotal.WithLabelValues(cacheTypePostings) 60 c.seriesRequests = commonMetrics.requestTotal.WithLabelValues(cacheTypeSeries) 61 c.expandedPostingRequests = commonMetrics.requestTotal.WithLabelValues(cacheTypeExpandedPostings) 62 63 c.postingHits = commonMetrics.hitsTotal.WithLabelValues(cacheTypePostings) 64 c.seriesHits = commonMetrics.hitsTotal.WithLabelValues(cacheTypeSeries) 65 c.expandedPostingHits = commonMetrics.hitsTotal.WithLabelValues(cacheTypeExpandedPostings) 66 67 c.postingDataSizeBytes = commonMetrics.dataSizeBytes.WithLabelValues(cacheTypePostings) 68 c.seriesDataSizeBytes = commonMetrics.dataSizeBytes.WithLabelValues(cacheTypeSeries) 69 c.expandedPostingDataSizeBytes = commonMetrics.dataSizeBytes.WithLabelValues(cacheTypeExpandedPostings) 70 71 level.Info(logger).Log("msg", "created index cache") 72 73 return c, nil 74 } 75 76 // StorePostings sets the postings identified by the ulid and label to the value v. 77 // The function enqueues the request and returns immediately: the entry will be 78 // asynchronously stored in the cache. 79 func (c *RemoteIndexCache) StorePostings(blockID ulid.ULID, l labels.Label, v []byte) { 80 c.postingDataSizeBytes.Observe(float64(len(v))) 81 key := cacheKey{blockID.String(), cacheKeyPostings(l), c.compressionScheme}.string() 82 if err := c.memcached.SetAsync(key, v, memcachedDefaultTTL); err != nil { 83 level.Error(c.logger).Log("msg", "failed to cache postings in memcached", "err", err) 84 } 85 } 86 87 // FetchMultiPostings fetches multiple postings - each identified by a label - 88 // and returns a map containing cache hits, along with a list of missing keys. 89 // In case of error, it logs and return an empty cache hits map. 90 func (c *RemoteIndexCache) FetchMultiPostings(ctx context.Context, blockID ulid.ULID, lbls []labels.Label) (hits map[labels.Label][]byte, misses []labels.Label) { 91 keys := make([]string, 0, len(lbls)) 92 93 blockIDKey := blockID.String() 94 for _, lbl := range lbls { 95 key := cacheKey{blockIDKey, cacheKeyPostings(lbl), c.compressionScheme}.string() 96 keys = append(keys, key) 97 } 98 99 // Fetch the keys from memcached in a single request. 100 c.postingRequests.Add(float64(len(keys))) 101 results := c.memcached.GetMulti(ctx, keys) 102 if len(results) == 0 { 103 return nil, lbls 104 } 105 106 // Construct the resulting hits map and list of missing keys. We iterate on the input 107 // list of labels to be able to easily create the list of ones in a single iteration. 108 hits = make(map[labels.Label][]byte, len(results)) 109 for i, lbl := range lbls { 110 // Check if the key has been found in memcached. If not, we add it to the list 111 // of missing keys. 112 value, ok := results[keys[i]] 113 if !ok { 114 misses = append(misses, lbl) 115 continue 116 } 117 118 hits[lbl] = value 119 } 120 121 c.postingHits.Add(float64(len(hits))) 122 return hits, misses 123 } 124 125 // StoreExpandedPostings sets the postings identified by the ulid and label to the value v. 126 // The function enqueues the request and returns immediately: the entry will be 127 // asynchronously stored in the cache. 128 func (c *RemoteIndexCache) StoreExpandedPostings(blockID ulid.ULID, keys []*labels.Matcher, v []byte) { 129 c.expandedPostingDataSizeBytes.Observe(float64(len(v))) 130 key := cacheKey{blockID.String(), cacheKeyExpandedPostings(labelMatchersToString(keys)), c.compressionScheme}.string() 131 132 if err := c.memcached.SetAsync(key, v, memcachedDefaultTTL); err != nil { 133 level.Error(c.logger).Log("msg", "failed to cache expanded postings in memcached", "err", err) 134 } 135 } 136 137 // FetchExpandedPostings fetches multiple postings - each identified by a label - 138 // and returns a map containing cache hits, along with a list of missing keys. 139 // In case of error, it logs and return an empty cache hits map. 140 func (c *RemoteIndexCache) FetchExpandedPostings(ctx context.Context, blockID ulid.ULID, lbls []*labels.Matcher) ([]byte, bool) { 141 key := cacheKey{blockID.String(), cacheKeyExpandedPostings(labelMatchersToString(lbls)), c.compressionScheme}.string() 142 143 // Fetch the keys from memcached in a single request. 144 c.expandedPostingRequests.Add(1) 145 results := c.memcached.GetMulti(ctx, []string{key}) 146 if len(results) == 0 { 147 return nil, false 148 } 149 if res, ok := results[key]; ok { 150 c.expandedPostingHits.Add(1) 151 return res, true 152 } 153 return nil, false 154 } 155 156 // StoreSeries sets the series identified by the ulid and id to the value v. 157 // The function enqueues the request and returns immediately: the entry will be 158 // asynchronously stored in the cache. 159 func (c *RemoteIndexCache) StoreSeries(blockID ulid.ULID, id storage.SeriesRef, v []byte) { 160 c.seriesDataSizeBytes.Observe(float64(len(v))) 161 key := cacheKey{blockID.String(), cacheKeySeries(id), ""}.string() 162 163 if err := c.memcached.SetAsync(key, v, memcachedDefaultTTL); err != nil { 164 level.Error(c.logger).Log("msg", "failed to cache series in memcached", "err", err) 165 } 166 } 167 168 // FetchMultiSeries fetches multiple series - each identified by ID - from the cache 169 // and returns a map containing cache hits, along with a list of missing IDs. 170 // In case of error, it logs and return an empty cache hits map. 171 func (c *RemoteIndexCache) FetchMultiSeries(ctx context.Context, blockID ulid.ULID, ids []storage.SeriesRef) (hits map[storage.SeriesRef][]byte, misses []storage.SeriesRef) { 172 keys := make([]string, 0, len(ids)) 173 174 blockIDKey := blockID.String() 175 for _, id := range ids { 176 key := cacheKey{blockIDKey, cacheKeySeries(id), ""}.string() 177 keys = append(keys, key) 178 } 179 180 // Fetch the keys from memcached in a single request. 181 c.seriesRequests.Add(float64(len(ids))) 182 results := c.memcached.GetMulti(ctx, keys) 183 if len(results) == 0 { 184 return nil, ids 185 } 186 187 // Construct the resulting hits map and list of missing keys. We iterate on the input 188 // list of ids to be able to easily create the list of ones in a single iteration. 189 hits = make(map[storage.SeriesRef][]byte, len(results)) 190 for i, id := range ids { 191 // Check if the key has been found in memcached. If not, we add it to the list 192 // of missing keys. 193 value, ok := results[keys[i]] 194 if !ok { 195 misses = append(misses, id) 196 continue 197 } 198 199 hits[id] = value 200 } 201 202 c.seriesHits.Add(float64(len(hits))) 203 return hits, misses 204 } 205 206 // NewMemcachedIndexCache is alias NewRemoteIndexCache for compatible. 207 func NewMemcachedIndexCache(logger log.Logger, memcached cacheutil.RemoteCacheClient, reg prometheus.Registerer) (*RemoteIndexCache, error) { 208 return NewRemoteIndexCache(logger, memcached, nil, reg) 209 }