Skip to content

Commit a31e124

Browse files
authored
Merge pull request #287 from cooperbenson-qz/valkey-store
Creating Valkey store
2 parents 0c274ed + dab076f commit a31e124

File tree

5 files changed

+483
-0
lines changed

5 files changed

+483
-0
lines changed

store/valkey/go.mod

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module github.com/eko/gocache/store/valkey/v4
2+
3+
go 1.24.3
4+
5+
require (
6+
github.com/eko/gocache/lib/v4 v4.2.0
7+
github.com/stretchr/testify v1.9.0
8+
github.com/valkey-io/valkey-go v1.0.59
9+
github.com/valkey-io/valkey-go/mock v1.0.59
10+
github.com/valkey-io/valkey-go/valkeycompat v1.0.59
11+
go.uber.org/mock v0.5.0
12+
)
13+
14+
require (
15+
github.com/davecgh/go-spew v1.1.1 // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
18+
golang.org/x/sys v0.31.0 // indirect
19+
gopkg.in/yaml.v3 v3.0.1 // indirect
20+
)

store/valkey/go.sum

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/eko/gocache/lib/v4 v4.2.0 h1:MNykyi5Xw+5Wu3+PUrvtOCaKSZM1nUSVftbzmeC7Yuw=
4+
github.com/eko/gocache/lib/v4 v4.2.0/go.mod h1:7ViVmbU+CzDHzRpmB4SXKyyzyuJ8A3UW3/cszpcqB4M=
5+
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
6+
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
7+
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
8+
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
9+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
10+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
11+
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
12+
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
13+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
14+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
15+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
16+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
17+
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
18+
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
19+
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
20+
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
21+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
22+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
23+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
24+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
25+
github.com/valkey-io/valkey-go v1.0.59 h1:W67Z0UY+Qqk3k8NKkFCFlM3X4yQUniixl7dSJAch2Qo=
26+
github.com/valkey-io/valkey-go v1.0.59/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
27+
github.com/valkey-io/valkey-go/mock v1.0.59 h1:rTuT0y73xcsQ304ARKagYBkiVmvVHDnAAoMiOIsI8RU=
28+
github.com/valkey-io/valkey-go/mock v1.0.59/go.mod h1:ZuO5azWVi0Pt3UbVzvVwz2Xk4/5cHVslfecRz861g0U=
29+
github.com/valkey-io/valkey-go/valkeycompat v1.0.59 h1:u9EDpfIv3CxsXYOgd4sZTmaL2UH/av8DtSfCRo7m6PE=
30+
github.com/valkey-io/valkey-go/valkeycompat v1.0.59/go.mod h1:O1+VUjvMYhsaG5jDrqu0j+Y+5qvHsZABQcYnlJHiNQU=
31+
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
32+
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
33+
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
34+
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
35+
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
36+
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
37+
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
38+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
39+
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
40+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
41+
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
42+
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
43+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
44+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
45+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
46+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
47+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

