1// Copyright (c) The Thanos Authors.
2// Licensed under the Apache License 2.0.
3
4package cache
5
6import (
7	"context"
8	"time"
9
10	"github.com/go-kit/kit/log"
11	"github.com/go-kit/kit/log/level"
12	"github.com/prometheus/client_golang/prometheus"
13	"github.com/prometheus/client_golang/prometheus/promauto"
14
15	"github.com/thanos-io/thanos/pkg/cacheutil"
16)
17
18// MemcachedCache is a memcached-based cache.
19type MemcachedCache struct {
20	logger    log.Logger
21	memcached cacheutil.MemcachedClient
22
23	// Metrics.
24	requests prometheus.Counter
25	hits     prometheus.Counter
26}
27
28// NewMemcachedCache makes a new MemcachedCache.
29func NewMemcachedCache(name string, logger log.Logger, memcached cacheutil.MemcachedClient, reg prometheus.Registerer) *MemcachedCache {
30	c := &MemcachedCache{
31		logger:    logger,
32		memcached: memcached,
33	}
34
35	c.requests = promauto.With(reg).NewCounter(prometheus.CounterOpts{
36		Name:        "thanos_cache_memcached_requests_total",
37		Help:        "Total number of items requests to memcached.",
38		ConstLabels: prometheus.Labels{"name": name},
39	})
40
41	c.hits = promauto.With(reg).NewCounter(prometheus.CounterOpts{
42		Name:        "thanos_cache_memcached_hits_total",
43		Help:        "Total number of items requests to the cache that were a hit.",
44		ConstLabels: prometheus.Labels{"name": name},
45	})
46
47	level.Info(logger).Log("msg", "created memcached cache")
48
49	return c
50}
51
52// Store data identified by keys.
53// The function enqueues the request and returns immediately: the entry will be
54// asynchronously stored in the cache.
55func (c *MemcachedCache) Store(ctx context.Context, data map[string][]byte, ttl time.Duration) {
56	var (
57		firstErr error
58		failed   int
59	)
60
61	for key, val := range data {
62		if err := c.memcached.SetAsync(ctx, key, val, ttl); err != nil {
63			failed++
64			if firstErr == nil {
65				firstErr = err
66			}
67		}
68	}
69
70	if firstErr != nil {
71		level.Warn(c.logger).Log("msg", "failed to store one or more items into memcached", "failed", failed, "firstErr", firstErr)
72	}
73}
74
75// Fetch fetches multiple keys and returns a map containing cache hits, along with a list of missing keys.
76// In case of error, it logs and return an empty cache hits map.
77func (c *MemcachedCache) Fetch(ctx context.Context, keys []string) map[string][]byte {
78	// Fetch the keys from memcached in a single request.
79	c.requests.Add(float64(len(keys)))
80	results := c.memcached.GetMulti(ctx, keys)
81	c.hits.Add(float64(len(results)))
82	return results
83}
84