Skip to content

Commit 7578024

Browse files
committed
feat(storers): Use UUID for memcached keys
1 parent 95cace4 commit 7578024

File tree

1 file changed

+120
-54
lines changed

1 file changed

+120
-54
lines changed

pkg/storage/nutsMemcachedProvider.go

Lines changed: 120 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import (
44
"bufio"
55
"bytes"
66
"encoding/json"
7+
"errors"
78
"net/http"
9+
"strconv"
810
"strings"
911
"time"
1012

11-
"github.com/bradfitz/gomemcache/memcache"
1213
t "github.com/darkweak/souin/configurationtypes"
1314
"github.com/darkweak/souin/pkg/rfc"
1415
"github.com/darkweak/souin/pkg/storage/types"
16+
"github.com/dgraph-io/ristretto"
1517
"github.com/imdario/mergo"
1618
"github.com/nutsdb/nutsdb"
1719
"go.uber.org/zap"
@@ -22,9 +24,10 @@ var nutsMemcachedInstanceMap = map[string]*nutsdb.DB{}
2224
// NutsMemcached provider type
2325
type NutsMemcached struct {
2426
*nutsdb.DB
25-
stale time.Duration
26-
logger *zap.Logger
27-
memcacheClient *memcache.Client
27+
stale time.Duration
28+
logger *zap.Logger
29+
//memcacheClient *memcache.Client
30+
ristrettoCache *ristretto.Cache
2831
}
2932

3033
// const (
@@ -82,6 +85,7 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
8285
nutsOptions.EntryIdxMode = nutsdb.HintKeyAndRAMIdxMode
8386
// `HintBPTSparseIdxMode` represents b+ tree sparse index mode.
8487
// Note: this mode was removed after v0.14.0
88+
// Use: github.com/nutsdb/nutsdb v0.14.0
8589
//nutsOptions.EntryIdxMode = nutsdb.HintBPTSparseIdxMode
8690

8791
if nutsConfiguration.Configuration != nil {
@@ -118,11 +122,35 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
118122
return nil, e
119123
}
120124

125+
var numCounters int64 = 1e7 // number of keys to track frequency of (10M).
126+
var maxCost int64 = 1 << 30 // maximum cost of cache (1GB).
127+
if nutsConfiguration.Configuration != nil {
128+
rawNumCounters, ok := nutsConfiguration.Configuration.(map[string]interface{})["NumCounters"]
129+
if ok {
130+
numCounters, _ = strconv.ParseInt(rawNumCounters.(string), 10, 64)
131+
}
132+
133+
rawMaxCost, ok := nutsConfiguration.Configuration.(map[string]interface{})["MaxCost"]
134+
if ok {
135+
maxCost, _ = strconv.ParseInt(rawMaxCost.(string), 10, 64)
136+
}
137+
}
138+
// See https://github.com/dgraph-io/ristretto?tab=readme-ov-file#example
139+
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{
140+
NumCounters: numCounters,
141+
MaxCost: maxCost,
142+
BufferItems: 64, // number of keys per Get buffer.
143+
})
144+
if err != nil {
145+
panic(err)
146+
}
147+
121148
instance := &NutsMemcached{
122-
DB: db,
123-
stale: dc.GetStale(),
124-
logger: c.GetLogger(),
125-
memcacheClient: memcache.New("127.0.0.1:11211"), // hardcoded for now
149+
DB: db,
150+
stale: dc.GetStale(),
151+
logger: c.GetLogger(),
152+
//memcacheClient: memcache.New("127.0.0.1:11211"), // hardcoded for now
153+
ristrettoCache: ristrettoCache,
126154
}
127155
nutsMemcachedInstanceMap[nutsOptions.Dir] = instance.DB
128156

@@ -179,29 +207,10 @@ func (provider *NutsMemcached) MapKeys(prefix string) map[string]string {
179207

180208
// Get method returns the populated response if exists, empty response then
181209
func (provider *NutsMemcached) Get(key string) (item []byte) {
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 {
202-
item = i.Value
203-
}
210+
memcachedKey, _ := provider.getFromNuts(key)
204211

212+
if memcachedKey != "" {
213+
item, _ = provider.getFromMemcached(memcachedKey)
205214
}
206215

207216
return
@@ -220,14 +229,14 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
220229
for _, entry := range entries {
221230
if varyVoter(key, req, string(entry.Key)) {
222231
// TODO: improve this
223-
// store header only in nuts and avoid query to memcached on each vary
232+
// Store only response header in nuts and avoid query to memcached on each vary
224233
// E.g, rfc.ValidateETag on NutsDB header value, retrieve response body later from memcached.
225234

226235
// Reminder: the key must be at most 250 bytes in length
227236
//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)
237+
i, e := provider.getFromMemcached(string(entry.Value))
238+
if e == nil {
239+
res, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(i)), req)
231240
if err == nil {
232241
rfc.ValidateETag(res, validator)
233242
if validator.Matched {
@@ -257,12 +266,16 @@ func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, ttl time
257266
if ttl == 0 {
258267
ttl = url.TTL.Duration
259268
}
269+
// Only for memcached (to overcome 250 bytes key limit)
270+
//memcachedKey := uuid.New().String()
271+
memcachedKey := key
260272

261273
// set to nuts (normal TTL)
262274
{
263275
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()))
276+
277+
// key: cache-key, value: memcached-key
278+
return tx.Put(bucket, []byte(key), []byte(memcachedKey), uint32(ttl.Seconds()))
266279
})
267280

268281
if err != nil {
@@ -275,8 +288,8 @@ func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, ttl time
275288
staleTtl := int32((provider.stale + ttl).Seconds())
276289
{
277290
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))
291+
// key: "STALE_" + cache-key, value: memcached-key
292+
return tx.Put(bucket, []byte(StalePrefix+key), []byte(memcachedKey), uint32(staleTtl))
280293
})
281294

