Skip to content

Commit d124d33

Browse files
authored
Merge pull request #947 from grafana/cache-index
Cache index queries for up to 15mins
2 parents af27c6f + f6b0901 commit d124d33

9 files changed

+426
-157
lines changed

pkg/chunk/cache/fifo_cache.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"sync"
6+
"time"
7+
8+
ot "github.com/opentracing/opentracing-go"
9+
otlog "github.com/opentracing/opentracing-go/log"
10+
"github.com/prometheus/client_golang/prometheus"
11+
"github.com/prometheus/client_golang/prometheus/promauto"
12+
)
13+
14+
var (
15+
cacheEntriesAdded = promauto.NewCounterVec(prometheus.CounterOpts{
16+
Namespace: "querier",
17+
Subsystem: "cache",
18+
Name: "added_total",
19+
Help: "The total number of Put calls on the cache",
20+
}, []string{"cache"})
21+
22+
cacheEntriesAddedNew = promauto.NewCounterVec(prometheus.CounterOpts{
23+
Namespace: "querier",
24+
Subsystem: "cache",
25+
Name: "added_new_total",
26+
Help: "The total number of new entries added to the cache",
27+
}, []string{"cache"})
28+
29+
cacheEntriesEvicted = promauto.NewCounterVec(prometheus.CounterOpts{
30+
Namespace: "querier",
31+
Subsystem: "cache",
32+
Name: "evicted_total",
33+
Help: "The total number of evicted entries",
34+
}, []string{"cache"})
35+
36+
cacheTotalGets = promauto.NewCounterVec(prometheus.CounterOpts{
37+
Namespace: "querier",
38+
Subsystem: "cache",
39+
Name: "gets_total",
40+
Help: "The total number of Get calls",
41+
}, []string{"cache"})
42+
43+
cacheTotalMisses = promauto.NewCounterVec(prometheus.CounterOpts{
44+
Namespace: "querier",
45+
Subsystem: "cache",
46+
Name: "misses_total",
47+
Help: "The total number of Get calls that had no valid entry",
48+
}, []string{"cache"})
49+
50+
cacheStaleGets = promauto.NewCounterVec(prometheus.CounterOpts{
51+
Namespace: "querier",
52+
Subsystem: "cache",
53+
Name: "stale_gets_total",
54+
Help: "The total number of Get calls that had an entry which expired",
55+
}, []string{"cache"})
56+
)
57+
58+
// FifoCache is a simple string -> interface{} cache which uses a fifo slide to
59+
// manage evictions. O(1) inserts and updates, O(1) gets.
60+
type FifoCache struct {
61+
lock sync.RWMutex
62+
size int
63+
validity time.Duration
64+
entries []cacheEntry
65+
index map[string]int
66+
67+
// indexes into entries to identify the most recent and least recent entry.
68+
first, last int
69+
70+
name string
71+
entriesAdded prometheus.Counter
72+
entriesAddedNew prometheus.Counter
73+
entriesEvicted prometheus.Counter
74+
totalGets prometheus.Counter
75+
totalMisses prometheus.Counter
76+
staleGets prometheus.Counter
77+
}
78+
79+
type cacheEntry struct {
80+
updated time.Time
81+
key string
82+
value interface{}
83+
prev, next int
84+
}
85+
86+
// NewFifoCache returns a new initialised FifoCache of size.
87+
func NewFifoCache(name string, size int, validity time.Duration) *FifoCache {
88+
return &FifoCache{
89+
size: size,
90+
validity: validity,
91+
entries: make([]cacheEntry, 0, size),
92+
index: make(map[string]int, size),
93+
94+
name: name,
95+
entriesAdded: cacheEntriesAdded.WithLabelValues(name),
96+
entriesAddedNew: cacheEntriesAddedNew.WithLabelValues(name),
97+
entriesEvicted: cacheEntriesEvicted.WithLabelValues(name),
98+
totalGets: cacheTotalGets.WithLabelValues(name),
99+
totalMisses: cacheTotalMisses.WithLabelValues(name),
100+
staleGets: cacheStaleGets.WithLabelValues(name),
101+
}
102+
}
103+
104+
// Put stores the value against the key.
105+
func (c *FifoCache) Put(ctx context.Context, key string, value interface{}) {
106+
span, ctx := ot.StartSpanFromContext(ctx, c.name+"-cache-put")
107+
defer span.Finish()
108+
109+
c.entriesAdded.Inc()
110+
if c.size == 0 {
111+
return
112+
}
113+
114+
c.lock.Lock()
115+
defer c.lock.Unlock()
116+
117+
// See if we already have the entry
118+
index, ok := c.index[key]
119+
if ok {
120+
entry := c.entries[index]
121+
122+
entry.updated = time.Now()
123+
entry.value = value
124+
125+
// Remove this entry from the FIFO linked-list.
126+
c.entries[entry.prev].next = entry.next
127+
c.entries[entry.next].prev = entry.prev
128+
129+
// Insert it at the beginning
130+
entry.next = c.first
131+
entry.prev = c.last
132+
c.entries[entry.next].prev = index
133+
c.entries[entry.prev].next = index
134+
c.first = index
135+
136+
c.entries[index] = entry
137+
return
138+
}
139+
c.entriesAddedNew.Inc()
140+
141+
// Otherwise, see if we need to evict an entry.
142+
if len(c.entries) >= c.size {
143+
c.entriesEvicted.Inc()
144+
index = c.last
145+
entry := c.entries[index]
146+
147+
c.last = entry.prev
148+
c.first = index
149+
delete(c.index, entry.key)
150+
c.index[key] = index
151+
152+
entry.updated = time.Now()
153+
entry.value = value
154+
entry.key = key
155+
c.entries[index] = entry
156+
return
157+
}
158+
159+
// Finally, no hit and we have space.
160+
index = len(c.entries)
161+
c.entries = append(c.entries, cacheEntry{
162+
updated: time.Now(),
163+
key: key,
164+
value: value,
165+
prev: c.last,
166+
next: c.first,
167+
})
168+
c.entries[c.first].prev = index
169+
c.entries[c.last].next = index
170+
c.first = index
171+
c.index[key] = index
172+
}
173+
174+
// Get returns the stored value against the key and when the key was last updated.
175+
func (c *FifoCache) Get(ctx context.Context, key string) (interface{}, bool) {
176+
span, ctx := ot.StartSpanFromContext(ctx, c.name+"-cache-get")
177+
defer span.Finish()
178+
179+
c.totalGets.Inc()
180+
if c.size == 0 {
181+
return nil, false
182+
}
183+
184+
c.lock.RLock()
185+
defer c.lock.RUnlock()
186+
187+
index, ok := c.index[key]
188+
if ok {
189+
updated := c.entries[index].updated
190+
if time.Now().Sub(updated) < c.validity {
191+
span.LogFields(otlog.Bool("hit", true))
192+
return c.entries[index].value, true
193+
}
194+
195+
c.totalMisses.Inc()
196+
c.staleGets.Inc()
197+
span.LogFields(otlog.Bool("hit", false), otlog.Bool("stale", true))
198+
return nil, false
199+
}
200+
201+
span.LogFields(otlog.Bool("hit", false), otlog.Bool("stale", false))
202+
c.totalMisses.Inc()
203+
return nil, false
204+
}

