Package xcache
offers caching alternatives for an application like a local in memory cache,
or distributed Redis cache, or combination of those two in a multi layered cache.
$ go get github.com/actforgood/xcache
Memory
- a local in memory cache, relies upon Freecache package.Redis6
- Redis version 6 cache (single instance / sentinel failover / cluster).Redis7
- Redis version 7 cache (single instance / sentinel failover / cluster).Multi
- A multi layer cache.Nop
- A no-operation cache.Mock
- A stub that can be used in Unit Tests.
Looks like:
// Cache provides prototype a for storing and returning a key-value into/from cache.
type Cache interface {
// Save stores the given key-value with expiration period into cache.
// An expiration period equal to 0 (NoExpire) means no expiration.
// A negative expiration period triggers deletion of key.
// It returns an error if the key could not be saved.
Save(ctx context.Context, key string, value []byte, expire time.Duration) error
// Load returns a key's value from cache, or an error if something bad happened.
// If the key is not found, ErrNotFound is returned.
Load(ctx context.Context, key string) ([]byte, error)
// TTL returns a key's remaining time to live, or an error if something bad happened.
// If the key is not found, a negative TTL is returned.
// If the key has no expiration, 0 (NoExpire) is returned.
TTL(ctx context.Context, key string) (time.Duration, error)
// Stats returns some statistics about cache's memory/keys.
// It returns an error if something goes wrong.
Stats(context.Context) (Stats, error)
}
func ExampleMemory() {
cache := xcache.NewMemory(10 * 1024 * 1024) // 10 Mb
ctx := context.Background()
key := "example-memory"
value := []byte("Hello Memory Cache")
ttl := 10 * time.Minute
// save a key for 10 minutes
if err := cache.Save(ctx, key, value, ttl); err != nil {
fmt.Println("could not save Memory cache key: " + err.Error())
}
// load the key's value
if value, err := cache.Load(ctx, key); err != nil {
fmt.Println("could not get Memory cache key: " + err.Error())
} else {
fmt.Println(string(value))
}
// Output:
// Hello Memory Cache
}
Benchmarks
go test -run=^# -benchmem -benchtime=5s -bench BenchmarkMemory github.com/actforgood/xcache
goos: linux
goarch: amd64
pkg: github.com/actforgood/xcache
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkMemory_Save-4 11463078 492.5 ns/op 2 B/op 0 allocs/op
BenchmarkMemory_Save_parallel-4 24244593 261.4 ns/op 25 B/op 1 allocs/op
BenchmarkMemory_Load-4 30390457 194.0 ns/op 40 B/op 2 allocs/op
BenchmarkMemory_Load_parallel-4 25545855 220.0 ns/op 40 B/op 2 allocs/op
BenchmarkMemory_TTL-4 55357045 110.5 ns/op 0 B/op 0 allocs/op
BenchmarkMemory_TTL_parallel-4 40464970 153.2 ns/op 0 B/op 0 allocs/op
BenchmarkMemory_Stats-4 5760609 983.7 ns/op 0 B/op 0 allocs/op
BenchmarkMemory_Stats_parallel-4 23939924 254.5 ns/op 0 B/op 0 allocs/op
func ExampleRedis() {
cache := xcache.NewRedis6(xcache.RedisConfig{ // or xcache.NewRedis7 if you're using ver. 7
Addrs: []string{"127.0.0.1:6379"},
})
ctx := context.Background()
key := "example-redis"
value := []byte("Hello Redis Cache")
ttl := 10 * time.Minute
// save a key for 10 minutes
if err := cache.Save(ctx, key, value, ttl); err != nil {
fmt.Println("could not save Redis cache key: " + err.Error())
}
// load the key's value
if value, err := cache.Load(ctx, key); err != nil {
fmt.Println("could not get Redis cache key: " + err.Error())
} else {
fmt.Println(string(value))
}
// close the cache when no needed anymore/at your application shutdown.
if err := cache.Close(); err != nil {
fmt.Println("could not close Redis cache: " + err.Error())
}
// should output:
// Hello Redis Cache
}
Benchmarks
go test -tags=integration -run=^# -benchmem -benchtime=5s -bench BenchmarkRedis github.com/actforgood/xcache
goos: linux
goarch: amd64
pkg: github.com/actforgood/xcache
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkRedis6_Save_integration-4 32479 161610 ns/op 272 B/op 7 allocs/op
BenchmarkRedis6_Save_parallel_integration-4 130350 43058 ns/op 296 B/op 8 allocs/op
BenchmarkRedis6_Load_integration-4 39960 144686 ns/op 208 B/op 6 allocs/op
BenchmarkRedis6_Load_parallel_integration-4 103783 62320 ns/op 208 B/op 6 allocs/op
BenchmarkRedis6_TTL_integration-4 43336 158656 ns/op 196 B/op 5 allocs/op
BenchmarkRedis6_TTL_parallel_integration-4 135324 43266 ns/op 196 B/op 5 allocs/op
BenchmarkRedis6_Stats-4 23257 244013 ns/op 5052 B/op 6 allocs/op
BenchmarkRedis6_Stats_parallel-4 63873 90896 ns/op 5052 B/op 6 allocs/op
BenchmarkRedis7_Save_integration-4 38620 162062 ns/op 312 B/op 10 allocs/op
BenchmarkRedis7_Save_parallel_integration-4 129525 46068 ns/op 336 B/op 11 allocs/op
BenchmarkRedis7_Load_integration-4 42074 153150 ns/op 248 B/op 9 allocs/op
BenchmarkRedis7_Load_parallel_integration-4 139232 43403 ns/op 248 B/op 9 allocs/op
BenchmarkRedis7_TTL_integration-4 32029 163338 ns/op 236 B/op 8 allocs/op
BenchmarkRedis7_TTL_parallel_integration-4 117226 56544 ns/op 236 B/op 8 allocs/op
BenchmarkRedis7_Stats-4 23600 254668 ns/op 5604 B/op 9 allocs/op
BenchmarkRedis7_Stats_parallel-4 59908 100755 ns/op 5604 B/op 9 allocs/op
func ExampleMulti() {
// create a frontend - backend multi cache.
frontCache := xcache.NewMemory(10 * 1024 * 1024) // 10 Mb
backCache := xcache.NewRedis6(xcache.RedisConfig{
Addrs: []string{"127.0.0.1:6379"},
})
defer backCache.Close()
cache := xcache.NewMulti(frontCache, backCache)
ctx := context.Background()
key := "example-multi"
value := []byte("Hello Multi Cache")
ttl := 10 * time.Minute
// save a key for 10 minutes
if err := cache.Save(ctx, key, value, ttl); err != nil {
fmt.Println("could not save Multi cache key: " + err.Error())
}
// load the key's value
if value, err := cache.Load(ctx, key); err != nil {
fmt.Println("could not get Multi cache key: " + err.Error())
} else {
fmt.Println(string(value))
}
// should output:
// Hello Multi Cache
}
If you need to change caches' configs without redeploying your application, you can use the xconf pkg adapter to initialize the caches: NewMemoryWithConfig
/ NewRedis6WithConfig
/ NewRedis7WithConfig
.
If you need to monitor your cache's statistics, you can check StatsWatcher
which can help you in this matter. It executes periodically a provided callback upon cache's Stats
, thus, you can log them / sent them to a metrics system.
in scripts
folder there is a shell script that sets up a Redis docker based environment with desired configuration and runs integration tests / benchmarks.
cd /path/to/xcache
./scripts/run_local.sh cluster // example of running tests in Redis cluster setup
./scripts/run_local.sh single bench // example of running benchmarks in Redis single instance setup.
Things that can be added to pkg, extended:
- Support also Memcached.
This package is released under a MIT license. See LICENSE.
Other 3rd party packages directly used by this package are released under their own licenses.
- github.com/coocood/freecache - MIT License
- github.com/redis/go-redis/v8 - BSD (2 Clause) License
- github.com/redis/go-redis/v9 - BSD (2 Clause) License
- github.com/actforgood/xerr - MIT License
- github.com/actforgood/xlog - MIT License
- github.com/actforgood/xconf - MIT License