Skip to content

Commit 13363b2

Browse files
committed
feat: refactoring use memory
1 parent 98207cb commit 13363b2

File tree

7 files changed

+254
-61
lines changed

7 files changed

+254
-61
lines changed

analytics.go

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,31 @@ package multi_tier_caching
22

33
import (
44
"sync"
5+
"time"
56
)
67

78
type CacheAnalytics struct {
8-
hits, misses int
9-
mu sync.Mutex
9+
hits, misses int
10+
frequencies map[string]int // Request frequency for each keyword
11+
recentFreq sync.Map // Request frequency in the last minute
12+
mu sync.Mutex
13+
lastResetTime time.Time
1014
}
1115

1216
func NewCacheAnalytics() *CacheAnalytics {
13-
return &CacheAnalytics{}
17+
return &CacheAnalytics{
18+
frequencies: make(map[string]int),
19+
lastResetTime: time.Now(),
20+
}
1421
}
1522

16-
func (ca *CacheAnalytics) LogHit() {
23+
func (ca *CacheAnalytics) LogHit(key string) {
1724
ca.mu.Lock()
1825
defer ca.mu.Unlock()
1926
ca.hits++
27+
ca.frequencies[key]++
28+
valRecent, _ := ca.recentFreq.LoadOrStore(key, 0)
29+
ca.recentFreq.Store(key, valRecent.(int)+1)
2030
}
2131

2232
func (ca *CacheAnalytics) LogMiss() {
@@ -30,3 +40,56 @@ func (ca *CacheAnalytics) GetStats() (int, int) {
3040
defer ca.mu.Unlock()
3141
return ca.hits, ca.misses
3242
}
43+
44+
func (ca *CacheAnalytics) GetFrequency(key string) int {
45+
ca.mu.Lock()
46+
defer ca.mu.Unlock()
47+
return ca.frequencies[key]
48+
}
49+
50+
// GetTotalFrequency Returns the total number of times a key has been accessed.
51+
func (ca *CacheAnalytics) GetTotalFrequency(key string) int {
52+
ca.mu.Lock()
53+
defer ca.mu.Unlock()
54+
return ca.frequencies[key]
55+
}
56+
57+
// GetFrequencyPerMinute Returns the request rate per minute and resets the counters
58+
func (ca *CacheAnalytics) GetFrequencyPerMinute() map[string]int {
59+
ca.mu.Lock()
60+
defer ca.mu.Unlock()
61+
62+
now := time.Now()
63+
if now.Sub(ca.lastResetTime).Minutes() < 1 {
64+
return nil // Not a minute has passed yet
65+
}
66+
67+
freqCopy := make(map[string]int)
68+
// Iterating over the sync.Map using Range
69+
ca.recentFreq.Range(func(k, v interface{}) bool {
70+
if key, ok := k.(string); ok {
71+
if count, ok := v.(int); ok {
72+
freqCopy[key] = count
73+
}
74+
}
75+
return true // continue iteration
76+
})
77+
78+
// Resetting temporary data
79+
ca.recentFreq = sync.Map{}
80+
ca.lastResetTime = now
81+
82+
return freqCopy
83+
}
84+
85+
// ResetFrequencies We reset the counters every minute
86+
func (ca *CacheAnalytics) ResetFrequencies() {
87+
ca.mu.Lock()
88+
defer ca.mu.Unlock()
89+
90+
now := time.Now()
91+
if now.Sub(ca.lastResetTime).Minutes() >= 1 {
92+
ca.frequencies = make(map[string]int) // Clearing the counters
93+
ca.lastResetTime = now
94+
}
95+
}

cache.go

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,61 +10,126 @@ import (
1010
var ErrCacheMiss = errors.New("cache miss")
1111

1212
type MultiTierCache struct {
13-
layers []CacheLayer
14-
db Database
15-
bloomFilter *BloomFilter
16-
writeQueue *WriteQueue
17-
analytics *CacheAnalytics
18-
ttlManager *TTLManager
13+
layers []CacheLayer // Cache layers sorted from hot to cold
14+
db Database
15+
bloomFilter *BloomFilter
16+
writeQueue *WriteQueue
17+
analytics *CacheAnalytics
18+
ttlManager *TTLManager
19+
freqThresholds []int // Request rate thresholds for transition between layers
1920
}
2021

21-
func NewMultiTierCache(layers []CacheLayer, db Database) *MultiTierCache {
22-
return &MultiTierCache{
23-
layers: layers,
24-
db: db,
25-
bloomFilter: NewBloomFilter(1000000, 5),
26-
writeQueue: NewWriteQueue(func(task WriteTask) { _ = db.Set(context.Background(), task.Key, task.Value, 0) }),
27-
analytics: NewCacheAnalytics(),
28-
ttlManager: NewTTLManager(),
22+
func NewMultiTierCache(layers []CacheLayer, db Database, thresholds []int) *MultiTierCache {
23+
cache := &MultiTierCache{
24+
layers: layers,
25+
db: db,
26+
bloomFilter: NewBloomFilter(1000000, 5),
27+
writeQueue: NewWriteQueue(func(task WriteTask) { _ = db.Set(context.Background(), task.Key, task.Value, 0) }),
28+
analytics: NewCacheAnalytics(),
29+
ttlManager: NewTTLManager(),
30+
freqThresholds: thresholds,
2931
}
32+
33+
// Starting a background process to clean frequencies
34+
go func() {
35+
for {
36+
time.Sleep(60 * time.Second)
37+
cache.analytics.ResetFrequencies()
38+
}
39+
}()
40+
41+
return cache
3042
}
3143

3244
func (c *MultiTierCache) Get(ctx context.Context, key string) (string, error) {
45+
// Searching in cache layers
3346
for _, layer := range c.layers {
3447
value, err := layer.Get(ctx, key)
3548
if err == nil {
36-
c.analytics.LogHit()
49+
c.analytics.LogHit(key) // Logging a hit with a key
3750
return value, nil
3851
}
3952
}
40-
53+
// Checking in Bloom filter
4154
if !c.bloomFilter.Exists(key) {
4255
c.analytics.LogMiss()
4356
return "", ErrCacheMiss
4457
}
45-
58+
// Getting from DB
4659
value, err := c.db.Get(ctx, key)
4760
if err != nil {
4861
return "", err
4962
}
5063

51-
err = c.Set(ctx, key, value)
64+
// Defining target layers based on frequency
65+
freq := c.analytics.GetFrequency(key)
66+
targetLayers := c.selectTargetLayers(freq)
67+
68+
// Update cache only in selected layers
69+
err = c.updateLayers(ctx, key, value, targetLayers)
5270
if err != nil {
5371
return "", err
5472
}
73+
5574
c.bloomFilter.Add(key)
5675
return value, nil
5776
}
5877

5978
func (c *MultiTierCache) Set(ctx context.Context, key, value string) error {
60-
ttl := c.ttlManager.AdjustTTL(key)
79+
freq := c.analytics.GetFrequency(key)
80+
ttl := c.calculateAdaptiveTTL(freq) // New Method for Adaptive TTL
6181
ttlSeconds := time.Duration(ttl) * time.Second
82+
6283
for _, layer := range c.layers {
6384
err := layer.Set(ctx, key, value, ttlSeconds)
6485
if err != nil {
6586
return err
6687
}
6788
}
89+
90+
c.writeQueue.Enqueue(WriteTask{Key: key, Value: value})
91+
return nil
92+
}
93+
94+
func (c *MultiTierCache) selectTargetLayers(freq int) []CacheLayer {
95+
var layers []CacheLayer
96+
97+
// Adaptive movement: frequently requested data stays in fast layers
98+
for i := len(c.freqThresholds) - 1; i >= 0; i-- {
99+
if freq >= c.freqThresholds[i] {
100+
layers = append(layers, c.layers[i])
101+
}
102+
}
103+
return layers
104+
}
105+
106+
func (c *MultiTierCache) updateLayers(ctx context.Context, key, value string, targetLayers []CacheLayer) error {
107+
ttl := c.ttlManager.AdjustTTL(key)
108+
ttlSeconds := time.Duration(ttl) * time.Second
109+
110+
// We update only target layers
111+
for _, layer := range targetLayers {
112+
if err := layer.Set(ctx, key, value, ttlSeconds); err != nil {
113+
return err
114+
}
115+
}
116+
117+
// Clearing the key from layers not included in the target
118+
for _, layer := range c.layers {
119+
if !containsLayer(targetLayers, layer) {
120+
layer.Delete(ctx, key)
121+
}
122+
}
123+
68124
c.writeQueue.Enqueue(WriteTask{Key: key, Value: value})
69125
return nil
70126
}
127+
128+
func containsLayer(layers []CacheLayer, target CacheLayer) bool {
129+
for _, l := range layers {
130+
if l == target {
131+
return true
132+
}
133+
}
134+
return false
135+
}

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,29 @@ go 1.23.1
44

55
require (
66
github.com/bits-and-blooms/bloom/v3 v3.7.0
7-
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
7+
github.com/dgraph-io/ristretto v0.2.0
88
github.com/jackc/pgx/v5 v5.7.2
99
github.com/redis/go-redis/v9 v9.7.0
10-
github.com/stretchr/testify v1.8.1
10+
github.com/stretchr/testify v1.8.4
1111
)
1212

1313
require (
1414
github.com/bits-and-blooms/bitset v1.10.0 // indirect
1515
github.com/cespare/xxhash/v2 v2.2.0 // indirect
1616
github.com/davecgh/go-spew v1.1.1 // indirect
1717
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
18+
github.com/dustin/go-humanize v1.0.1 // indirect
1819
github.com/jackc/pgpassfile v1.0.0 // indirect
1920
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
2021
github.com/jackc/puddle/v2 v2.2.2 // indirect
2122
github.com/kr/text v0.2.0 // indirect
23+
github.com/pkg/errors v0.9.1 // indirect
2224
github.com/pmezard/go-difflib v1.0.0 // indirect
2325
github.com/rogpeppe/go-internal v1.13.1 // indirect
2426
github.com/stretchr/objx v0.5.0 // indirect
2527
golang.org/x/crypto v0.31.0 // indirect
2628
golang.org/x/sync v0.10.0 // indirect
29+
golang.org/x/sys v0.28.0 // indirect
2730
golang.org/x/text v0.21.0 // indirect
2831
gopkg.in/yaml.v3 v3.0.1 // indirect
2932
)

go.sum

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsy
22
github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
33
github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls=
44
github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg=
5-
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
6-
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
75
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
86
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
97
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -14,8 +12,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
1412
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1513
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1614
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15+
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
16+
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
17+
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
18+
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
1719
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
1820
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
21+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
22+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
1923
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
2024
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
2125
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -28,6 +32,8 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
2832
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
2933
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3034
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
35+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
36+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
3137
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3238
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3339
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
@@ -42,14 +48,16 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
4248
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
4349
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
4450
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
45-
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
46-
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
51+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
52+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
4753
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
4854
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
4955
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
5056
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
5157
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
5258
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
59+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
60+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5361
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
5462
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
5563
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)