Skip to content

Commit

Permalink
feat(storers): Add memcached backend to NutsMemcached
Browse files Browse the repository at this point in the history
In this commit cache values are moved to memcached and only keys are kept in Nuts.
  • Loading branch information
Vincent Jordan committed Feb 13, 2024
1 parent 6fc5b9b commit 95cace4
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 33 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/darkweak/souin
go 1.19

require (
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
github.com/buraksezer/olric v0.5.4
github.com/dgraph-io/badger/v3 v3.2103.5
github.com/dgraph-io/ristretto v0.1.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU=
github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
github.com/buraksezer/olric v0.5.4 h1:LDgLIfVoyol4qzdNirrrDUKqzFw0yDsa7ukvLrpP4cU=
Expand Down
130 changes: 97 additions & 33 deletions pkg/storage/nutsMemcachedProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/bradfitz/gomemcache/memcache"
t "github.com/darkweak/souin/configurationtypes"
"github.com/darkweak/souin/pkg/rfc"
"github.com/darkweak/souin/pkg/storage/types"
Expand All @@ -21,8 +22,9 @@ var nutsMemcachedInstanceMap = map[string]*nutsdb.DB{}
// NutsMemcached provider type
type NutsMemcached struct {
*nutsdb.DB
stale time.Duration
logger *zap.Logger
stale time.Duration
logger *zap.Logger
memcacheClient *memcache.Client
}

// const (
Expand Down Expand Up @@ -75,6 +77,13 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
nutsConfiguration := dc.GetNutsMemcached()
nutsOptions := nutsdb.DefaultOptions
nutsOptions.Dir = "/tmp/souin-nuts-memcached"

// `HintKeyAndRAMIdxMode` represents ram index (only key) mode.
nutsOptions.EntryIdxMode = nutsdb.HintKeyAndRAMIdxMode
// `HintBPTSparseIdxMode` represents b+ tree sparse index mode.
// Note: this mode was removed after v0.14.0
//nutsOptions.EntryIdxMode = nutsdb.HintBPTSparseIdxMode

if nutsConfiguration.Configuration != nil {
var parsedNuts nutsdb.Options
nutsConfiguration.Configuration = sanitizeProperties(nutsConfiguration.Configuration.(map[string]interface{}))
Expand Down Expand Up @@ -110,9 +119,10 @@ func NutsMemcachedConnectionFactory(c t.AbstractConfigurationInterface) (types.S
}

instance := &NutsMemcached{
DB: db,
stale: dc.GetStale(),
logger: c.GetLogger(),
DB: db,
stale: dc.GetStale(),
logger: c.GetLogger(),
memcacheClient: memcache.New("127.0.0.1:11211"), // hardcoded for now
}
nutsMemcachedInstanceMap[nutsOptions.Dir] = instance.DB

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

// Get method returns the populated response if exists, empty response then
func (provider *NutsMemcached) Get(key string) (item []byte) {
_ = provider.DB.View(func(tx *nutsdb.Tx) error {
i, e := tx.Get(bucket, []byte(key))
if i != nil {
// get from nuts
keyFound := false
{
_ = provider.DB.View(func(tx *nutsdb.Tx) error {
i, e := tx.Get(bucket, []byte(key))
if i != nil {
// Value is stored in memcached
//item = i.Value
keyFound = true
}
return e
})
}

// get from memcached
if keyFound {
// Reminder: the key must be at most 250 bytes in length
//fmt.Println("memcached GET", key)
i, e := provider.memcacheClient.Get(key)
if e == nil && i != nil {
item = i.Value
}
return e
})

}

return
}
Expand All @@ -192,17 +219,29 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
} else {
for _, entry := range entries {
if varyVoter(key, req, string(entry.Key)) {
if res, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(entry.Value)), req); err == nil {
rfc.ValidateETag(res, validator)
if validator.Matched {
provider.logger.Sugar().Debugf("The stored key %s matched the current iteration key ETag %+v", string(entry.Key), validator)
result = res
return nil
// TODO: improve this
// store header only in nuts and avoid query to memcached on each vary
// E.g, rfc.ValidateETag on NutsDB header value, retrieve response body later from memcached.

// Reminder: the key must be at most 250 bytes in length
//fmt.Println("memcached PREFIX", key, "GET", string(entry.Key))
i, e := provider.memcacheClient.Get(string(entry.Key))
if e == nil && i != nil {
res, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(i.Value)), req)
if err == nil {
rfc.ValidateETag(res, validator)
if validator.Matched {
provider.logger.Sugar().Debugf("The stored key %s matched the current iteration key ETag %+v", string(entry.Key), validator)
result = res
return nil
}

provider.logger.Sugar().Debugf("The stored key %s didn't match the current iteration key ETag %+v", string(entry.Key), validator)
} else {
provider.logger.Sugar().Errorf("An error occured while reading response for the key %s: %v", string(entry.Key), err)
}

provider.logger.Sugar().Debugf("The stored key %s didn't match the current iteration key ETag %+v", string(entry.Key), validator)
} else {
provider.logger.Sugar().Errorf("An error occured while reading response for the key %s: %v", string(entry.Key), err)
provider.logger.Sugar().Errorf("An error occured while reading memcached for the key %s: %v", string(entry.Key), err)
}
}
}
Expand All @@ -214,26 +253,51 @@ func (provider *NutsMemcached) Prefix(key string, req *http.Request, validator *
}

// Set method will store the response in Nuts provider
func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, duration time.Duration) error {
if duration == 0 {
duration = url.TTL.Duration
func (provider *NutsMemcached) Set(key string, value []byte, url t.URL, ttl time.Duration) error {
if ttl == 0 {
ttl = url.TTL.Duration
}

err := provider.DB.Update(func(tx *nutsdb.Tx) error {
return tx.Put(bucket, []byte(key), value, uint32(duration.Seconds()))
})
// set to nuts (normal TTL)
{
err := provider.DB.Update(func(tx *nutsdb.Tx) error {
// No value is stored, value is stored in memcached
return tx.Put(bucket, []byte(key), []byte{}, uint32(ttl.Seconds()))
})

if err != nil {
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
return err
if err != nil {
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
return err
}
}

err = provider.DB.Update(func(tx *nutsdb.Tx) error {
return tx.Put(bucket, []byte(StalePrefix+key), value, uint32((provider.stale + duration).Seconds()))
})
// set to nuts (stale TTL)
staleTtl := int32((provider.stale + ttl).Seconds())
{
err := provider.DB.Update(func(tx *nutsdb.Tx) error {
// No value is stored, value is stored in memcached
return tx.Put(bucket, []byte(StalePrefix+key), []byte{}, uint32(staleTtl))
})

if err != nil {
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
if err != nil {
provider.logger.Sugar().Errorf("Impossible to set value into Nuts, %v", err)
}
}

// set to memcached with stale TTL
{
// Reminder: the key must be at most 250 bytes in length
//fmt.Println("memcached SET", key)
err := provider.memcacheClient.Set(
&memcache.Item{
Key: key,
Value: value,
Expiration: staleTtl,
},
)
if err != nil {
provider.logger.Sugar().Errorf("Impossible to set value into Memcached, %v", err)
}
}

return nil
Expand Down

0 comments on commit 95cace4

Please sign in to comment.