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  }