pkg/chunk/fifo_cache_test.go renamed to pkg/chunk/cache/fifo_cache_test.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
package chunk
1+
package cache
22

33
import (
4+
"context"
45
"fmt"
56
"strconv"
67
"testing"
8+
"time"
79

810
"github.com/stretchr/testify/require"
911
)
@@ -12,56 +14,73 @@ const size = 10
1214
const overwrite = 5
1315

1416
func TestFifoCache(t *testing.T) {
15-
c := newFifoCache(size)
17+
c := NewFifoCache("test", size, 1*time.Minute)
18+
ctx := context.Background()
1619

1720
// Check put / get works
1821
for i := 0; i < size; i++ {
19-
c.put(strconv.Itoa(i), i)
22+
c.Put(ctx, strconv.Itoa(i), i)
2023
//c.print()
2124
}
2225
require.Len(t, c.index, size)
2326
require.Len(t, c.entries, size)
2427

2528
for i := 0; i < size; i++ {
26-
value, _, ok := c.get(strconv.Itoa(i))
29+
value, ok := c.Get(ctx, strconv.Itoa(i))
2730
require.True(t, ok)
2831
require.Equal(t, i, value.(int))
2932
}
3033

3134
// Check evictions
3235
for i := size; i < size+overwrite; i++ {
33-
c.put(strconv.Itoa(i), i)
36+
c.Put(ctx, strconv.Itoa(i), i)
3437
//c.print()
3538
}
3639
require.Len(t, c.index, size)
3740
require.Len(t, c.entries, size)
3841

3942
for i := 0; i < size-overwrite; i++ {
40-
_, _, ok := c.get(strconv.Itoa(i))
43+
_, ok := c.Get(ctx, strconv.Itoa(i))
4144
require.False(t, ok)
4245
}
4346
for i := size; i < size+overwrite; i++ {
44-
value, _, ok := c.get(strconv.Itoa(i))
47+
value, ok := c.Get(ctx, strconv.Itoa(i))
4548
require.True(t, ok)
4649
require.Equal(t, i, value.(int))
4750
}
4851

4952
// Check updates work
5053
for i := size; i < size+overwrite; i++ {
51-
c.put(strconv.Itoa(i), i*2)
54+
c.Put(ctx, strconv.Itoa(i), i*2)
5255
//c.print()
5356
}
5457
require.Len(t, c.index, size)
5558
require.Len(t, c.entries, size)
5659

5760
for i := size; i < size+overwrite; i++ {
58-
value, _, ok := c.get(strconv.Itoa(i))
61+
value, ok := c.Get(ctx, strconv.Itoa(i))
5962
require.True(t, ok)
6063
require.Equal(t, i*2, value.(int))
6164
}
6265
}
6366

64-
func (c *fifoCache) print() {
67+
func TestFifoCacheExpiry(t *testing.T) {
68+
c := NewFifoCache("test", size, 5*time.Millisecond)
69+
ctx := context.Background()
70+
71+
c.Put(ctx, "0", 0)
72+
73+
value, ok := c.Get(ctx, "0")
74+
require.True(t, ok)
75+
require.Equal(t, 0, value.(int))
76+
77+
// Expire the entry.
78+
time.Sleep(5 * time.Millisecond)
79+
_, ok = c.Get(ctx, strconv.Itoa(0))
80+
require.False(t, ok)
81+
}
82+
83+
func (c *FifoCache) print() {
6584
fmt.Println("first", c.first, "last", c.last)
6685
for i, entry := range c.entries {
6786
fmt.Printf(" %d -> key: %s, value: %v, next: %d, prev: %d\n", i, entry.key, entry.value, entry.next, entry.prev)

0 commit comments

Comments
 (0)