1
|
|
|
// Package lfucache implements LFU (Least Frequently Used) cache |
2
|
|
|
package lfucache |
3
|
|
|
|
4
|
|
|
import ( |
5
|
|
|
"encoding/json" |
6
|
|
|
"github.com/arazmj/gerdu/cache" |
7
|
|
|
"github.com/arazmj/gerdu/dlinklist" |
8
|
|
|
"github.com/arazmj/gerdu/metrics" |
9
|
|
|
"github.com/hashicorp/raft" |
10
|
|
|
"github.com/inhies/go-bytesize" |
11
|
|
|
"io" |
12
|
|
|
"sync" |
13
|
|
|
) |
14
|
|
|
|
15
|
|
|
// LFUCache data structure |
16
|
|
|
type LFUCache struct { |
17
|
|
|
sync.RWMutex |
18
|
|
|
cache.UnImplementedCache |
19
|
|
|
size bytesize.ByteSize |
20
|
|
|
capacity bytesize.ByteSize |
21
|
|
|
node map[string]*dlinklist.Node |
22
|
|
|
freq map[int]*dlinklist.DLinkedList |
23
|
|
|
minFreq int |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
// NewCache LFUCache constructor |
27
|
|
|
func NewCache(capacity bytesize.ByteSize) *LFUCache { |
28
|
|
|
return &LFUCache{ |
29
|
|
|
size: 0, |
30
|
|
|
capacity: capacity, |
31
|
|
|
node: map[string]*dlinklist.Node{}, |
32
|
|
|
freq: map[int]*dlinklist.DLinkedList{}, |
33
|
|
|
minFreq: 0, |
34
|
|
|
} |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
// This is a helper function that used in the following two cases: |
38
|
|
|
// |
39
|
|
|
// 1. when Get(key)` is called; and |
40
|
|
|
// 2. when Put(key, value)` is called and the key exists. |
41
|
|
|
// |
42
|
|
|
// The common point of these two cases is that: |
43
|
|
|
// |
44
|
|
|
// 1. no new node comes in, and |
45
|
|
|
// 2. the node is visited one more times -> node.freq changed -> |
46
|
|
|
// thus the place of this node will change |
47
|
|
|
// |
48
|
|
|
// The logic of this function is: |
49
|
|
|
// |
50
|
|
|
// 1. pop the node from the old DLinkedList (with freq `f`) |
51
|
|
|
// 2. append the node to new DLinkedList (with freq `f+1`) |
52
|
|
|
// 3. if old DLinkedList has size 0 and minFreq is `f`, |
53
|
|
|
// update minFreq to `f+1` |
54
|
|
|
// |
55
|
|
|
// All of the above operations took O(1) time. |
56
|
|
|
func (c *LFUCache) update(node *dlinklist.Node) { |
57
|
|
|
freq := node.Freq |
58
|
|
|
|
59
|
|
|
c.freq[freq].RemoveNode(node) |
60
|
|
|
if v, _ := c.freq[freq]; c.minFreq == freq && v.Size() == 0 { |
61
|
|
|
delete(c.freq, freq) |
62
|
|
|
c.minFreq++ |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
node.Freq++ |
66
|
|
|
freq = node.Freq |
67
|
|
|
if _, ok := c.freq[freq]; !ok { |
68
|
|
|
c.freq[freq] = dlinklist.NewLinkedList() |
69
|
|
|
} |
70
|
|
|
c.freq[freq].AddNode(node) |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
// Get through checking node[key], we can get the node in O(1) time. |
74
|
|
|
// Just performs update, then we can return the value of node. |
75
|
|
|
func (c *LFUCache) Get(key string) (value string, ok bool) { |
76
|
|
|
defer c.Unlock() |
77
|
|
|
c.Lock() |
78
|
|
|
|
79
|
|
|
if _, ok := c.node[key]; !ok { |
80
|
|
|
metrics.Miss.Inc() |
81
|
|
|
return "", false |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
metrics.Hits.Inc() |
85
|
|
|
node := c.node[key] |
86
|
|
|
c.update(node) |
87
|
|
|
return node.Value, true |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
// Put If `key` already exists in self._node, we do the same operations as `get`, except |
91
|
|
|
// updating the node.val to new value. Otherwise |
92
|
|
|
// 1. if the cache reaches its capacity, pop the least frequently used item. (*) |
93
|
|
|
// 2. add new node to self._node |
94
|
|
|
// 3. add new node to the DLinkedList with frequency 1 |
95
|
|
|
// 4. reset minFreq to 1 |
96
|
|
|
// |
97
|
|
|
// (*) How to pop the least frequently used item? Two facts: |
98
|
|
|
// 1. we maintain the minFreq, the minimum possible frequency in cache. |
99
|
|
|
// 2. All cache with the same frequency are stored as a DLinkedList, with |
100
|
|
|
// recently used order (Always append at head) |
101
|
|
|
// 3. The tail of the DLinkedList with minFreq is the least |
102
|
|
|
//recently used one, pop it. |
103
|
|
|
func (c *LFUCache) Put(key, value string) (created bool) { |
104
|
|
|
defer c.Unlock() |
105
|
|
|
c.Lock() |
106
|
|
|
if c.capacity == 0 { |
107
|
|
|
return |
108
|
|
|
} |
109
|
|
|
if _, ok := c.node[key]; ok { |
110
|
|
|
metrics.Hits.Inc() |
111
|
|
|
node := c.node[key] |
112
|
|
|
c.update(node) |
113
|
|
|
node.Value = value |
114
|
|
|
created = false |
115
|
|
|
} else { |
116
|
|
|
c.size += bytesize.ByteSize(len(value)) |
117
|
|
|
for c.size > c.capacity { |
118
|
|
|
minList, ok := c.freq[c.minFreq] |
119
|
|
|
metrics.Deletes.Inc() |
120
|
|
|
if !ok || minList.Size() == 0 { |
121
|
|
|
delete(c.freq, c.minFreq) |
122
|
|
|
c.minFreq++ |
123
|
|
|
} else { |
124
|
|
|
node := minList.PopTail() |
125
|
|
|
freq := node.Freq |
126
|
|
|
if v, _ := c.freq[c.minFreq]; c.minFreq == freq && v.Size() == 0 { |
127
|
|
|
delete(c.freq, freq) |
128
|
|
|
c.minFreq++ |
129
|
|
|
} |
130
|
|
|
c.size -= bytesize.ByteSize(len(node.Value)) |
131
|
|
|
delete(c.node, node.Key) |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
metrics.Adds.Inc() |
135
|
|
|
node := &dlinklist.Node{ |
136
|
|
|
Key: key, |
137
|
|
|
Value: value, |
138
|
|
|
Freq: 1, |
139
|
|
|
} |
140
|
|
|
c.node[key] = node |
141
|
|
|
if _, ok := c.freq[1]; !ok { |
142
|
|
|
c.freq[1] = dlinklist.NewLinkedList() |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
c.freq[1].AddNode(node) |
146
|
|
|
c.minFreq = 1 |
147
|
|
|
created = true |
148
|
|
|
} |
149
|
|
|
return created |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
//Delete deletes a key from LFU cache |
153
|
|
|
func (c *LFUCache) Delete(key string) (ok bool) { |
154
|
|
|
node, ok := c.node[key] |
155
|
|
|
if !ok { |
156
|
|
|
return false |
157
|
|
|
} |
158
|
|
|
freq := node.Freq |
159
|
|
|
if v, _ := c.freq[c.minFreq]; c.minFreq == freq && v.Size() == 0 { |
160
|
|
|
metrics.Deletes.Inc() |
161
|
|
|
delete(c.freq, freq) |
162
|
|
|
c.minFreq++ |
163
|
|
|
} |
164
|
|
|
delete(c.node, key) |
165
|
|
|
return true |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
func (c *LFUCache) Snapshot() (raft.FSMSnapshot, error) { |
169
|
|
|
c.RLock() |
170
|
|
|
defer c.RUnlock() |
171
|
|
|
|
172
|
|
|
o := make(map[string]string) |
173
|
|
|
|
174
|
|
|
for k, v := range c.node { |
175
|
|
|
o[k] = v.Value |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return &fsmSnapshot{store: o}, nil |
179
|
|
|
|
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
func (c *LFUCache) Restore(closer io.ReadCloser) error { |
183
|
|
|
o := make(map[string]string) |
184
|
|
|
if err := json.NewDecoder(closer).Decode(&o); err != nil { |
185
|
|
|
return err |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
// Set the state from the snapshot, no lock required according to |
189
|
|
|
// Hashicorp docs. |
190
|
|
|
for k, v := range o { |
191
|
|
|
c.Put(k, v) |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return nil |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
type fsmSnapshot struct { |
198
|
|
|
store map[string]string |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
func (f *fsmSnapshot) Persist(sink raft.SnapshotSink) error { |
202
|
|
|
err := func() error { |
203
|
|
|
// Encode data. |
204
|
|
|
b, err := json.Marshal(f.store) |
205
|
|
|
if err != nil { |
206
|
|
|
return err |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
// Write data to sink. |
210
|
|
|
if _, err := sink.Write(b); err != nil { |
211
|
|
|
return err |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
// Close the sink. |
215
|
|
|
return sink.Close() |
216
|
|
|
}() |
217
|
|
|
|
218
|
|
|
if err != nil { |
219
|
|
|
sink.Cancel() |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
return err |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
func (f *fsmSnapshot) Release() {} |
226
|
|
|
|