Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LRU upgrade #785

Merged
merged 2 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@

// 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
Loading