Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #198 from semihbkgr/hazelcast-store
Hazelcast store support
- Loading branch information
Showing
9 changed files
with
656 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ jobs: | |
- bigcache | ||
- freecache | ||
- go_cache | ||
- hazelcast | ||
- memcache | ||
- pegasus | ||
- redis | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module github.com/eko/gocache/store/hazelcast/v4 | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/eko/gocache/lib/v4 v4.1.2 | ||
github.com/golang/mock v1.6.0 | ||
github.com/hazelcast/hazelcast-go-client v1.3.2 | ||
github.com/stretchr/testify v1.8.1 | ||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c | ||
) | ||
|
||
require ( | ||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/go-ole/go-ole v1.2.4 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/shirou/gopsutil/v3 v3.21.5 // indirect | ||
github.com/tklauser/go-sysconf v0.3.4 // indirect | ||
github.com/tklauser/numcpus v0.2.1 // indirect | ||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect | ||
golang.org/x/sys v0.1.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) | ||
|
||
replace github.com/eko/gocache/lib/v4 => ../../lib/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= | ||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= | ||
github.com/apache/thrift v0.14.1 h1:Yh8v0hpCj63p5edXOLaqTJW0IJ1p+eMW6+YSOqw1d6s= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= | ||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= | ||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= | ||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= | ||
github.com/hazelcast/hazelcast-go-client v1.3.2 h1:RSaEoL5NIoPZuKbHWXcgYO12xZW8Br63DYhMENXVRCo= | ||
github.com/hazelcast/hazelcast-go-client v1.3.2/go.mod h1:JH7sI0kvMSlJJ5D+YFRg/J/P41MRPffzCt0bN9LYd0M= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/shirou/gopsutil/v3 v3.21.5 h1:YUBf0w/KPLk7w1803AYBnH7BmA+1Z/Q5MEZxpREUaB4= | ||
github.com/shirou/gopsutil/v3 v3.21.5/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= | ||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M= | ||
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= | ||
github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc= | ||
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= | ||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= | ||
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4= | ||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= | ||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= | ||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= | ||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= | ||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | ||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= | ||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= | ||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package hazelcast | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
"time" | ||
|
||
lib_store "github.com/eko/gocache/lib/v4/store" | ||
"github.com/hazelcast/hazelcast-go-client/types" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
// HazelcastMapInterface represents a hazelcast/hazelcast-go-client map | ||
type HazelcastMapInterface interface { | ||
Get(ctx context.Context, key any) (any, error) | ||
GetEntryView(ctx context.Context, key any) (*types.SimpleEntryView, error) | ||
SetWithTTL(ctx context.Context, key any, value any, ttl time.Duration) error | ||
Remove(ctx context.Context, key any) (any, error) | ||
Clear(ctx context.Context) error | ||
} | ||
|
||
const ( | ||
// HazelcastType represents the storage type as a string value | ||
HazelcastType = "hazelcast" | ||
// HazelcastTagPattern represents the tag pattern to be used as a key in specified storage | ||
HazelcastTagPattern = "gocache_tag_%s" | ||
|
||
TagKeyExpiry = 720 * time.Hour | ||
) | ||
|
||
// HazelcastStore is a store for Hazelcast | ||
type HazelcastStore struct { | ||
hzMap HazelcastMapInterface | ||
options *lib_store.Options | ||
} | ||
|
||
// NewHazelcast creates a new store to Hazelcast instance(s) | ||
func NewHazelcast(hzMap HazelcastMapInterface, options ...lib_store.Option) *HazelcastStore { | ||
return &HazelcastStore{ | ||
hzMap: hzMap, | ||
options: lib_store.ApplyOptions(options...), | ||
} | ||
} | ||
|
||
// Get returns data stored from a given key | ||
func (s *HazelcastStore) Get(ctx context.Context, key any) (any, error) { | ||
value, err := s.hzMap.Get(ctx, key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if value == nil { | ||
return nil, lib_store.NotFoundWithCause(errors.New("unable to retrieve data from hazelcast")) | ||
} | ||
return value, err | ||
} | ||
|
||
// GetWithTTL returns data stored from a given key and its corresponding TTL | ||
func (s *HazelcastStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) { | ||
entryView, err := s.hzMap.GetEntryView(ctx, key) | ||
if err != nil { | ||
return nil, 0, err | ||
} | ||
if entryView == nil { | ||
return nil, 0, lib_store.NotFoundWithCause(errors.New("unable to retrieve data from hazelcast")) | ||
} | ||
return entryView.Value, time.Duration(entryView.TTL) * time.Millisecond, err | ||
} | ||
|
||
// Set defines data in Hazelcast for given key identifier | ||
func (s *HazelcastStore) Set(ctx context.Context, key any, value any, options ...lib_store.Option) error { | ||
opts := lib_store.ApplyOptionsWithDefault(s.options, options...) | ||
err := s.hzMap.SetWithTTL(ctx, key, value, opts.Expiration) | ||
if err != nil { | ||
return err | ||
} | ||
if tags := opts.Tags; len(tags) > 0 { | ||
s.setTags(ctx, key, tags) | ||
} | ||
return nil | ||
} | ||
|
||
func (s *HazelcastStore) setTags(ctx context.Context, key any, tags []string) { | ||
group, ctx := errgroup.WithContext(ctx) | ||
for _, tag := range tags { | ||
currentTag := tag | ||
group.Go(func() error { | ||
tagKey := fmt.Sprintf(HazelcastTagPattern, currentTag) | ||
tagValue, err := s.hzMap.Get(ctx, tagKey) | ||
if err != nil { | ||
return err | ||
} | ||
if tagValue == nil { | ||
return s.hzMap.SetWithTTL(ctx, tagKey, key.(string), TagKeyExpiry) | ||
} | ||
cacheKeys := strings.Split(tagValue.(string), ",") | ||
for _, cacheKey := range cacheKeys { | ||
if key == cacheKey { | ||
return nil | ||
} | ||
} | ||
cacheKeys = append(cacheKeys, key.(string)) | ||
newTagValue := strings.Join(cacheKeys, ",") | ||
return s.hzMap.SetWithTTL(ctx, tagKey, newTagValue, TagKeyExpiry) | ||
}) | ||
} | ||
group.Wait() | ||
} | ||
|
||
// Delete removes data from Hazelcast for given key identifier | ||
func (s *HazelcastStore) Delete(ctx context.Context, key any) error { | ||
_, err := s.hzMap.Remove(ctx, key) | ||
return err | ||
} | ||
|
||
// Invalidate invalidates some cache data in Hazelcast for given options | ||
func (s *HazelcastStore) Invalidate(ctx context.Context, options ...lib_store.InvalidateOption) error { | ||
opts := lib_store.ApplyInvalidateOptions(options...) | ||
if tags := opts.Tags; len(tags) > 0 { | ||
for _, tag := range tags { | ||
tagKey := fmt.Sprintf(HazelcastTagPattern, tag) | ||
tagValue, err := s.hzMap.Get(ctx, tagKey) | ||
if err != nil || tagValue == nil { | ||
continue | ||
} | ||
cacheKeys := strings.Split(tagValue.(string), ",") | ||
for _, cacheKey := range cacheKeys { | ||
s.Delete(ctx, cacheKey) | ||
} | ||
s.Delete(ctx, tagKey) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Clear resets all data in the store | ||
func (s *HazelcastStore) Clear(ctx context.Context) error { | ||
return s.hzMap.Clear(ctx) | ||
} | ||
|
||
// GetType returns the store type | ||
func (s *HazelcastStore) GetType() string { | ||
return HazelcastType | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package hazelcast | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math" | ||
"testing" | ||
|
||
lib_store "github.com/eko/gocache/lib/v4/store" | ||
"github.com/hazelcast/hazelcast-go-client" | ||
) | ||
|
||
func BenchmarkHazelcastSet(b *testing.B) { | ||
ctx := context.Background() | ||
|
||
client, err := hazelcast.StartNewClient(ctx) | ||
if err != nil { | ||
b.Fatalf("Failed to start client: %v", err) | ||
} | ||
|
||
hzMap, err := client.GetMap(ctx, "gocache") | ||
if err != nil { | ||
b.Fatalf("Failed to get map: %v", err) | ||
} | ||
|
||
store := NewHazelcast(hzMap) | ||
|
||
for k := 0.; k <= 10; k++ { | ||
n := int(math.Pow(2, k)) | ||
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { | ||
for i := 0; i < b.N*n; i++ { | ||
key := fmt.Sprintf("test-%d", n) | ||
value := []byte(fmt.Sprintf("value-%d", n)) | ||
store.Set(ctx, key, value, lib_store.WithTags([]string{fmt.Sprintf("tag-%d", n)})) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func BenchmarkHazelcastGet(b *testing.B) { | ||
ctx := context.Background() | ||
|
||
client, err := hazelcast.StartNewClient(ctx) | ||
if err != nil { | ||
b.Fatalf("Failed to start client: %v", err) | ||
} | ||
|
||
hzMap, err := client.GetMap(ctx, "gocache") | ||
if err != nil { | ||
b.Fatalf("Failed to get map: %v", err) | ||
} | ||
|
||
store := NewHazelcast(hzMap) | ||
|
||
key := "test" | ||
value := []byte("value") | ||
|
||
store.Set(ctx, key, value) | ||
|
||
for k := 0.; k <= 10; k++ { | ||
n := int(math.Pow(2, k)) | ||
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { | ||
for i := 0; i < b.N*n; i++ { | ||
_, _ = store.Get(ctx, key) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.