Skip to content

Commit

Permalink
Merge 2a07122 into 51598f3
Browse files Browse the repository at this point in the history
  • Loading branch information
tyzhnenko committed Oct 14, 2019
2 parents 51598f3 + 2a07122 commit f33307f
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 11 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@

The library adds a thin layer on top of [lru cache](https://github.com/hashicorp/golang-lru) and [patrickmn/go-cache](https://github.com/patrickmn/go-cache).

| Cache name | Constructor | Defaults | Description |
| -------------- | --------------------- | ----------------- | --------------------- |
| LruCache | lcw.NewLruCache | keys=1000 | LRU cache with limits |
| ExpirableCache | lcw.NewExpirableCache | keys=1000, ttl=5m | TTL cache with limits |
| Nop | lcw.NewNopCache | | Do-nothing cache |
| Cache name | Constructor | Defaults | Description |
| -------------- | --------------------- | ----------------- | ----------------------- |
| LruCache | lcw.NewLruCache | keys=1000 | LRU cache with limits |
| ExpirableCache | lcw.NewExpirableCache | keys=1000, ttl=5m | TTL cache with limits |
| RedisCache | lcw.NewRedisCache | ttl=5m | Redis cache with limits |
| Nop | lcw.NewNopCache | | Do-nothing cache |


Main features:

- LoadingCache (guava style)
- Limit maximum cache size (in bytes)
- Limit maximum key size
- Limit maximum size of a value
- Limit maximum size of a value
- Limit number of keys
- TTL support (`ExpirableCache` only)
- Callback on eviction event
- TTL support (`ExpirableCache` and `RedisCache`)
- Callback on eviction event (not supported in `RedisCache`)
- Functional style invalidation
- Functional options
- Sane defaults

## Install and update

`go get -u github.com/go-pkgz/lcw`
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module github.com/go-pkgz/lcw

require (
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-redis/redis/v7 v7.0.0-beta.4
github.com/hashicorp/golang-lru v0.5.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.8.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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-redis/redis/v7 v7.0.0-beta.4 h1:p6z7Pde69EGRWvlC++y8aFcaWegyrKHzOBGo0zUACTQ=
github.com/go-redis/redis/v7 v7.0.0-beta.4/go.mod h1:xhhSbUMTsleRPur+Vgx9sUHtyN33bdjxY+9/0n9Ig8s=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
Expand Down
2 changes: 1 addition & 1 deletion lru_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/pkg/errors"
)

// LruCache wraps lru.LruCache with laoding cache Get and size limits
// LruCache wraps lru.LruCache with loading cache Get and size limits
type LruCache struct {
options
CacheStat
Expand Down
129 changes: 129 additions & 0 deletions redis_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package lcw

import (
"sync/atomic"
"time"

redis "github.com/go-redis/redis/v7"
"github.com/pkg/errors"
)

// RedisSizeLimit is maximum allowed value size in Redis
const RedisSizeLimit = 512 * 1024 * 1024

// RedisCache implements LoadingCache for Redis.
type RedisCache struct {
options
CacheStat
backend *redis.Client
}

// NewRedisCache makes Redis LoadingCache implementation.
func NewRedisCache(backend *redis.Client, opts ...Option) (*RedisCache, error) {

res := RedisCache{
options: options{
ttl: 5 * time.Minute,
},
}
for _, opt := range opts {
if err := opt(&res.options); err != nil {
return nil, errors.Wrap(err, "failed to set cache option")
}
}

res.backend = backend

return &res, nil
}

// Get gets value by key or load with fn if not found in cache
func (c *RedisCache) Get(key string, fn func() (Value, error)) (data Value, err error) {

v, getErr := c.backend.Get(key).Result()
switch getErr {
case nil:
atomic.AddInt64(&c.Hits, 1)
return v, nil
case redis.Nil:
if data, err = fn(); err != nil {
atomic.AddInt64(&c.Errors, 1)
return data, err
}
default:
atomic.AddInt64(&c.Errors, 1)
return v, getErr
}
atomic.AddInt64(&c.Misses, 1)

if c.allowed(key, data) {
c.backend.Set(key, data, c.ttl)
}
return data, nil
}

// Invalidate removes keys with passed predicate fn, i.e. fn(key) should be true to get evicted
func (c *RedisCache) Invalidate(fn func(key string) bool) {
for _, key := range c.backend.Keys("*").Val() { // Keys() returns copy of cache's key, safe to remove directly
if fn(key) {
c.backend.Del(key)
}
}
}

// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
func (c *RedisCache) Peek(key string) (Value, bool) {
ret, err := c.backend.Get(key).Result()
if err != nil {
return nil, false
}
return ret, true
}

// Purge clears the cache completely.
func (c *RedisCache) Purge() {
c.backend.FlushDB()

}

// Delete cache item by key
func (c *RedisCache) Delete(key string) {
c.backend.Del(key)
}

// Stat returns cache statistics
func (c *RedisCache) Stat() CacheStat {
return CacheStat{
Hits: c.Hits,
Misses: c.Misses,
Size: c.size(),
Keys: c.keys(),
Errors: c.Errors,
}
}

func (c *RedisCache) size() int64 {
return 0
}

func (c *RedisCache) keys() int {
return int(c.backend.DBSize().Val())
}

func (c *RedisCache) allowed(key string, data Value) bool {
if c.backend.DBSize().Val() >= int64(c.maxKeys) {
return false
}
if c.maxKeySize > 0 && len(key) > c.maxKeySize {
return false
}
if s, ok := data.(Sizer); ok {
if c.maxValueSize > 0 && (s.Size() >= c.maxValueSize || s.Size() >= RedisSizeLimit) {
return false
}
if c.maxValueSize <= 0 && s.Size() >= RedisSizeLimit {
return false
}
}
return true
}

0 comments on commit f33307f

Please sign in to comment.