282295
if err != nil {
@@ -285,38 +298,36 @@ func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, ttl time
285298
}
286299

287300
// 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-
}
301-
}
301+
_ = provider.setToMemcached(memcachedKey, value, staleTtl)
302302

303303
return nil
304304
}
305305

306306
// Delete method will delete the response in Nuts provider if exists corresponding to key param
307307
func (provider *NutsMemcached) Delete(key string) {
308+
memcachedKey, _ := provider.getFromNuts(key)
309+
310+
// delete from memcached
311+
if memcachedKey != "" {
312+
_ = provider.delFromMemcached(memcachedKey)
313+
}
314+
315+
// delete from nuts
308316
_ = provider.DB.Update(func(tx *nutsdb.Tx) error {
309317
return tx.Delete(bucket, []byte(key))
310318
})
311319
}
312320

313321
// DeleteMany method will delete the responses in Nuts provider if exists corresponding to the regex key param
314-
func (provider *NutsMemcached) DeleteMany(key string) {
322+
func (provider *NutsMemcached) DeleteMany(keyReg string) {
315323
_ = provider.DB.Update(func(tx *nutsdb.Tx) error {
316-
if entries, err := tx.PrefixSearchScan(bucket, []byte(""), key, 0, nutsLimit); err != nil {
324+
if entries, err := tx.PrefixSearchScan(bucket, []byte(""), keyReg, 0, nutsLimit); err != nil {
317325
return err
318326
} else {
319327
for _, entry := range entries {
328+
// delete from memcached
329+
_ = provider.delFromMemcached(string(entry.Value))
330+
// delete from nuts
320331
_ = tx.Delete(bucket, entry.Key)
321332
}
322333
}
@@ -335,3 +346,58 @@ func (provider *NutsMemcached) Reset() error {
335346
return tx.DeleteBucket(1, bucket)
336347
})
337348
}
349+
350+
func (provider *NutsMemcached) getFromNuts(nutsKey string) (memcachedKey string, err error) {
351+
err = provider.DB.View(func(tx *nutsdb.Tx) error {
352+
i, e := tx.Get(bucket, []byte(nutsKey))
353+
if i != nil {
354+
memcachedKey = string(i.Value)
355+
}
356+
return e
357+
})
358+
return
359+
}
360+
361+
// Reminder: the memcachedKey must be at most 250 bytes in length
362+
func (provider *NutsMemcached) setToMemcached(memcachedKey string, value []byte, ttl int32) (err error) {
363+
//fmt.Println("memcached SET", key)
364+
// err = provider.memcacheClient.Set(
365+
// &memcache.Item{
366+
// Key: memcachedKey,
367+
// Value: value,
368+
// Expiration: ttl,
369+
// },
370+
// )
371+
//if err != nil {
372+
// provider.logger.Sugar().Errorf("Failed to set into memcached, %v", err)
373+
// }
374+
ok := provider.ristrettoCache.Set(memcachedKey, value, int64(len(value)))
375+
if !ok {
376+
provider.logger.Sugar().Debugf("Value not set to cache, key=%v", memcachedKey)
377+
}
378+
return
379+
}
380+
381+
// Reminder: the memcachedKey must be at most 250 bytes in length
382+
func (provider *NutsMemcached) getFromMemcached(memcachedKey string) (value []byte, err error) {
383+
//fmt.Println("memcached GET", key)
384+
// i, err := provider.memcacheClient.Get(memcachedKey)
385+
// if err == nil && i != nil {
386+
// value = i.Value
387+
// } else {
388+
// provider.logger.Sugar().Errorf("Failed to get from memcached, %v", err)
389+
// }
390+
rawValue, found := provider.ristrettoCache.Get(memcachedKey)
391+
if !found {
392+
provider.logger.Sugar().Debugf("Failed to get from cache, key=%v", memcachedKey)
393+
return nil, errors.New("failed to get from cache")
394+
}
395+
value = rawValue.([]byte)
396+
return
397+
}
398+
399+
func (provider *NutsMemcached) delFromMemcached(memcachedKey string) (err error) {
400+
//err = provider.memcacheClient.Delete(memcachedKey)
401+
provider.ristrettoCache.Del(memcachedKey)
402+
return
403+
}

0 commit comments

Comments
 (0)