store/valkey/valkey.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package valkey
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
lib_store "github.com/eko/gocache/lib/v4/store"
9+
"github.com/valkey-io/valkey-go"
10+
"github.com/valkey-io/valkey-go/valkeycompat"
11+
)
12+
13+
const (
14+
// ValkeyType represents the storage type as a string value
15+
ValkeyType = "valkey"
16+
// ValkeyTagPattern represents the tag pattern to be used as a key in specified storage
17+
ValkeyTagPattern = "gocache_tag_%s"
18+
19+
defaultClientSideCacheExpiration = 10 * time.Second
20+
)
21+
22+
// ValkeyStore is a store for Valkey
23+
type ValkeyStore struct {
24+
client valkey.Client
25+
options *lib_store.Options
26+
}
27+
28+
// NewValkey creates a new store to Valkey instance(s)
29+
func NewValkey(client valkey.Client, options ...lib_store.Option) *ValkeyStore {
30+
// defaults client side cache expiration to 10s
31+
appliedOptions := lib_store.ApplyOptions(options...)
32+
33+
if appliedOptions.ClientSideCacheExpiration == 0 {
34+
appliedOptions.ClientSideCacheExpiration = defaultClientSideCacheExpiration
35+
}
36+
37+
return &ValkeyStore{
38+
client: client,
39+
options: appliedOptions,
40+
}
41+
}
42+
43+
// Get returns data stored from a given key
44+
func (s *ValkeyStore) Get(ctx context.Context, key any) (any, error) {
45+
cmd := s.client.B().Get().Key(key.(string)).Cache()
46+
res := s.client.DoCache(ctx, cmd, s.options.ClientSideCacheExpiration)
47+
str, err := res.ToString()
48+
if valkey.IsValkeyNil(err) {
49+
err = lib_store.NotFoundWithCause(err)
50+
}
51+
return str, err
52+
}
53+
54+
// GetWithTTL returns data stored from a given key and its corresponding TTL
55+
func (s *ValkeyStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) {
56+
cmd := s.client.B().Get().Key(key.(string)).Cache()
57+
res := s.client.DoCache(ctx, cmd, s.options.ClientSideCacheExpiration)
58+
str, err := res.ToString()
59+
if valkey.IsValkeyNil(err) {
60+
err = lib_store.NotFoundWithCause(err)
61+
}
62+
return str, time.Duration(res.CacheTTL()) * time.Second, err
63+
}
64+
65+
// Set defines data in Valkey for given key identifier
66+
func (s *ValkeyStore) Set(ctx context.Context, key any, value any, options ...lib_store.Option) error {
67+
opts := lib_store.ApplyOptionsWithDefault(s.options, options...)
68+
ttl := int64(opts.Expiration.Seconds())
69+
var cmd valkey.Completed
70+
switch value.(type) {
71+
case string:
72+
cmd = s.client.B().Set().Key(key.(string)).Value(value.(string)).ExSeconds(ttl).Build()
73+
74+
case []byte:
75+
cmd = s.client.B().Set().Key(key.(string)).Value(valkey.BinaryString(value.([]byte))).ExSeconds(ttl).Build()
76+
}
77+
err := s.client.Do(ctx, cmd).Error()
78+
if err != nil {
79+
return err
80+
}
81+
if tags := opts.Tags; len(tags) > 0 {
82+
s.setTags(ctx, key, tags)
83+
}
84+
85+
return nil
86+
}
87+
88+
func (s *ValkeyStore) setTags(ctx context.Context, key any, tags []string) {
89+
ttl := 720 * time.Hour
90+
for _, tag := range tags {
91+
tagKey := fmt.Sprintf(ValkeyTagPattern, tag)
92+
s.client.DoMulti(ctx,
93+
s.client.B().Sadd().Key(tagKey).Member(key.(string)).Build(),
94+
s.client.B().Expire().Key(tagKey).Seconds(int64(ttl.Seconds())).Build(),
95+
)
96+
}
97+
}
98+
99+
// Delete removes data from Valkey for given key identifier
100+
func (s *ValkeyStore) Delete(ctx context.Context, key any) error {
101+
return s.client.Do(ctx, s.client.B().Del().Key(key.(string)).Build()).Error()
102+
}
103+
104+
// Invalidate invalidates some cache data in Valkey for given options
105+
func (s *ValkeyStore) Invalidate(ctx context.Context, options ...lib_store.InvalidateOption) error {
106+
opts := lib_store.ApplyInvalidateOptions(options...)
107+
108+
if tags := opts.Tags; len(tags) > 0 {
109+
for _, tag := range tags {
110+
tagKey := fmt.Sprintf(ValkeyTagPattern, tag)
111+
112+
cacheKeys, err := s.client.Do(ctx, s.client.B().Smembers().Key(tagKey).Build()).AsStrSlice()
113+
if err != nil {
114+
continue
115+
}
116+
117+
for _, cacheKey := range cacheKeys {
118+
s.Delete(ctx, cacheKey)
119+
}
120+
121+
s.Delete(ctx, tagKey)
122+
}
123+
}
124+
125+
return nil
126+
}
127+
128+
// GetType returns the store type
129+
func (s *ValkeyStore) GetType() string {
130+
return ValkeyType
131+
}
132+
133+
// Clear resets all data in the store
134+
func (s *ValkeyStore) Clear(ctx context.Context) error {
135+
return valkeycompat.NewAdapter(s.client).FlushAll(ctx).Err()
136+
}

store/valkey/valkey_bench_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package valkey
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
"testing"
8+
"time"
9+
10+
lib_store "github.com/eko/gocache/lib/v4/store"
11+
"github.com/valkey-io/valkey-go"
12+
)
13+
14+
func BenchmarkValkeySet(b *testing.B) {
15+
ctx := context.Background()
16+
17+
valkeyClient, _ := valkey.NewClient(valkey.ClientOption{
18+
InitAddress: []string{"localhost:26379"},
19+
Sentinel: valkey.SentinelOption{
20+
MasterSet: "mymaster",
21+
},
22+
SelectDB: 0,
23+
})
24+
25+
store := NewValkey(valkeyClient, lib_store.WithExpiration(time.Hour*4))
26+
27+
for k := 0.; k <= 10; k++ {
28+
n := int(math.Pow(2, k))
29+
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
30+
for range b.N * n {
31+
key := fmt.Sprintf("test-%d", n)
32+
value := fmt.Sprintf("value-%d", n)
33+
34+
store.Set(ctx, key, value, lib_store.WithTags([]string{fmt.Sprintf("tag-%d", n)}))
35+
}
36+
})
37+
}
38+
}
39+
40+
func BenchmarkValkeyGet(b *testing.B) {
41+
ctx := context.Background()
42+
43+
valkeyClient, _ := valkey.NewClient(valkey.ClientOption{
44+
InitAddress: []string{"localhost:26379"},
45+
Sentinel: valkey.SentinelOption{
46+
MasterSet: "mymaster",
47+
},
48+
SelectDB: 0,
49+
})
50+
51+
store := NewValkey(valkeyClient, lib_store.WithExpiration(time.Hour*4))
52+
53+
key := "test"
54+
value := "value"
55+
56+
_ = store.Set(ctx, key, value)
57+
58+
for k := 0.; k <= 10; k++ {
59+
n := int(math.Pow(2, k))
60+
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
61+
for range b.N * n {
62+
_, _ = store.Get(ctx, key)
63+
}
64+
})
65+
}
66+
}

0 commit comments

Comments
 (0)