github.com/sequix/cortex@v1.1.6/pkg/chunk/cache/fifo_cache.go (about) 1 package cache 2 3 import ( 4 "context" 5 "flag" 6 "sync" 7 "time" 8 9 "github.com/prometheus/client_golang/prometheus" 10 "github.com/prometheus/client_golang/prometheus/promauto" 11 ) 12 13 var ( 14 cacheEntriesAdded = promauto.NewCounterVec(prometheus.CounterOpts{ 15 Namespace: "querier", 16 Subsystem: "cache", 17 Name: "added_total", 18 Help: "The total number of Put calls on the cache", 19 }, []string{"cache"}) 20 21 cacheEntriesAddedNew = promauto.NewCounterVec(prometheus.CounterOpts{ 22 Namespace: "querier", 23 Subsystem: "cache", 24 Name: "added_new_total", 25 Help: "The total number of new entries added to the cache", 26 }, []string{"cache"}) 27 28 cacheEntriesEvicted = promauto.NewCounterVec(prometheus.CounterOpts{ 29 Namespace: "querier", 30 Subsystem: "cache", 31 Name: "evicted_total", 32 Help: "The total number of evicted entries", 33 }, []string{"cache"}) 34 35 cacheTotalGets = promauto.NewCounterVec(prometheus.CounterOpts{ 36 Namespace: "querier", 37 Subsystem: "cache", 38 Name: "gets_total", 39 Help: "The total number of Get calls", 40 }, []string{"cache"}) 41 42 cacheTotalMisses = promauto.NewCounterVec(prometheus.CounterOpts{ 43 Namespace: "querier", 44 Subsystem: "cache", 45 Name: "misses_total", 46 Help: "The total number of Get calls that had no valid entry", 47 }, []string{"cache"}) 48 49 cacheStaleGets = promauto.NewCounterVec(prometheus.CounterOpts{ 50 Namespace: "querier", 51 Subsystem: "cache", 52 Name: "stale_gets_total", 53 Help: "The total number of Get calls that had an entry which expired", 54 }, []string{"cache"}) 55 ) 56 57 // FifoCacheConfig holds config for the FifoCache. 58 type FifoCacheConfig struct { 59 Size int `yaml:"size,omitempty"` 60 Validity time.Duration `yaml:"validity,omitempty"` 61 } 62 63 // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet 64 func (cfg *FifoCacheConfig) RegisterFlagsWithPrefix(prefix, description string, f *flag.FlagSet) { 65 f.IntVar(&cfg.Size, prefix+"fifocache.size", 0, description+"The number of entries to cache.") 66 f.DurationVar(&cfg.Validity, prefix+"fifocache.duration", 0, description+"The expiry duration for the cache.") 67 } 68 69 // FifoCache is a simple string -> interface{} cache which uses a fifo slide to 70 // manage evictions. O(1) inserts and updates, O(1) gets. 71 type FifoCache struct { 72 lock sync.RWMutex 73 size int 74 validity time.Duration 75 entries []cacheEntry 76 index map[string]int 77 78 // indexes into entries to identify the most recent and least recent entry. 79 first, last int 80 81 entriesAdded prometheus.Counter 82 entriesAddedNew prometheus.Counter 83 entriesEvicted prometheus.Counter 84 totalGets prometheus.Counter 85 totalMisses prometheus.Counter 86 staleGets prometheus.Counter 87 } 88 89 type cacheEntry struct { 90 updated time.Time 91 key string 92 value interface{} 93 prev, next int 94 } 95 96 // NewFifoCache returns a new initialised FifoCache of size. 97 // TODO(bwplotka): Fix metrics, get them out of globals, separate or allow prefixing. 98 func NewFifoCache(name string, cfg FifoCacheConfig) *FifoCache { 99 return &FifoCache{ 100 size: cfg.Size, 101 validity: cfg.Validity, 102 entries: make([]cacheEntry, 0, cfg.Size), 103 index: make(map[string]int, cfg.Size), 104 105 // TODO(bwplotka): There might be simple cache.Cache wrapper for those. 106 entriesAdded: cacheEntriesAdded.WithLabelValues(name), 107 entriesAddedNew: cacheEntriesAddedNew.WithLabelValues(name), 108 entriesEvicted: cacheEntriesEvicted.WithLabelValues(name), 109 totalGets: cacheTotalGets.WithLabelValues(name), 110 totalMisses: cacheTotalMisses.WithLabelValues(name), 111 staleGets: cacheStaleGets.WithLabelValues(name), 112 } 113 } 114 115 // Fetch implements Cache. 116 func (c *FifoCache) Fetch(ctx context.Context, keys []string) (found []string, bufs [][]byte, missing []string) { 117 found, missing, bufs = make([]string, 0, len(keys)), make([]string, 0, len(keys)), make([][]byte, 0, len(keys)) 118 for _, key := range keys { 119 val, ok := c.Get(ctx, key) 120 if !ok { 121 missing = append(missing, key) 122 continue 123 } 124 125 found = append(found, key) 126 bufs = append(bufs, val.([]byte)) 127 } 128 129 return 130 } 131 132 // Store implements Cache. 133 func (c *FifoCache) Store(ctx context.Context, keys []string, bufs [][]byte) { 134 values := make([]interface{}, 0, len(bufs)) 135 for _, buf := range bufs { 136 values = append(values, buf) 137 } 138 c.Put(ctx, keys, values) 139 } 140 141 // Stop implements Cache. 142 func (c *FifoCache) Stop() error { 143 return nil 144 } 145 146 // Put stores the value against the key. 147 func (c *FifoCache) Put(ctx context.Context, keys []string, values []interface{}) { 148 c.entriesAdded.Inc() 149 if c.size == 0 { 150 return 151 } 152 153 c.lock.Lock() 154 defer c.lock.Unlock() 155 156 for i := range keys { 157 c.put(ctx, keys[i], values[i]) 158 } 159 } 160 161 func (c *FifoCache) put(ctx context.Context, key string, value interface{}) { 162 // See if we already have the entry 163 index, ok := c.index[key] 164 if ok { 165 entry := c.entries[index] 166 167 entry.updated = time.Now() 168 entry.value = value 169 170 // Remove this entry from the FIFO linked-list. 171 c.entries[entry.prev].next = entry.next 172 c.entries[entry.next].prev = entry.prev 173 174 // Insert it at the beginning 175 entry.next = c.first 176 entry.prev = c.last 177 c.entries[entry.next].prev = index 178 c.entries[entry.prev].next = index 179 c.first = index 180 181 c.entries[index] = entry 182 return 183 } 184 c.entriesAddedNew.Inc() 185 186 // Otherwise, see if we need to evict an entry. 187 if len(c.entries) >= c.size { 188 c.entriesEvicted.Inc() 189 index = c.last 190 entry := c.entries[index] 191 192 c.last = entry.prev 193 c.first = index 194 delete(c.index, entry.key) 195 c.index[key] = index 196 197 entry.updated = time.Now() 198 entry.value = value 199 entry.key = key 200 c.entries[index] = entry 201 return 202 } 203 204 // Finally, no hit and we have space. 205 index = len(c.entries) 206 c.entries = append(c.entries, cacheEntry{ 207 updated: time.Now(), 208 key: key, 209 value: value, 210 prev: c.last, 211 next: c.first, 212 }) 213 c.entries[c.first].prev = index 214 c.entries[c.last].next = index 215 c.first = index 216 c.index[key] = index 217 } 218 219 // Get returns the stored value against the key and when the key was last updated. 220 func (c *FifoCache) Get(ctx context.Context, key string) (interface{}, bool) { 221 c.totalGets.Inc() 222 if c.size == 0 { 223 return nil, false 224 } 225 226 c.lock.RLock() 227 defer c.lock.RUnlock() 228 229 index, ok := c.index[key] 230 if ok { 231 updated := c.entries[index].updated 232 if c.validity == 0 || time.Now().Sub(updated) < c.validity { 233 return c.entries[index].value, true 234 } 235 236 c.totalMisses.Inc() 237 c.staleGets.Inc() 238 return nil, false 239 } 240 241 c.totalMisses.Inc() 242 return nil, false 243 }