-
Notifications
You must be signed in to change notification settings - Fork 176
/
cache.go
125 lines (103 loc) · 2.93 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package badger
import (
"fmt"
"github.com/dgraph-io/badger/v2"
lru "github.com/hashicorp/golang-lru"
"github.com/onflow/flow-go/module"
"github.com/onflow/flow-go/module/metrics"
)
func withLimit(limit uint) func(*Cache) {
return func(c *Cache) {
c.limit = limit
}
}
type storeFunc func(key interface{}, val interface{}) func(*badger.Txn) error
func withStore(store storeFunc) func(*Cache) {
return func(c *Cache) {
c.store = store
}
}
func noStore(key interface{}, val interface{}) func(*badger.Txn) error {
return func(tx *badger.Txn) error {
return fmt.Errorf("no store function for cache put available")
}
}
type retrieveFunc func(key interface{}) func(*badger.Txn) (interface{}, error)
func withRetrieve(retrieve retrieveFunc) func(*Cache) {
return func(c *Cache) {
c.retrieve = retrieve
}
}
func noRetrieve(key interface{}) func(*badger.Txn) (interface{}, error) {
return func(tx *badger.Txn) (interface{}, error) {
return nil, fmt.Errorf("no retrieve function for cache get available")
}
}
func withResource(resource string) func(*Cache) {
return func(c *Cache) {
c.resource = resource
}
}
type Cache struct {
metrics module.CacheMetrics
limit uint
store storeFunc
retrieve retrieveFunc
resource string
cache *lru.Cache
}
func newCache(collector module.CacheMetrics, options ...func(*Cache)) *Cache {
c := Cache{
metrics: collector,
limit: 1000,
store: noStore,
retrieve: noRetrieve,
resource: metrics.ResourceUndefined,
}
for _, option := range options {
option(&c)
}
c.cache, _ = lru.New(int(c.limit))
c.metrics.CacheEntries(c.resource, uint(c.cache.Len()))
return &c
}
// Get will try to retrieve the resource from cache first, and then from the
// injected
func (c *Cache) Get(key interface{}) func(*badger.Txn) (interface{}, error) {
return func(tx *badger.Txn) (interface{}, error) {
// check if we have it in the cache
resource, cached := c.cache.Get(key)
if cached {
c.metrics.CacheHit(c.resource)
return resource, nil
}
// get it from the database
c.metrics.CacheMiss(c.resource)
resource, err := c.retrieve(key)(tx)
if err != nil {
return nil, fmt.Errorf("could not retrieve resource: %w", err)
}
// cache the resource and eject least recently used one if we reached limit
evicted := c.cache.Add(key, resource)
if !evicted {
c.metrics.CacheEntries(c.resource, uint(c.cache.Len()))
}
return resource, nil
}
}
// Put will add an resource to the cache with the given ID.
func (c *Cache) Put(key interface{}, resource interface{}) func(*badger.Txn) error {
return func(tx *badger.Txn) error {
// try to store the resource
err := c.store(key, resource)(tx)
if err != nil {
return fmt.Errorf("could not store resource: %w", err)
}
// cache the resource and eject least recently used one if we reached limit
evicted := c.cache.Add(key, resource)
if !evicted {
c.metrics.CacheEntries(c.resource, uint(c.cache.Len()))
}
return nil
}
}