Skip to content

Commit

Permalink
LRU upgrade (#785)
Browse files Browse the repository at this point in the history
<!--
Thanks for taking precious time for making a PR.

Before creating a pull request, please make sure:
- Your PR solves one problem for which an issue exist and a solution has
been discussed
- You have read the guide for contributing
- See
https://github.com/beatlabs/patron/blob/master/README.md#how-to-contribute
- You signed all your commits (otherwise we won't be able to merge the
PR)
  - See https://github.com/beatlabs/patron/blob/master/SIGNYOURWORK.md
- You added unit tests for the new functionality
- You mention in the PR description which issue it is addressing, e.g.
"Resolves #123"
-->

## Which problem is this PR solving?

<!-- REQUIRED -->

## Short description of the changes

<!-- REQUIRED -->
  • Loading branch information
mantzas authored Nov 16, 2024
1 parent bc84d26 commit 56680b8
Show file tree
Hide file tree
Showing 22 changed files with 627 additions and 495 deletions.
55 changes: 39 additions & 16 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)), nil
}

// 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)), nil
}

func newFunction[k comparable, v any](chc *lru.Cache[k, v], typeAttr attribute.KeyValue,
useCaseAttr attribute.KeyValue,
) *Cache[k, v] {
return &Cache[k, v]{
cache: chc,
useCaseAttribute: cache.UseCaseAttribute(useCase),
}, nil
typeAttribute: typeAttr,
useCaseAttribute: useCaseAttr,
}
}

// 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(_, _ string) {})
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(_, _ string) {})
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(_, _ int) {})
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)
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 56680b8

Please sign in to comment.