Skip to content

Commit 95cace4

Browse files
committed
feat(storers): Add memcached backend to NutsMemcached
In this commit cache values are moved to memcached and only keys are kept in Nuts.
1 parent 6fc5b9b commit 95cace4

File tree

3 files changed

+100
-33
lines changed

3 files changed

+100
-33
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/darkweak/souin
33
go 1.19
44

55
require (
6+
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
67
github.com/buraksezer/olric v0.5.4
78
github.com/dgraph-io/badger/v3 v3.2103.5
89
github.com/dgraph-io/ristretto v0.1.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
2727
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
2828
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
2929
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
30+
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
31+
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
3032
github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU=
3133
github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
3234
github.com/buraksezer/olric v0.5.4 h1:LDgLIfVoyol4qzdNirrrDUKqzFw0yDsa7ukvLrpP4cU=

pkg/storage/nutsMemcachedProvider.go

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/bradfitz/gomemcache/memcache"
1112
t "github.com/darkweak/souin/configurationtypes"
1213
"github.com/darkweak/souin/pkg/rfc"
1314
"github.com/darkweak/souin/pkg/storage/types"
@@ -21,8 +22,9 @@ var nutsMemcachedInstanceMap = map[string]*nutsdb.DB{}
2122
// NutsMemcached provider type
2223
type NutsMemcached struct {
2324
*nutsdb.DB
24-
stale time.Duration
25-
logger *zap.Logger
25+
stale time.Duration
26+
logger *zap.Logger
27+
memcacheClient *memcache.Client
2628
}
2729

2830
// const (
@@ -75,6 +77,13 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
7577
nutsConfiguration := dc.GetNutsMemcached()
7678
nutsOptions := nutsdb.DefaultOptions
7779
nutsOptions.Dir = "/tmp/souin-nuts-memcached"
80+
81+
// `HintKeyAndRAMIdxMode` represents ram index (only key) mode.
82+
nutsOptions.EntryIdxMode = nutsdb.HintKeyAndRAMIdxMode
83+
// `HintBPTSparseIdxMode` represents b+ tree sparse index mode.
84+
// Note: this mode was removed after v0.14.0
85+
//nutsOptions.EntryIdxMode = nutsdb.HintBPTSparseIdxMode
86+
7887
if nutsConfiguration.Configuration != nil {
7988
var parsedNuts nutsdb.Options
8089
nutsConfiguration.Configuration = sanitizeProperties(nutsConfiguration.Configuration.(map[string]interface{}))
@@ -110,9 +119,10 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
110119
}
111120

112121
instance := &NutsMemcached{
113-
DB: db,
114-
stale: dc.GetStale(),
115-
logger: c.GetLogger(),
122+
DB: db,
123+
stale: dc.GetStale(),
124+
logger: c.GetLogger(),
125+
memcacheClient: memcache.New("127.0.0.1:11211"), // hardcoded for now
116126
}
117127
nutsMemcachedInstanceMap[nutsOptions.Dir] = instance.DB
118128

@@ -169,13 +179,30 @@ func (provider *NutsMemcached) MapKeys(prefix string) map[string]string {
169179

170180
// Get method returns the populated response if exists, empty response then
171181
func (provider *NutsMemcached) Get(key string) (item []byte) {
172-
_ = provider.DB.View(func(tx *nutsdb.Tx) error {
173-
i, e := tx.Get(bucket, []byte(key))
174-
if i != nil {
182+
// get from nuts
183+
keyFound := false
184+
{
185+
_ = provider.DB.View(func(tx *nutsdb.Tx) error {
186+
i, e := tx.Get(bucket, []byte(key))
187+
if i != nil {
188+
// Value is stored in memcached
189+
//item = i.Value
190+
keyFound = true
191+
}
192+
return e
193+
})
194+
}
195+
196+
// get from memcached
197+
if keyFound {
198+
// Reminder: the key must be at most 250 bytes in length
199+
//fmt.Println("memcached GET", key)
200+
i, e := provider.memcacheClient.Get(key)
201+
if e == nil && i != nil {
175202
item = i.Value
176203
}
177-
return e
178-
})
204+
205+
}
179206

180207
return
181208
}
@@ -192,17 +219,29 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
192219
} else {
193220
for _, entry := range entries {
194221
if varyVoter(key, req, string(entry.Key)) {
195-
if res, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(entry.Value)), req); err == nil {
196-
rfc.ValidateETag(res, validator)
197-
if validator.Matched {
198-
provider.logger.Sugar().Debugf("The stored key %s matched the current iteration key ETag %+v", string(entry.Key), validator)
199-
result = res
200-
return nil
222+
// TODO: improve this
223+
// store header only in nuts and avoid query to memcached on each vary
224+
// E.g, rfc.ValidateETag on NutsDB header value, retrieve response body later from memcached.
225+
226+
// Reminder: the key must be at most 250 bytes in length
227+
//fmt.Println("memcached PREFIX", key, "GET", string(entry.Key))
228+
i, e := provider.memcacheClient.Get(string(entry.Key))
229+
if e == nil && i != nil {
230+
res, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(i.Value)), req)
231+
if err == nil {
232+
rfc.ValidateETag(res, validator)
233+
if validator.Matched {
234+
provider.logger.Sugar().Debugf("The stored key %s matched the current iteration key ETag %+v", string(entry.Key), validator)
235+
result = res
236+
return nil
237+
}
238+
239+
provider.logger.Sugar().Debugf("The stored key %s didn't match the current iteration key ETag %+v", string(entry.Key), validator)
240+
} else {
241+
provider.logger.Sugar().Errorf("An error occured while reading response for the key %s: %v", string(entry.Key), err)
201242
}
202-
203-
provider.logger.Sugar().Debugf("The stored key %s didn't match the current iteration key ETag %+v", string(entry.Key), validator)
204243
} else {
205-
provider.logger.Sugar().Errorf("An error occured while reading response for the key %s: %v", string(entry.Key), err)
244+
provider.logger.Sugar().Errorf("An error occured while reading memcached for the key %s: %v", string(entry.Key), err)
206245
}
207246
}
208247
}
@@ -214,26 +253,51 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
214253
}
215254

