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  }