-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
scache.go
145 lines (125 loc) · 3.53 KB
/
scache.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package lcw
import (
"fmt"
"strings"
)
// Scache wraps LoadingCache with partitions (sub-system), and scopes.
// Simplified interface with just 4 funcs - Get, Flush, Stats and Close
type Scache struct {
lc LoadingCache
}
// NewScache creates Scache on top of LoadingCache
func NewScache(lc LoadingCache) *Scache {
return &Scache{lc: lc}
}
// Get retrieves a key from underlying backend
func (m *Scache) Get(key Key, fn func() ([]byte, error)) (data []byte, err error) {
keyStr := key.String()
val, err := m.lc.Get(keyStr, func() (value interface{}, e error) {
return fn()
})
return val.([]byte), err
}
// Stat delegates the call to the underlying cache backend
func (m *Scache) Stat() CacheStat {
return m.lc.Stat()
}
// Close calls Close function of the underlying cache
func (m *Scache) Close() error {
return m.lc.Close()
}
// Flush clears cache and calls postFlushFn async
func (m *Scache) Flush(req FlusherRequest) {
if len(req.scopes) == 0 {
m.lc.Purge()
return
}
// check if fullKey has matching scopes
inScope := func(fullKey string) bool {
key, err := parseKey(fullKey)
if err != nil {
return false
}
for _, s := range req.scopes {
for _, ks := range key.scopes {
if ks == s {
return true
}
}
}
return false
}
for _, k := range m.lc.Keys() {
if inScope(k) {
m.lc.Delete(k) // Keys() returns copy of cache's key, safe to remove directly
}
}
}
// Key for scoped cache. Created foe given partition (can be empty) and set with ID and Scopes.
// example: k := NewKey("sys1").ID(postID).Scopes("last_posts", customer_id)
type Key struct {
id string // the primary part of the key, i.e. usual cache's key
partition string // optional id for a subsystem or cache partition
scopes []string // list of scopes to use in invalidation
}
// NewKey makes base key for given partition. Partition can be omitted.
func NewKey(partition ...string) Key {
if len(partition) == 0 {
return Key{partition: ""}
}
return Key{partition: partition[0]}
}
// ID sets key id
func (k Key) ID(id string) Key {
k.id = id
return k
}
// Scopes of the key
func (k Key) Scopes(scopes ...string) Key {
k.scopes = scopes
return k
}
// String makes full string key from primary key, partition and scopes
// key string made as <partition>@@<id>@@<scope1>$$<scope2>....
func (k Key) String() string {
bld := strings.Builder{}
_, _ = bld.WriteString(k.partition)
_, _ = bld.WriteString("@@")
_, _ = bld.WriteString(k.id)
_, _ = bld.WriteString("@@")
_, _ = bld.WriteString(strings.Join(k.scopes, "$$"))
return bld.String()
}
// parseKey gets compound key string created by Key func and split it to the actual key, partition and scopes
// key string made as <partition>@@<id>@@<scope1>$$<scope2>....
func parseKey(keyStr string) (Key, error) {
elems := strings.Split(keyStr, "@@")
if len(elems) != 3 {
return Key{}, fmt.Errorf("can't parse cache key %s, invalid number of segments %d", keyStr, len(elems))
}
scopes := strings.Split(elems[2], "$$")
if len(scopes) == 1 && scopes[0] == "" {
scopes = []string{}
}
key := Key{
partition: elems[0],
id: elems[1],
scopes: scopes,
}
return key, nil
}
// FlusherRequest used as input for cache.Flush
type FlusherRequest struct {
partition string
scopes []string
}
// Flusher makes new FlusherRequest with empty scopes
func Flusher(partition string) FlusherRequest {
res := FlusherRequest{partition: partition}
return res
}
// Scopes adds scopes to FlusherRequest
func (f FlusherRequest) Scopes(scopes ...string) FlusherRequest {
f.scopes = scopes
return f
}