7 "github.com/hashicorp/golang-lru/simplelru"
11 // Default2QRecentRatio is the ratio of the 2Q cache dedicated
12 // to recently added entries that have only been accessed once.
13 Default2QRecentRatio = 0.25
15 // Default2QGhostEntries is the default ratio of ghost
16 // entries kept to track entries recently evicted
17 Default2QGhostEntries = 0.50
20 // TwoQueueCache is a thread-safe fixed size 2Q cache.
21 // 2Q is an enhancement over the standard LRU cache
22 // in that it tracks both frequently and recently used
23 // entries separately. This avoids a burst in access to new
24 // entries from evicting frequently used entries. It adds some
25 // additional tracking overhead to the standard LRU cache, and is
26 // computationally about 2x the cost, and adds some metadata over
27 // head. The ARCCache is similar, but does not require setting any
29 type TwoQueueCache struct {
33 recent simplelru.LRUCache
34 frequent simplelru.LRUCache
35 recentEvict simplelru.LRUCache
39 // New2Q creates a new TwoQueueCache using the default
40 // values for the parameters.
41 func New2Q(size int) (*TwoQueueCache, error) {
42 return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
45 // New2QParams creates a new TwoQueueCache using the provided
47 func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) {
49 return nil, fmt.Errorf("invalid size")
51 if recentRatio < 0.0 || recentRatio > 1.0 {
52 return nil, fmt.Errorf("invalid recent ratio")
54 if ghostRatio < 0.0 || ghostRatio > 1.0 {
55 return nil, fmt.Errorf("invalid ghost ratio")
58 // Determine the sub-sizes
59 recentSize := int(float64(size) * recentRatio)
60 evictSize := int(float64(size) * ghostRatio)
63 recent, err := simplelru.NewLRU(size, nil)
67 frequent, err := simplelru.NewLRU(size, nil)
71 recentEvict, err := simplelru.NewLRU(evictSize, nil)
76 // Initialize the cache
79 recentSize: recentSize,
82 recentEvict: recentEvict,
87 // Get looks up a key's value from the cache.
88 func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
92 // Check if this is a frequent value
93 if val, ok := c.frequent.Get(key); ok {
97 // If the value is contained in recent, then we
98 // promote it to frequent
99 if val, ok := c.recent.Peek(key); ok {
101 c.frequent.Add(key, val)
109 // Add adds a value to the cache.
110 func (c *TwoQueueCache) Add(key, value interface{}) {
112 defer c.lock.Unlock()
114 // Check if the value is frequently used already,
115 // and just update the value
116 if c.frequent.Contains(key) {
117 c.frequent.Add(key, value)
121 // Check if the value is recently used, and promote
122 // the value into the frequent list
123 if c.recent.Contains(key) {
125 c.frequent.Add(key, value)
129 // If the value was recently evicted, add it to the
130 // frequently used list
131 if c.recentEvict.Contains(key) {
133 c.recentEvict.Remove(key)
134 c.frequent.Add(key, value)
138 // Add to the recently seen list
140 c.recent.Add(key, value)
144 // ensureSpace is used to ensure we have space in the cache
145 func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
146 // If we have space, nothing to do
147 recentLen := c.recent.Len()
148 freqLen := c.frequent.Len()
149 if recentLen+freqLen < c.size {
153 // If the recent buffer is larger than
154 // the target, evict from there
155 if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
156 k, _, _ := c.recent.RemoveOldest()
157 c.recentEvict.Add(k, nil)
161 // Remove from the frequent list otherwise
162 c.frequent.RemoveOldest()
165 // Len returns the number of items in the cache.
166 func (c *TwoQueueCache) Len() int {
168 defer c.lock.RUnlock()
169 return c.recent.Len() + c.frequent.Len()
172 // Keys returns a slice of the keys in the cache.
173 // The frequently used keys are first in the returned slice.
174 func (c *TwoQueueCache) Keys() []interface{} {
176 defer c.lock.RUnlock()
177 k1 := c.frequent.Keys()
178 k2 := c.recent.Keys()
179 return append(k1, k2...)
182 // Remove removes the provided key from the cache.
183 func (c *TwoQueueCache) Remove(key interface{}) {
185 defer c.lock.Unlock()
186 if c.frequent.Remove(key) {
189 if c.recent.Remove(key) {
192 if c.recentEvict.Remove(key) {
197 // Purge is used to completely clear the cache.
198 func (c *TwoQueueCache) Purge() {
200 defer c.lock.Unlock()
203 c.recentEvict.Purge()
206 // Contains is used to check if the cache contains a key
207 // without updating recency or frequency.
208 func (c *TwoQueueCache) Contains(key interface{}) bool {
210 defer c.lock.RUnlock()
211 return c.frequent.Contains(key) || c.recent.Contains(key)
214 // Peek is used to inspect the cache value of a key
215 // without updating recency or frequency.
216 func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) {
218 defer c.lock.RUnlock()
219 if val, ok := c.frequent.Peek(key); ok {
222 return c.recent.Peek(key)