Skip to content

Commit

Permalink
LRU upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
mantzas committed Nov 16, 2024
1 parent bc84d26 commit 6fb175b
Show file tree
Hide file tree
Showing 22 changed files with 626 additions and 494 deletions.
53 changes: 38 additions & 15 deletions cache/lru/lru.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,83 @@ import (
"context"

"github.com/beatlabs/patron/cache"
lru "github.com/hashicorp/golang-lru"
lru "github.com/hashicorp/golang-lru/v2"
"go.opentelemetry.io/otel/attribute"
)

var (
_ cache.Cache = &Cache{}
lruAttribute = attribute.String("cache.type", "lru")
lruAttribute = attribute.String("cache.type", "lru")
lruEvictAttribute = attribute.String("cache.type", "lru-evict")
)

// Cache encapsulates a thread-safe fixed size LRU cache.
type Cache struct {
cache *lru.Cache
type Cache[k comparable, v any] struct {
cache *lru.Cache[k, v]
useCaseAttribute attribute.KeyValue
typeAttribute attribute.KeyValue
}

// New returns a new LRU cache that can hold 'size' number of keys at a time.
func New(size int, useCase string) (*Cache, error) {
func New[k comparable, v any](size int, useCase string) (*Cache[k, v], error) {
cache.SetupMetricsOnce()
chc, err := lru.New(size)
chc, err := lru.New[k, v](size)
if err != nil {
return nil, err
}

return &Cache{
return newFunction(chc, lruAttribute, cache.UseCaseAttribute(useCase))
}

// NewWithEvict returns a new LRU cache that can hold 'size' number of keys at a time.
func NewWithEvict[k comparable, v any](size int, useCase string, onEvict func(k, v)) (*Cache[k, v], error) {
cache.SetupMetricsOnce()

chc, err := lru.NewWithEvict[k, v](size, func(key k, value v) {
onEvict(key, value)
cache.ObserveEviction(context.Background(), lruEvictAttribute, cache.UseCaseAttribute(useCase))
})
if err != nil {
return nil, err
}

return newFunction(chc, lruEvictAttribute, cache.UseCaseAttribute(useCase))
}

func newFunction[k comparable, v any](chc *lru.Cache[k, v], typeAttr attribute.KeyValue,
useCaseAttr attribute.KeyValue,
) (*Cache[k, v], error) {

Check failure on line 52 in cache/lru/lru.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

newFunction - result 1 (error) is always nil (unparam)
return &Cache[k, v]{
cache: chc,
useCaseAttribute: cache.UseCaseAttribute(useCase),
typeAttribute: typeAttr,
useCaseAttribute: useCaseAttr,
}, nil
}

// Get executes a lookup and returns whether a key exists in the cache along with its value.
func (c *Cache) Get(ctx context.Context, key string) (interface{}, bool, error) {
func (c *Cache[k, v]) Get(ctx context.Context, key k) (interface{}, bool, error) {
value, ok := c.cache.Get(key)
if !ok {
cache.ObserveMiss(ctx, lruAttribute, c.useCaseAttribute)
cache.ObserveMiss(ctx, lruAttribute, c.useCaseAttribute, c.typeAttribute)
return nil, false, nil
}
cache.ObserveHit(ctx, lruAttribute, c.useCaseAttribute)
cache.ObserveHit(ctx, lruAttribute, c.useCaseAttribute, c.typeAttribute)
return value, true, nil
}

// Purge evicts all keys present in the cache.
func (c *Cache) Purge(_ context.Context) error {
func (c *Cache[k, v]) Purge(_ context.Context) error {
c.cache.Purge()
return nil
}

// Remove evicts a specific key from the cache.
func (c *Cache) Remove(_ context.Context, key string) error {
func (c *Cache[k, v]) Remove(_ context.Context, key k) error {
c.cache.Remove(key)
return nil
}

// Set registers a key-value pair to the cache.
func (c *Cache) Set(_ context.Context, key string, value interface{}) error {
func (c *Cache[k, v]) Set(_ context.Context, key k, value v) error {
c.cache.Add(key, value)
return nil
}
46 changes: 44 additions & 2 deletions cache/lru/lru_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,32 @@ func TestNew(t *testing.T) {

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
c, err := New(tt.size, "test")
c, err := New[string, string](tt.size, "test")
if tt.wantErr {
assert.Nil(t, c)
assert.EqualError(t, err, tt.err)
} else {
assert.NotNil(t, c)
require.NoError(t, err)
}
})
}
}

func TestNewWithEvict(t *testing.T) {
tests := map[string]struct {
err string
size int
wantErr bool
}{
"negative size": {size: -1, wantErr: true, err: "must provide a positive size"},
"zero size": {size: 0, wantErr: true, err: "must provide a positive size"},
"positive size": {size: 1024, wantErr: false},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
c, err := NewWithEvict[string, string](tt.size, "test", func(k, v string) {})

Check failure on line 49 in cache/lru/lru_test.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

unused-parameter: parameter 'k' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 49 in cache/lru/lru_test.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

unused-parameter: parameter 'v' seems to be unused, consider removing or renaming it as _ (revive)
if tt.wantErr {
assert.Nil(t, c)
assert.EqualError(t, err, tt.err)
Expand All @@ -34,7 +59,7 @@ func TestNew(t *testing.T) {
}

func TestCacheOperations(t *testing.T) {
c, err := New(10, "test")
c, err := NewWithEvict[string, string](10, "test", func(k, v string) {})

Check failure on line 62 in cache/lru/lru_test.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

unused-parameter: parameter 'k' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 62 in cache/lru/lru_test.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

unused-parameter: parameter 'v' seems to be unused, consider removing or renaming it as _ (revive)
assert.NotNil(t, c)
require.NoError(t, err)

Expand Down Expand Up @@ -80,3 +105,20 @@ func TestCacheOperations(t *testing.T) {
assert.Equal(t, 0, c.cache.Len())
})
}

func BenchmarkCache(b *testing.B) {
c, err := NewWithEvict[int, int](b.N, "test", func(k, v int) {})

Check failure on line 110 in cache/lru/lru_test.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

unused-parameter: parameter 'k' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 110 in cache/lru/lru_test.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

unused-parameter: parameter 'v' seems to be unused, consider removing or renaming it as _ (revive)
require.NoError(b, err)

ctx := context.Background()
for i := 0; i < b.N; i++ {
err = c.Set(ctx, i, i)
require.NoError(b, err)
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err = c.Get(ctx, i)
require.NoError(b, err)
}
}
18 changes: 12 additions & 6 deletions cache/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
const packageName = "cache"

var (
cashHitAttribute = attribute.String("cache.status", "hit")
cashMissAttribute = attribute.String("cache.status", "miss")
cacheCounter metric.Int64Counter
cacheOnce sync.Once
cacheHitAttribute = attribute.String("cache.status", "hit")
cacheMissAttribute = attribute.String("cache.status", "miss")
cacheEvictAttribute = attribute.String("cache.status", "evict")
cacheCounter metric.Int64Counter
cacheOnce sync.Once
)

// SetupMetricsOnce initializes the cache counter.
Expand All @@ -32,12 +33,17 @@ func UseCaseAttribute(useCase string) attribute.KeyValue {

// ObserveHit increments the cache hit counter.
func ObserveHit(ctx context.Context, attrs ...attribute.KeyValue) {
attrs = append(attrs, cashHitAttribute)
attrs = append(attrs, cacheHitAttribute)
cacheCounter.Add(ctx, 1, metric.WithAttributes(attrs...))
}

// ObserveMiss increments the cache miss counter.
func ObserveMiss(ctx context.Context, attrs ...attribute.KeyValue) {
attrs = append(attrs, cashMissAttribute)
attrs = append(attrs, cacheMissAttribute)
cacheCounter.Add(ctx, 1, metric.WithAttributes(attrs...))
}

func ObserveEviction(ctx context.Context, attrs ...attribute.KeyValue) {
attrs = append(attrs, cacheEvictAttribute)

Check warning on line 47 in cache/metric.go

View check run for this annotation

Codecov / codecov/patch

cache/metric.go#L46-L47

Added lines #L46 - L47 were not covered by tests
cacheCounter.Add(ctx, 1, metric.WithAttributes(attrs...))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/elastic/go-elasticsearch/v8 v8.16.0
github.com/go-sql-driver/mysql v1.8.1
github.com/google/uuid v1.6.0
github.com/hashicorp/golang-lru v1.0.2
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/rabbitmq/amqp091-go v1.10.0
github.com/redis/go-redis/extra/redisotel/v9 v9.7.0
github.com/redis/go-redis/v9 v9.7.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
Expand Down
30 changes: 0 additions & 30 deletions vendor/github.com/hashicorp/golang-lru/.golangci.yml

This file was deleted.

7 changes: 0 additions & 7 deletions vendor/github.com/hashicorp/golang-lru/README.md

This file was deleted.

Loading

0 comments on commit 6fb175b

Please sign in to comment.