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

feat: New WithMaxCost option for custom cache capacity management strategies #152

Merged
merged 36 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2d92605
new option to define the max memory the cache can use in bytes
dadrus Oct 22, 2024
6e92deb
cache.set updated to cope with memory limitations
dadrus Oct 22, 2024
666a987
options updated to implement the suggested new option
dadrus Nov 10, 2024
0a2236a
cache impl updated + tests
dadrus Nov 10, 2024
fa705e7
dependencies updated
dadrus Nov 10, 2024
dcc9743
imports organized
dadrus Nov 10, 2024
7d51be1
readme updated to document the new configuration option
dadrus Nov 10, 2024
fea314a
better explanation in the example
dadrus Nov 10, 2024
e7e24cc
readme line removed by accident restored
dadrus Nov 10, 2024
53b1934
function renamed (typo fixed)
dadrus Nov 10, 2024
7a9baa3
wording fixed
dadrus Nov 10, 2024
0b17592
useless sentence removed
dadrus Nov 10, 2024
d1bdbee
costs renamed to cost
dadrus Nov 14, 2024
a2f5915
cost used in tests simplified
dadrus Nov 14, 2024
8965baf
CostCalcFunc renamed to CostFunc
dadrus Nov 14, 2024
58dcb90
costsCalcFunc renamed to costFunc
dadrus Nov 14, 2024
23cfbf0
updateExpirations moved
dadrus Nov 14, 2024
08a47e8
totalCost renamed to maxCost
dadrus Nov 14, 2024
c594b8d
WithTotalCost option renamed to WithMaxCost
dadrus Nov 14, 2024
c4b0baa
example in README updated
dadrus Nov 14, 2024
2a04fee
signature of the WithMaxCost option changed
dadrus Nov 14, 2024
e595b68
Merge branch 'v3' into feat/cache_size_in_bytes
dadrus Nov 14, 2024
e36c05f
readme updated
dadrus Nov 14, 2024
ce9482f
Item impl updated to hold the current cost and the corresponding cost…
dadrus Nov 17, 2024
da1f07e
cache impl updated to make use of the new item properties
dadrus Nov 17, 2024
d275891
new item options
dadrus Nov 17, 2024
d20ea4d
tests for the new options
dadrus Nov 17, 2024
692baf0
new Item related tests
dadrus Nov 17, 2024
2308b72
cache tests fixed to make them compile
dadrus Nov 17, 2024
fd2c7ad
item option implementation updated to become a private interface
dadrus Nov 27, 2024
2a15bc1
cache set test updated
dadrus Nov 27, 2024
eebe1a8
made new item related functions private + eviction reason renamed
dadrus Nov 29, 2024
5cb43bf
moved initial item cost calsulation to the constructor function
dadrus Nov 29, 2024
e70f90d
test functions renamed to reflect the private nature of functions und…
dadrus Nov 29, 2024
43fc033
version tracking fixed
dadrus Nov 29, 2024
41f3351
small readme update
dadrus Nov 29, 2024
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
38 changes: 38 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync"
"time"

"github.com/DmitriyVTitov/size"
dadrus marked this conversation as resolved.
Show resolved Hide resolved
"golang.org/x/sync/singleflight"
)

Expand All @@ -15,6 +16,7 @@ const (
EvictionReasonDeleted EvictionReason = iota + 1
EvictionReasonCapacityReached
EvictionReasonExpired
EvictionReasonMaxMemorySizeExceeded
)

// EvictionReason is used to specify why a certain item was
Expand All @@ -36,6 +38,7 @@ type Cache[K comparable, V any] struct {

timerCh chan time.Duration
}
sizeInBytes uint64

metricsMu sync.RWMutex
metrics Metrics
Expand Down Expand Up @@ -137,7 +140,23 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] {
if elem != nil {
// update/overwrite an existing item
item := elem.Value.(*Item[K, V])
oldValue := item.value
item.update(value, ttl)

if c.options.sizeInBytes != 0 {
oldSize := size.Of(oldValue)
newSize := size.Of(value)

// size.Of returns -1 on errors
if oldSize != -1 && newSize != -1 {
c.sizeInBytes = c.sizeInBytes - uint64(oldSize) + uint64(newSize)
}

for c.sizeInBytes > c.options.sizeInBytes {
c.evict(EvictionReasonMaxMemorySizeExceeded, c.items.lru.Back())
}
}

c.updateExpirations(false, elem)
dadrus marked this conversation as resolved.
Show resolved Hide resolved

return item
Expand All @@ -158,6 +177,17 @@ func (c *Cache[K, V]) set(key K, value V, ttl time.Duration) *Item[K, V] {
c.items.values[key] = elem
c.updateExpirations(true, elem)

if c.options.sizeInBytes != 0 {
itemSize := size.Of(item)
if itemSize != -1 {
c.sizeInBytes += uint64(itemSize)
}

for c.sizeInBytes > c.options.sizeInBytes {
c.evict(EvictionReasonMaxMemorySizeExceeded, c.items.lru.Back())
}
}

c.metricsMu.Lock()
c.metrics.Insertions++
c.metricsMu.Unlock()
Expand Down Expand Up @@ -258,6 +288,14 @@ func (c *Cache[K, V]) evict(reason EvictionReason, elems ...*list.Element) {
for i := range elems {
item := elems[i].Value.(*Item[K, V])
delete(c.items.values, item.key)

if c.options.sizeInBytes != 0 {
itemSize := size.Of(item)
if itemSize != -1 {
c.sizeInBytes -= uint64(itemSize)
}
}

c.items.lru.Remove(elems[i])
c.items.expQueue.remove(elems[i])

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/jellydator/ttlcache/v3
go 1.18

require (
github.com/DmitriyVTitov/size v1.5.0
github.com/stretchr/testify v1.9.0
go.uber.org/goleak v1.3.0
golang.org/x/sync v0.8.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g=
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
Expand Down
9 changes: 9 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func (fn optionFunc[K, V]) apply(opts *options[K, V]) {
// options holds all available cache configuration options.
type options[K comparable, V any] struct {
capacity uint64
sizeInBytes uint64
dadrus marked this conversation as resolved.
Show resolved Hide resolved
ttl time.Duration
loader Loader[K, V]
disableTouchOnHit bool
Expand Down Expand Up @@ -75,3 +76,11 @@ func WithDisableTouchOnHit[K comparable, V any]() Option[K, V] {
opts.disableTouchOnHit = true
})
}

// WithMemorySize sets the maximum memory size the cache is allowed to grow.
// If used together with WithCapacity, WithMemorySize overrules the maximum capacity.
func WithMemorySize[K comparable, V any](s uint64) Option[K, V] {
dadrus marked this conversation as resolved.
Show resolved Hide resolved
return optionFunc[K, V](func(opts *options[K, V]) {
opts.sizeInBytes = s
})
}
7 changes: 7 additions & 0 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,10 @@ func Test_WithDisableTouchOnHit(t *testing.T) {
WithDisableTouchOnHit[string, string]().apply(&opts)
assert.True(t, opts.disableTouchOnHit)
}

func Test_WithMemorySize(t *testing.T) {
var opts options[string, string]

WithMemorySize[string, string](1024).apply(&opts)
assert.Equal(t, uint64(1024), opts.sizeInBytes)
}