216255
// Set method will store the response in Nuts provider
217-
func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, duration time.Duration) error {
218-
if duration == 0 {
219-
duration = url.TTL.Duration
256+
func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, ttl time.Duration) error {
257+
if ttl == 0 {
258+
ttl = url.TTL.Duration
220259
}
221260

222-
err := provider.DB.Update(func(tx *nutsdb.Tx) error {
223-
return tx.Put(bucket, []byte(key), value, uint32(duration.Seconds()))
224-
})
261+
// set to nuts (normal TTL)
262+
{
263+
err := provider.DB.Update(func(tx *nutsdb.Tx) error {
264+
// No value is stored, value is stored in memcached
265+
return tx.Put(bucket, []byte(key), []byte{}, uint32(ttl.Seconds()))
266+
})
225267

226-
if err != nil {
227-
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
228-
return err
268+
if err != nil {
269+
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
270+
return err
271+
}
229272
}
230273

231-
err = provider.DB.Update(func(tx *nutsdb.Tx) error {
232-
return tx.Put(bucket, []byte(StalePrefix+key), value, uint32((provider.stale + duration).Seconds()))
233-
})
274+
// set to nuts (stale TTL)
275+
staleTtl := int32((provider.stale + ttl).Seconds())
276+
{
277+
err := provider.DB.Update(func(tx *nutsdb.Tx) error {
278+
// No value is stored, value is stored in memcached
279+
return tx.Put(bucket, []byte(StalePrefix+key), []byte{}, uint32(staleTtl))
280+
})
234281

235-
if err != nil {
236-
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
282+
if err != nil {
283+
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
284+
}
285+
}
286+
287+
// set to memcached with stale TTL
288+
{
289+
// Reminder: the key must be at most 250 bytes in length
290+
//fmt.Println("memcached SET", key)
291+
err := provider.memcacheClient.Set(
292+
&memcache.Item{
293+
Key: key,
294+
Value: value,
295+
Expiration: staleTtl,
296+
},
297+
)
298+
if err != nil {
299+
provider.logger.Sugar().Errorf("Impossible to set value into Memcached, %v", err)
300+
}
237301
}
238302

239303
return nil

0 commit comments

Comments
 (0)