Skip to content

Commit b8e1385

Browse files
authored
Merge pull request #2 from rocketlaunchr/ristretto
Add Ristretto as A caching Storage
2 parents d2e5b89 + 941d962 commit b8e1385

File tree

6 files changed

+174
-4
lines changed

6 files changed

+174
-4
lines changed

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ var rs = red.NewRedisStore(&redis.Pool{
6767
An experimental (and untested) memcached driver is provided.
6868
It relies on Brad Fitzpatrick's [memcache driver](https://godoc.org/github.com/bradfitz/gomemcache/memcache).
6969

70+
### Ristretto
71+
72+
DGraph's [Ristretto](https://github.com/dgraph-io/ristretto) is a fast, fixed size, in-memory cache with a dual focus on throughput and hit ratio performance.
73+
74+
The API is potentially still in flux so no backward compatibility guarantee is provided for this driver.
75+
7076
## Create a SlowRetrieve Function
7177

7278
The package initially checks if data exists in the cache. If it doesn’t, then it elegantly fetches the data directly from the database by calling the `SlowRetrieve` function. It then saves the data into the cache so that next time it doesn’t have to refetch it from the database.
@@ -127,7 +133,7 @@ return results.([]Result) // Type assert in order to use
127133

128134
## Gob Register Errors
129135

130-
The Redis storage driver stores the data in a [`gob`](https://golang.org/pkg/encoding/gob/) encoded form. You have to register with the gob package the data type returned by the `SlowRetrieve` function. It can be done inside a `func init()`. Alternatively, you can set the `GobRegister` option to true. This will slightly impact concurrency performance however.
136+
The Redis storage driver stores the data in a `gob` encoded form. You have to register with the [`gob`](https://golang.org/pkg/encoding/gob/) package the data type returned by the `SlowRetrieve` function. It can be done inside a `func init()`. Alternatively, you can set the `GobRegister` option to true. This will slightly impact concurrency performance however.
131137

132138
## Other useful packages
133139

memcached/memcached.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (c *MemcachedStore) StorePointer() bool {
4141
}
4242

4343
// Get retrieves a value from the cache. The key must be at most 250 bytes in length.
44-
func (c *MemcachedStore) Get(key string) (interface{}, bool, error) {
44+
func (c *MemcachedStore) Get(key string) (_ interface{}, found bool, _ error) {
4545

4646
item, err := c.client.Get(key)
4747
if err != nil {

memory/memory.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (c *MemoryStore) StorePointer() bool {
4040
}
4141

4242
// Get returns a value from the cache if the key exists.
43-
func (c *MemoryStore) Get(key string) (interface{}, bool, error) {
43+
func (c *MemoryStore) Get(key string) (_ interface{}, found bool, _ error) {
4444
item, found := c.cache.Get(key)
4545
return item, found, nil
4646
}

redis/redis.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (c *RedisConn) StorePointer() bool {
4848
}
4949

5050
// Get returns a value from the cache if the key exists.
51-
func (c *RedisConn) Get(key string) (interface{}, bool, error) {
51+
func (c *RedisConn) Get(key string) (_ interface{}, found bool, _ error) {
5252

5353
val, err := redis.Bytes(c.conn.Do("GET", key))
5454
if err != nil {

ristretto/ristretto.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package ristretto
2+
3+
import (
4+
"context"
5+
"errors"
6+
"time"
7+
8+
"github.com/dgraph-io/ristretto"
9+
"github.com/rocketlaunchr/remember-go"
10+
)
11+
12+
// ErrItemDropped signifies that the item to store was not inserted into the cache.
13+
//
14+
// See: https://godoc.org/github.com/dgraph-io/ristretto#Cache.Set
15+
var ErrItemDropped = errors.New("item dropped")
16+
17+
// RistrettoStore is used to create an in-memory ristretto cache.
18+
//
19+
// See: https://godoc.org/github.com/dgraph-io/ristretto
20+
type RistrettoStore struct {
21+
Cache *ristretto.Cache
22+
}
23+
24+
// NewRistrettoStore creates an in-memory ristretto cache.
25+
//
26+
// See: https://godoc.org/github.com/dgraph-io/ristretto#Config
27+
func NewRistrettoStore(config *ristretto.Config) *RistrettoStore {
28+
cache, err := ristretto.NewCache(config)
29+
if err != nil {
30+
panic(err)
31+
}
32+
33+
return &RistrettoStore{
34+
Cache: cache,
35+
}
36+
}
37+
38+
// Conn does nothing for this storage driver.
39+
func (r *RistrettoStore) Conn(ctx context.Context) (remember.Cacher, error) {
40+
return r, nil
41+
}
42+
43+
// StorePointer sets whether a storage driver requires itemToStore to be
44+
// stored as a pointer or as a concrete value.
45+
func (r *RistrettoStore) StorePointer() bool {
46+
return false
47+
}
48+
49+
// Get returns a value from the cache if the key exists.
50+
// It is possible for nil to be returned while found is also true.
51+
//
52+
// See: https://godoc.org/github.com/dgraph-io/ristretto#Cache.Get
53+
func (r *RistrettoStore) Get(key string) (_ interface{}, found bool, _ error) {
54+
item, found := r.Cache.Get(key)
55+
return item, found, nil
56+
}
57+
58+
// Set sets a item into the cache for a particular key.
59+
// cost must be converted to a time.Duration despite being unrelated to time.
60+
//
61+
// See: https://godoc.org/github.com/dgraph-io/ristretto#Cache.Set
62+
func (r *RistrettoStore) Set(key string, cost time.Duration, itemToStore interface{}) error {
63+
stored := r.Cache.Set(key, itemToStore, int64(cost))
64+
if stored {
65+
return nil
66+
}
67+
return ErrItemDropped
68+
}
69+
70+
// Close returns the connection back to the pool for storage drivers that utilize a pool.
71+
// For this driver, it does nothing.
72+
func (r *RistrettoStore) Close() {}
73+
74+
// Forget clears the value from the cache for the particular key.
75+
//
76+
// See: https://godoc.org/github.com/dgraph-io/ristretto#Cache.Del
77+
func (r *RistrettoStore) Forget(key string) error {
78+
r.Cache.Del(key)
79+
return nil
80+
}
81+
82+
// ForgetAll clears all values from the cache.
83+
// Note that this is not an atomic operation.
84+
//
85+
// See: https://godoc.org/github.com/dgraph-io/ristretto#Cache.Clear
86+
func (r *RistrettoStore) ForgetAll() error {
87+
r.Cache.Clear()
88+
return nil
89+
}

ristretto/ristretto_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package ristretto_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
rist "github.com/dgraph-io/ristretto"
9+
"github.com/rocketlaunchr/remember-go"
10+
"github.com/rocketlaunchr/remember-go/ristretto"
11+
)
12+
13+
var cfg = &rist.Config{
14+
NumCounters: 1e7, // number of keys to track frequency of (10M).
15+
MaxCost: 1 << 30, // maximum cost of cache (1GB).
16+
BufferItems: 64, // number of keys per Get buffer.
17+
}
18+
19+
func TestKeyBasicOperation(t *testing.T) {
20+
ctx := context.Background()
21+
var ms = ristretto.NewRistrettoStore(cfg)
22+
23+
key := "key"
24+
exp := 10 * time.Minute
25+
26+
slowQuery := func(ctx context.Context) (interface{}, error) {
27+
return "val", nil
28+
}
29+
30+
actual, _, _ := remember.Cache(ctx, ms, key, exp, slowQuery)
31+
32+
expected := "val"
33+
34+
if actual.(string) != expected {
35+
t.Errorf("wrong val: expected: %v actual: %v", expected, actual)
36+
}
37+
}
38+
39+
func TestFetchFromCacheAndDisableCache(t *testing.T) {
40+
ctx := context.Background()
41+
var ms = ristretto.NewRistrettoStore(cfg)
42+
43+
key := "key"
44+
exp := 10 * time.Minute
45+
46+
slowQuery := func(ctx context.Context) (interface{}, error) {
47+
return "val", nil
48+
}
49+
50+
// warm up cache
51+
remember.Cache(ctx, ms, key, exp, slowQuery)
52+
53+
// This time fetch from cache
54+
actual, _, _ := remember.Cache(ctx, ms, key, exp, slowQuery)
55+
56+
expected := "val"
57+
58+
if actual.(string) != expected {
59+
t.Errorf("wrong val: expected: %v actual: %v", expected, actual)
60+
}
61+
62+
// Actual is now "val", Let's change it to "val2" and disable cache usage.
63+
64+
slowQuery = func(ctx context.Context) (interface{}, error) {
65+
return "val2", nil
66+
}
67+
68+
actual, _, _ = remember.Cache(ctx, ms, key, exp, slowQuery, remember.Options{DisableCacheUsage: true})
69+
70+
expected = "val2"
71+
72+
if actual.(string) != expected {
73+
t.Errorf("wrong val: expected: %v actual: %v", expected, actual)
74+
}
75+
}

0 commit comments

Comments
 (0)