github.com/thanos-io/thanos@v0.32.5/pkg/store/cache/cache.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  	"encoding/base64"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/oklog/ulid"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/prometheus/client_golang/prometheus/promauto"
    15  	"github.com/prometheus/prometheus/model/labels"
    16  	"github.com/prometheus/prometheus/storage"
    17  	"golang.org/x/crypto/blake2b"
    18  )
    19  
    20  const (
    21  	cacheTypePostings         string = "Postings"
    22  	cacheTypeExpandedPostings string = "ExpandedPostings"
    23  	cacheTypeSeries           string = "Series"
    24  
    25  	sliceHeaderSize = 16
    26  )
    27  
    28  var (
    29  	ulidSize = uint64(len(ulid.ULID{}))
    30  )
    31  
    32  // IndexCache is the interface exported by index cache backends.
    33  // Store operations do not support context.Context, deadlines need to be
    34  // supported by the backends themselves. This is because Set operations are
    35  // run async and it does not make sense to attach same context
    36  // (potentially with a deadline) as in the original user's request.
    37  type IndexCache interface {
    38  	// StorePostings stores postings for a single series.
    39  	StorePostings(blockID ulid.ULID, l labels.Label, v []byte)
    40  
    41  	// FetchMultiPostings fetches multiple postings - each identified by a label -
    42  	// and returns a map containing cache hits, along with a list of missing keys.
    43  	FetchMultiPostings(ctx context.Context, blockID ulid.ULID, keys []labels.Label) (hits map[labels.Label][]byte, misses []labels.Label)
    44  
    45  	// StoreExpandedPostings stores expanded postings for a set of label matchers.
    46  	StoreExpandedPostings(blockID ulid.ULID, matchers []*labels.Matcher, v []byte)
    47  
    48  	// FetchExpandedPostings fetches expanded postings and returns cached data and a boolean value representing whether it is a cache hit or not.
    49  	FetchExpandedPostings(ctx context.Context, blockID ulid.ULID, matchers []*labels.Matcher) ([]byte, bool)
    50  
    51  	// StoreSeries stores a single series.
    52  	StoreSeries(blockID ulid.ULID, id storage.SeriesRef, v []byte)
    53  
    54  	// FetchMultiSeries fetches multiple series - each identified by ID - from the cache
    55  	// and returns a map containing cache hits, along with a list of missing IDs.
    56  	FetchMultiSeries(ctx context.Context, blockID ulid.ULID, ids []storage.SeriesRef) (hits map[storage.SeriesRef][]byte, misses []storage.SeriesRef)
    57  }
    58  
    59  // Common metrics that should be used by all cache implementations.
    60  type commonMetrics struct {
    61  	requestTotal  *prometheus.CounterVec
    62  	hitsTotal     *prometheus.CounterVec
    63  	dataSizeBytes *prometheus.HistogramVec
    64  }
    65  
    66  func newCommonMetrics(reg prometheus.Registerer) *commonMetrics {
    67  	return &commonMetrics{
    68  		requestTotal: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{
    69  			Name: "thanos_store_index_cache_requests_total",
    70  			Help: "Total number of items requests to the cache.",
    71  		}, []string{"item_type"}),
    72  		hitsTotal: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{
    73  			Name: "thanos_store_index_cache_hits_total",
    74  			Help: "Total number of items requests to the cache that were a hit.",
    75  		}, []string{"item_type"}),
    76  		dataSizeBytes: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{
    77  			Name: "thanos_store_index_cache_stored_data_size_bytes",
    78  			Help: "Histogram to track item data size stored in index cache",
    79  			Buckets: []float64{
    80  				32, 256, 512, 1024, 32 * 1024, 256 * 1024, 512 * 1024, 1024 * 1024, 32 * 1024 * 1024, 64 * 1024 * 1024, 128 * 1024 * 1024, 256 * 1024 * 1024, 512 * 1024 * 1024,
    81  			},
    82  		}, []string{"item_type"}),
    83  	}
    84  }
    85  
    86  type cacheKey struct {
    87  	block string
    88  	key   interface{}
    89  
    90  	compression string
    91  }
    92  
    93  func (c cacheKey) keyType() string {
    94  	switch c.key.(type) {
    95  	case cacheKeyPostings:
    96  		return cacheTypePostings
    97  	case cacheKeySeries:
    98  		return cacheTypeSeries
    99  	case cacheKeyExpandedPostings:
   100  		return cacheTypeExpandedPostings
   101  	}
   102  	return "<unknown>"
   103  }
   104  
   105  func (c cacheKey) size() uint64 {
   106  	switch k := c.key.(type) {
   107  	case cacheKeyPostings:
   108  		// ULID + 2 slice headers + number of chars in value and name.
   109  		return ulidSize + 2*sliceHeaderSize + uint64(len(k.Value)+len(k.Name))
   110  	case cacheKeyExpandedPostings:
   111  		return ulidSize + sliceHeaderSize + uint64(len(k))
   112  	case cacheKeySeries:
   113  		return ulidSize + 8 // ULID + uint64.
   114  	}
   115  	return 0
   116  }
   117  
   118  func (c cacheKey) string() string {
   119  	switch c.key.(type) {
   120  	case cacheKeyPostings:
   121  		// Use cryptographically hash functions to avoid hash collisions
   122  		// which would end up in wrong query results.
   123  		lbl := c.key.(cacheKeyPostings)
   124  		lblHash := blake2b.Sum256([]byte(lbl.Name + ":" + lbl.Value))
   125  		key := "P:" + c.block + ":" + base64.RawURLEncoding.EncodeToString(lblHash[0:])
   126  		if len(c.compression) > 0 {
   127  			key += ":" + c.compression
   128  		}
   129  		return key
   130  	case cacheKeyExpandedPostings:
   131  		// Use cryptographically hash functions to avoid hash collisions
   132  		// which would end up in wrong query results.
   133  		matchers := c.key.(cacheKeyExpandedPostings)
   134  		matchersHash := blake2b.Sum256([]byte(matchers))
   135  		key := "EP:" + c.block + ":" + base64.RawURLEncoding.EncodeToString(matchersHash[0:])
   136  		if len(c.compression) > 0 {
   137  			key += ":" + c.compression
   138  		}
   139  		return key
   140  	case cacheKeySeries:
   141  		return "S:" + c.block + ":" + strconv.FormatUint(uint64(c.key.(cacheKeySeries)), 10)
   142  	default:
   143  		return ""
   144  	}
   145  }
   146  
   147  func labelMatchersToString(matchers []*labels.Matcher) string {
   148  	sb := strings.Builder{}
   149  	for i, lbl := range matchers {
   150  		sb.WriteString(lbl.String())
   151  		if i < len(matchers)-1 {
   152  			sb.WriteRune(';')
   153  		}
   154  	}
   155  	return sb.String()
   156  }
   157  
   158  type cacheKeyPostings labels.Label
   159  type cacheKeyExpandedPostings string // We don't use []*labels.Matcher because it is not a hashable type so fail at inmemory cache.
   160  type cacheKeySeries uint64