@@ -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
2325type 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
181209func (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
307307func (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