Skip to content

Commit b1cc7f1

Browse files
authored
feat(cache): make LRU cache config-driven (#572)
1 parent b8d907e commit b1cc7f1

File tree

3 files changed

+82
-23
lines changed

3 files changed

+82
-23
lines changed

utils/cache/config.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2016-2019 Uber Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package cache
15+
16+
import "time"
17+
18+
// LRUCacheConfig defines LRU cache configuration.
19+
type LRUCacheConfig struct {
20+
// Size is the maximum number of entries in the LRU cache.
21+
Size int `yaml:"size"`
22+
// TTL is how long an entry stays cached before it expires.
23+
TTL time.Duration `yaml:"ttl"`
24+
}
25+
26+
func (c LRUCacheConfig) applyDefaults() LRUCacheConfig {
27+
if c.Size == 0 {
28+
c.Size = 300
29+
}
30+
if c.TTL == 0 {
31+
c.TTL = 5 * time.Minute
32+
}
33+
return c
34+
}

utils/cache/lru.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,18 @@ import (
2424
// concurrent reads are allowed while writes get exclusive access.
2525
type LRUCache struct {
2626
mu sync.RWMutex
27+
config LRUCacheConfig
2728
entries map[string]time.Time // key -> expiration time
2829
lruOrder []string // keys in LRU order (oldest first)
29-
maxSize int
30-
ttl time.Duration
3130
}
3231

33-
// NewLRUCache creates a new LRU cache with the specified maximum size and TTL.
34-
func NewLRUCache(maxSize int, ttl time.Duration) *LRUCache {
32+
// NewLRUCache creates a new LRU cache from the provided configuration.
33+
func NewLRUCache(cfg LRUCacheConfig) *LRUCache {
34+
cfg = cfg.applyDefaults()
3535
return &LRUCache{
36+
config: cfg,
3637
entries: make(map[string]time.Time),
37-
lruOrder: make([]string, 0, maxSize),
38-
maxSize: maxSize,
39-
ttl: ttl,
38+
lruOrder: make([]string, 0, cfg.Size),
4039
}
4140
}
4241

@@ -55,13 +54,13 @@ func (c *LRUCache) Has(key string) bool {
5554

5655
// Add marks a key as cached. This operation uses a write lock for exclusive access.
5756
// If the key already exists, its expiration time is updated and it's moved to the
58-
// end of the LRU order. If the cache exceeds maxSize, oldest entries are evicted.
57+
// end of the LRU order. If the cache exceeds config.Size, oldest entries are evicted.
5958
func (c *LRUCache) Add(key string) {
6059
c.mu.Lock()
6160
defer c.mu.Unlock()
6261

6362
now := time.Now()
64-
expireTime := now.Add(c.ttl)
63+
expireTime := now.Add(c.config.TTL)
6564

6665
// If key already exists, update expiration and move to end
6766
if _, exists := c.entries[key]; exists {
@@ -130,7 +129,7 @@ func (c *LRUCache) evict() {
130129
}
131130

132131
// Enforce size limit by removing oldest entries
133-
for len(c.entries) > c.maxSize {
132+
for len(c.entries) > c.config.Size {
134133
oldest := c.lruOrder[0]
135134
delete(c.entries, oldest)
136135
c.lruOrder = c.lruOrder[1:]

utils/cache/lru_test.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,34 @@ import (
2222
"github.com/stretchr/testify/require"
2323
)
2424

25+
func TestLRUCacheConfig_ApplyDefaults(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
input LRUCacheConfig
29+
expected LRUCacheConfig
30+
}{
31+
{
32+
name: "zero values get defaults",
33+
input: LRUCacheConfig{},
34+
expected: LRUCacheConfig{Size: 300, TTL: 5 * time.Minute},
35+
},
36+
{
37+
name: "positive values are preserved",
38+
input: LRUCacheConfig{Size: 500, TTL: 10 * time.Minute},
39+
expected: LRUCacheConfig{Size: 500, TTL: 10 * time.Minute},
40+
},
41+
}
42+
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
result := tt.input.applyDefaults()
46+
require.Equal(t, tt.expected, result)
47+
})
48+
}
49+
}
50+
2551
func TestLRUCache_Basic(t *testing.T) {
26-
cache := NewLRUCache(3, time.Hour)
52+
cache := NewLRUCache(LRUCacheConfig{Size: 3, TTL: time.Hour})
2753

2854
// Initially empty
2955
require.Equal(t, 0, cache.Size())
@@ -45,7 +71,7 @@ func TestLRUCache_Basic(t *testing.T) {
4571
}
4672

4773
func TestLRUCache_SizeLimit(t *testing.T) {
48-
cache := NewLRUCache(2, time.Hour)
74+
cache := NewLRUCache(LRUCacheConfig{Size: 2, TTL: time.Hour})
4975

5076
// Fill cache to capacity
5177
cache.Add("key1")
@@ -61,7 +87,7 @@ func TestLRUCache_SizeLimit(t *testing.T) {
6187
}
6288

6389
func TestLRUCache_LRUOrdering(t *testing.T) {
64-
cache := NewLRUCache(2, time.Hour)
90+
cache := NewLRUCache(LRUCacheConfig{Size: 2, TTL: time.Hour})
6591

6692
// Add two keys
6793
cache.Add("key1")
@@ -78,7 +104,7 @@ func TestLRUCache_LRUOrdering(t *testing.T) {
78104
}
79105

80106
func TestLRUCache_TTL(t *testing.T) {
81-
cache := NewLRUCache(10, 50*time.Millisecond)
107+
cache := NewLRUCache(LRUCacheConfig{Size: 10, TTL: 50 * time.Millisecond})
82108

83109
cache.Add("key1")
84110
require.True(t, cache.Has("key1"))
@@ -89,7 +115,7 @@ func TestLRUCache_TTL(t *testing.T) {
89115
}
90116

91117
func TestLRUCache_Delete(t *testing.T) {
92-
cache := NewLRUCache(10, time.Hour)
118+
cache := NewLRUCache(LRUCacheConfig{Size: 10, TTL: time.Hour})
93119

94120
cache.Add("key1")
95121
cache.Add("key2")
@@ -108,7 +134,7 @@ func TestLRUCache_Delete(t *testing.T) {
108134
}
109135

110136
func TestLRUCache_Clear(t *testing.T) {
111-
cache := NewLRUCache(10, time.Hour)
137+
cache := NewLRUCache(LRUCacheConfig{Size: 10, TTL: time.Hour})
112138

113139
cache.Add("key1")
114140
cache.Add("key2")
@@ -123,7 +149,7 @@ func TestLRUCache_Clear(t *testing.T) {
123149
}
124150

125151
func TestLRUCache_ConcurrentAccess(t *testing.T) {
126-
cache := NewLRUCache(100, time.Hour)
152+
cache := NewLRUCache(LRUCacheConfig{Size: 100, TTL: time.Hour})
127153

128154
// Test concurrent reads and writes
129155
done := make(chan bool, 10)
@@ -162,7 +188,7 @@ func TestLRUCache_ConcurrentAccess(t *testing.T) {
162188
// Benchmark tests for measuring LRU cache performance
163189

164190
func BenchmarkLRUCache_Add(b *testing.B) {
165-
cache := NewLRUCache(1000, time.Hour)
191+
cache := NewLRUCache(LRUCacheConfig{Size: 1000, TTL: time.Hour})
166192

167193
b.ResetTimer()
168194
for i := 0; i < b.N; i++ {
@@ -171,7 +197,7 @@ func BenchmarkLRUCache_Add(b *testing.B) {
171197
}
172198

173199
func BenchmarkLRUCache_Has_Hit(b *testing.B) {
174-
cache := NewLRUCache(1000, time.Hour)
200+
cache := NewLRUCache(LRUCacheConfig{Size: 1000, TTL: time.Hour})
175201

176202
// Pre-populate cache
177203
for i := 0; i < 500; i++ {
@@ -185,7 +211,7 @@ func BenchmarkLRUCache_Has_Hit(b *testing.B) {
185211
}
186212

187213
func BenchmarkLRUCache_Has_Miss(b *testing.B) {
188-
cache := NewLRUCache(1000, time.Hour)
214+
cache := NewLRUCache(LRUCacheConfig{Size: 1000, TTL: time.Hour})
189215

190216
// Pre-populate cache
191217
for i := 0; i < 500; i++ {
@@ -199,7 +225,7 @@ func BenchmarkLRUCache_Has_Miss(b *testing.B) {
199225
}
200226

201227
func BenchmarkLRUCache_Mixed_ReadHeavy(b *testing.B) {
202-
cache := NewLRUCache(1000, time.Hour)
228+
cache := NewLRUCache(LRUCacheConfig{Size: 1000, TTL: time.Hour})
203229

204230
// Pre-populate cache
205231
for i := 0; i < 100; i++ {
@@ -217,7 +243,7 @@ func BenchmarkLRUCache_Mixed_ReadHeavy(b *testing.B) {
217243
}
218244

219245
func BenchmarkLRUCache_ConcurrentAccess(b *testing.B) {
220-
cache := NewLRUCache(1000, time.Hour)
246+
cache := NewLRUCache(LRUCacheConfig{Size: 1000, TTL: time.Hour})
221247

222248
// Pre-populate cache
223249
for i := 0; i < 100; i++ {
@@ -240,7 +266,7 @@ func BenchmarkLRUCache_ConcurrentAccess(b *testing.B) {
240266
}
241267

242268
func BenchmarkLRUCache_EvictionPressure(b *testing.B) {
243-
cache := NewLRUCache(50, time.Hour) // Small cache to force evictions
269+
cache := NewLRUCache(LRUCacheConfig{Size: 50, TTL: time.Hour}) // Small cache to force evictions
244270

245271
b.ResetTimer()
246272
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)