-
Notifications
You must be signed in to change notification settings - Fork 374
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
MaxCost sometimes ignored #96
Comments
Thanks for finding this! From what I can tell, this is what's happening:
Definitely a confusing one. I'm going to dig around. |
I believe the issue is in here. Edit: Nevermind, using another bloom filter implementation didn't fix anything. The issue has something to do with Get increments. |
Spent a few hours looking at this, going to continue tomorrow. Some notes for future reference (from
func (p *tinyLFU) Increment(key uint64) {
// flip doorkeeper bit if not already
//if added := p.door.AddIfNotHas(key); !added {
// increment count-min counter if doorkeeper bit is already set.
//p.freq.Increment(key)
//}
p.incrs++
if p.incrs >= p.resetAt {
p.reset()
}
}
func (p *tinyLFU) Increment(key uint64) {
// flip doorkeeper bit if not already
//if added := p.door.AddIfNotHas(key); !added {
// increment count-min counter if doorkeeper bit is already set.
p.freq.Increment(key)
//}
p.incrs++
if p.incrs >= p.resetAt {
p.reset()
}
}
func (p *tinyLFU) Increment(key uint64) {
// flip doorkeeper bit if not already
if added := p.door.AddIfNotHas(key); !added {
// increment count-min counter if doorkeeper bit is already set.
//p.freq.Increment(key)
}
p.incrs++
if p.incrs >= p.resetAt {
p.reset()
}
} |
Maybe this is due to updating a key with a large size without recalculating its cost due to the if c.store.Update(i.keyHash, i.key, i.value) {
i.flag = itemUpdate
}
// attempt to send item to policy
select {
case c.setBuf <- i:
return true
default:
c.Metrics.add(dropSets, i.keyHash, 1)
return false
} |
@brk0v While we do have an eventually-consistent cost mechanism, the problem seems to be within the interaction between TinyLFU counter increments and Sets... For example, forcing the value + cost update does nothing: func (c *Cache) Set(key, value interface{}, cost int64) bool {
if c == nil || key == nil {
return false
}
i := &item{
flag: itemNew,
key: key,
keyHash: z.KeyToHash(key, 0),
value: value,
cost: cost,
}
// attempt to immediately update hashmap value and set flag to update so the
// cost is eventually updated
if c.store.Update(i.keyHash, i.key, i.value) {
i.flag = itemUpdate
}
c.setBuf <- i
return true
/*
// attempt to send item to policy
select {
case c.setBuf <- i:
return true
default:
c.Metrics.add(dropSets, i.keyHash, 1)
return false
}
*/
} And the cost is updated here. |
When I print out |
Scratch that, it still happens, but weirdly it happens a lot slower it seems. But if you remove the Get it doesn't happen. |
I'm seeing this behavior in real life as well. I'm using this ristretto test for microcache: https://github.com/erikdubbelboer/microcache/tree/ristretto pprof output:
|
I tried to reproduce this locally and couldn't. So I tried on another machine and I was able to reproduce the error. I compared the versions and realized I had an older version checked out which made me think this was a regression. Tracked it down to #75 on Oct 1st. I read the diff and spotted the error. // Before Oct 1st commit
victims, added := c.policy.Add(item.key, item.cost)
if added {
// ...
}
for _, victim := range victims {
// ...
}
// After Oct 1st commit
if victims, added := c.policy.Add(item.key, item.cost); added {
// ...
for _, victim := range victims {
// ...
}
} The new code is different, causing victims to sometimes not be deleted. Submitted PR #99 |
With the PR it appears to be fixed on my machine. Let me know if you run into any more issues @erikdubbelboer and I'll re-open. |
Seems to be fixed 👍 |
Shouldn't there be some test added for this so it doesn't happen again in the future? Ristretto not limiting it's cache to the configured size seems like a major bug to me that should never happen. |
@erikdubbelboer I'm working on a Test PR right now. |
I have written a simple program that Gets and Sets entries in Ristretto. The program sets a
MaxCost
(in bytes) and passes a value for the cost toSet
based on the size in bytes of the value.Code can be found here: https://gist.github.com/erikdubbelboer/bd904c51f00079421fd3eb2a061a50c0
As you can see from the source Ristretto should be limited to 1GB of memory. The program doesn't do anything strange.
In most cases this program works fine and Ristretto limits the memory usage correctly. But in the case of the exact code as above the memory usage keeps growing.
Also keep in mind that
runtime.GC()
is run every second so it's not just uncollected allocations we seeing here. Not runningruntime.GC()
has the same result just more variation in the amount allocated.This is the output when run for a couple of seconds (just
go run ristretto-bug.go
):The output of http://localhost:6060/debug/pprof/heap?debug=1 shows that all memory is allocated inside
strings.Repeat
.My guess is that in some cases Ristretto keeps references to memory for entries that aren't part of the cache anymore.
The text was updated successfully, but these errors were encountered: