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