dgraph-io / ristretto Goto Github PK
View Code? Open in Web Editor NEWA high performance memory-bound Go cache
Home Page: https://dgraph.io/blog/post/introducing-ristretto-high-perf-go-cache/
License: Apache License 2.0
A high performance memory-bound Go cache
Home Page: https://dgraph.io/blog/post/introducing-ristretto-high-perf-go-cache/
License: Apache License 2.0
Requested by someone at GoSF meetup.
Ensure you do not have ristretto on your system and run go get -v github.com/dgraph-io/ristretto
$ go get -v github.com/dgraph-io/ristretto
go get: warning: modules disabled by GO111MODULE=auto in GOPATH/src;
ignoring go.mod;
see 'go help modules'
github.com/dgraph-io/ristretto (download)
# cd .; git clone https://github.com/dgraph-io/ristretto C:\Users\Ibrahim\go\src\github.com\dgraph-io\ristretto
Cloning into 'C:\Users\Ibrahim\go\src\github.com\dgraph-io\ristretto'...
Downloading bench/trace/ds1.arc.gz (7.4 MB)
Error downloading object: bench/trace/ds1.arc.gz (1c4eb88): Smudge error: Error downloading bench/trace/ds1.arc.gz (1c4eb8811b9840a6a006b5c6560e0a93e2dc978fae274378fb48ce2969e78366): batch response: This repository is over its data quota. Purchase more data packs to restore access.
Errors logged to C:\Users\Ibrahim\go\src\github.com\dgraph-io\ristretto\.git\lfs\logs\20190906T074835.9821484.log
Use `git lfs logs last` to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: bench/trace/ds1.arc.gz: smudge filter lfs failed
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry the checkout with 'git checkout -f HEAD'
package github.com/dgraph-io/ristretto: exit status 128
A similar failure was seen on https://ci.appveyor.com/project/manishrjain/badger/builds/27224437
I see you keep creating milestones and closing them, iterating the version. But where are the releases?
I'm sure you have your reasons, but would you be interested in doing git tags+github releases as well?
Currently here https://github.com/dgraph-io/ristretto/blob/master/z/z.go#L20 all of the possible types are written out. However, users like me may want to use a "custom" struct as a key but that does not work at the moment since it is not written there and then my whole program panics.
I propose the following: add some kind of "advanced" option to the library which is a function (func (key interface{}) (uint64, error)
) which accepts the key (interface{}
) and produces a uint64
hash. It then could be called in the default
case. If an error happens - only then we should panic.
What do you think?
Migrate away from Github actions and use something else as discussed.
Existing Implementation Reference: https://github.com/dgryski/go-tinylfu
Reference Paper: https://drive.google.com/file/d/1xW69zyULqcPf63A-cLdNsNOVrr8QygOr/view?usp=sharing
The current NewCache constructor requires the user to hard code ristretto defaults in their code.
Specifically, NumCounters is 100 x estimated items. The interface should allow the user to avoid hardcoding this into their code. Similarly, BufferItems: 64 is just a good number that works. The user should not need to hardcode this into client software either.
If any implementation changes in the future affect these currently valid defaults, the lack of encapsulation will require unnecessary changes to client code.
I suggest adding something like the following code (which also encapsulates the most likely default panic behavior on constructor errors). This allows the client to use defaults appropriate to their current version of ristretto. Also, it provides a simplified constructor that does not require client code knowledge of typically encapsulated implementation details.
As a small side benefit, the example code will also be cleaner and clearer.
// CountersPerItem is a good multiple of the number of expected items to track
const CountersPerItem = 100
// DefaultBufferItems is a good default size for Get buffers
const DefaultBufferItems = 64
func NewDefaultCache(expectedItems, avgCostPerItem int64) (cache *Cache) {
cache, err := NewCache(&Config{
NumCounters: expectedItems * CountersPerItem,
MaxCost: expectedItems * avgCostPerItem,
BufferItems: DefaultBufferItems, // number of keys per Get buffer.
})
if err != nil {
panic(err)
}
return
}
Support multi delete.
Make Del as a vardic function to support delete.
I believe the commit f823dc4 broke the purpose of the cm sketch to have four rows. The bitwise-xor and mask are not creating the mix of indexes expected by the design. The same Increment/Estimate results, the same values, are achieved from a single row with or without the bitwise-xor. The earlier implementation seems to have been a good and very fast approximation to a distinct hash for each row except when the high 32 bits were all zero. One solution to fixing the earlier version could be to bitwise-or a single bit into the top 32 half. I can provide my testing and benchmarking on this offline if interested.
For small values of row length as used in the current unit tests, this doesn't matter. As the row length gets larger, the gap between the earlier algorithm and the current one widens and I believe becomes significant.
With #48 we introduced CI for ristretto but it has to be enabled in the repository settings.
I am adding ristretto to Dgraph but I am seeing a few issues that are caused because the cache is not being cleared after a DropAll operation.
I tried fixing this by recreating the entire cache every time that a DropAll op is processed, but this leads to out of memory issues. Ideally, ristretto has an API to efficiently clear the entire contents of the cache.
I'll take a look at the code and see if I can come up with something myself, but most likely I will need help from someone more familiar with the internals of ristretto.
https://blog.xfce.org/2008/03/ristretto-a-lightweight-image-viewer/
not sure this matters, but i figure this is a much better time to point it out than a year from now would be.
Github actions always fail with
- Workflows can't be executed on this repository. Please check your payment method or billing status.
see https://github.com/dgraph-io/ristretto/pull/118/checks?check_run_id=382966914
We should get rid of it since we now have Travis builds.
Would you consider adding a time aware LFU, such that completely new entries can't have the probability of being evicted right after insertion?
Eg. New entries are given a 1 minute time frame to become useful. Potentially increasing survival rates of fresh entries.
==================
WARNING: DATA RACE
Read at 0x00c019349648 by goroutine 35:
github.com/dgraph-io/ristretto.(*Cache).Set()
/home/pawan0201/go/pkg/mod/github.com/dgraph-io/[email protected]/cache.go:205 +0x1ed
github.com/dgraph-io/badger/v2/table.(*Table).block()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/table.go:459 +0xe9a
github.com/dgraph-io/badger/v2/table.(*Iterator).next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:296 +0x216
github.com/dgraph-io/badger/v2/table.(*Iterator).next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:311 +0x699
github.com/dgraph-io/badger/v2/table.(*Iterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:367 +0x70
github.com/dgraph-io/badger/v2/table.(*ConcatIterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:485 +0x59
github.com/dgraph-io/badger/v2/table.(*node).next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/merge_iterator.go:82 +0xce
github.com/dgraph-io/badger/v2/table.(*MergeIterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/merge_iterator.go:158 +0x6a
github.com/dgraph-io/badger/v2/table.(*node).next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/merge_iterator.go:80 +0x73
github.com/dgraph-io/badger/v2/table.(*MergeIterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/merge_iterator.go:158 +0x6a
github.com/dgraph-io/badger/v2.(*Iterator).parseItem()
/home/pawan0201/go/src/github.com/dgraph-io/badger/iterator.go:648 +0x933
github.com/dgraph-io/badger/v2.(*Iterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/iterator.go:561 +0x1fd
github.com/dgraph-io/badger/v2.TestDropAllWithPendingTxn.func3()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:218 +0x183
Previous write at 0x00c019349648 by goroutine 82:
github.com/dgraph-io/ristretto.(*Cache).Clear()
/home/pawan0201/go/pkg/mod/github.com/dgraph-io/[email protected]/cache.go:254 +0x9f
github.com/dgraph-io/badger/v2.(*DB).dropAll()
/home/pawan0201/go/src/github.com/dgraph-io/badger/db.go:1511 +0x57e
github.com/dgraph-io/badger/v2.(*DB).DropAll()
/home/pawan0201/go/src/github.com/dgraph-io/badger/db.go:1467 +0x47
github.com/dgraph-io/badger/v2.TestDropAllWithPendingTxn.func4()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:248 +0x7f
Goroutine 35 (running) created at:
github.com/dgraph-io/badger/v2.TestDropAllWithPendingTxn()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:210 +0x3e6
testing.tRunner()
/usr/local/go/src/testing/testing.go:909 +0x199
Goroutine 82 (running) created at:
github.com/dgraph-io/badger/v2.TestDropAllWithPendingTxn()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:245 +0x43f
testing.tRunner()
/usr/local/go/src/testing/testing.go:909 +0x199
==================
badger 2020/02/05 10:11:51 INFO: Resuming writes
badger 2020/02/05 10:11:51 INFO: Got compaction priority: {level:0 score:1.73 dropPrefix:[]}
--- FAIL: TestDropAllWithPendingTxn (8.77s)
managed_db_test.go:228: Got number of keys: 10000
managed_db_test.go:224: Got error during value copy: Invalid value pointer offset: 3689714 greater than current offset: 20
testing.go:853: race detected during execution of test
The line in question is
Line 205 in 8f368f2
Line 254 in 8f368f2
Thanks for the great write-up on this exciting project. Per a note in HN from one of the maintainers, I'm inquiring here about a possible race condition between Set()
and Del()
.
As of this writing, Set()
asynchronously enqueues the actual write, where as Del()
seems to synchronously update the internal store. In other words, Del()
does not appear to be aware of the write buffering implemented by Set()
.
At first glance, this would seem to introduce a race condition for invalidation. Wouldn't it be possible for the following sequence to be incorrect?
_ := c.Set(k, v, cost)
c.Del(k)
got, ok := c.Get(k)
I think most or all client code would expect read-your-write consistency here, but it does seem like Get()
could return a valid value for k
despite the invalidation on the prior line.
Here I want to get the key numbers current cache contains. Seems it's easy to maintain.
I have the following test case that creates a lot of cache instances, and somehow the memory can not be garbage collected, and therefore used up a lot of memory.
func TestMemLeak(t *testing.T) {
type state struct {
cache *ristretto.Cache
}
t.Run("test_cache", func(t *testing.T) {
for i := 0; i < 10; i++ {
engines := make([]*state, 0)
for e := 0; e < 100; e++ {
cache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1e7, // 10x num items on full cache
MaxCost: 1e6, // 1 MB total size
BufferItems: 64, // recommended value
})
require.Nil(t, err)
cache.Close()
engines = append(engines, &state{
// setting the cache as nil will stop the memory leak
// cache: nil,
cache: cache,
})
}
}
})
}
I measured the memory usage with /usr/bin/time
like this:
time -l go test mem_leak_test.go -v -count=1 -run=TestMemLeak
Running the above test will use 8056582144 maximum resident set size
, 8GB memory was used.
However, if I set cache: nil
then there is no memory leak, 157466624 maximum resident set size
, only 150MB was used.
After adding Ristretto to our micro-service orchestrator written in go, we have been having data corruption issues. The cached data has randomly been inaccurate, forcing me to remove this in-memory cache implementation. Not sure how I would go about logging this information. I can try and take some time over the weekend to create an example repo to exhibit the issue.
Question 1:
We know that the expired data is stored in the map's map, The key generation strategy is related to time.
like
time:0s storageBucket:1 cleanupBucket:0
time:1s storageBucket:1 cleanupBucket:0
time:2s storageBucket:1 cleanupBucket:0
time:3s storageBucket:1 cleanupBucket:0
time:4s storageBucket:1 cleanupBucket:0
time:5s storageBucket:2 cleanupBucket:1
time:6s storageBucket:2 cleanupBucket:1
time:7s storageBucket:2 cleanupBucket:1
time:8s storageBucket:2 cleanupBucket:1
time:9s storageBucket:2 cleanupBucket:1
time:10s storageBucket:3 cleanupBucket:2
time:11s storageBucket:3 cleanupBucket:2
time:12s storageBucket:3 cleanupBucket:2
time:13s storageBucket:3 cleanupBucket:2
time:14s storageBucket:3 cleanupBucket:2
time:15s storageBucket:4 cleanupBucket:3
If my cleanup time set is not good, will some records be skipped?
eg: Every 15s, only found bucket3. bucket1, bucket2 is never be cleaned up again, and alway in the expiration map, is it right?
Question2:
cleanup code as below
func (m *expirationMap) cleanup(store store, policy policy, onEvict onEvictFunc) {
now := time.Now()
bucketNum := cleanupBucket(now)
keys := m.buckets[bucketNum]
delete(m.buckets, bucketNum)
for key, conflict := range keys {
// if key is expired del it
// bla bla
}
}
assume now want to clean bucket1, bucket1 has 100 keys, 60 expired, 40 not, according to the code, 60 keys will be deleted, and bucket1 deleted
the other 40keys, will in store, but never be clean up, it doesn't matter? why design like this?
At the same time, I also noticed that when I get, I only check whether it is expired, and I did not delete it at this time, Wouldn't it be better to delete?
Question3:
cleanupTicker is check every two seconds, Checked the bucket about 5 seconds ago, not all keys expire within 5s(eg. 1Day), So I guess that most of the keys will not be cleaned, but bucket is deleled. So I wonder if this design can be improved?
Thank you very much, please forgive my english
Metrics returns a private type that doesn't show it's methods in the GoDoc. Ideally I'd like to be able to expose the metrics to a prometheus collector. It looks like I could possibly parse some string formatted output, but, ideally, I'd be able to get a current set of structuted mwtrics from the call.
It would be really helpful if we expose this as a Prometheus metric.
Hello,
I've found an issue when playing with the new TTL feature.
It's not possible to reuse a key to update the cache with a new value.
The sample use case :
c, err := NewCache(...)
// Add a value with key 1 in the cache
c.SetWithTTL(1, 1, 1, 5*time.Second)
_, ok := c.Get(1) // return true
// some time later
// Add another value with key 1 in the cache
c.SetWithTTL(1, 1, 1, 5*time.Second)
_, ok := c.Get(1) // return false
I submit the following PR to correct this issue : #130
The following race condition was seen in badger on PR dgraph-io/badger#1066
------- Stdout: -------
=== RUN TestDropAllWithPendingTxn
badger 2019/10/21 08:34:34 INFO: All 0 tables opened in 0s
badger 2019/10/21 08:34:35 DEBUG: Flushing memtable, mt.size=34867 size of flushChan: 0
badger 2019/10/21 08:34:35 DEBUG: Storing value log head: {Fid:0 Len:30 Offset:4831937}
badger 2019/10/21 08:34:35 DEBUG: Storing value log head: {Fid:1 Len:30 Offset:4026620}
badger 2019/10/21 08:34:35 DEBUG: Flushing memtable, mt.size=34837 size of flushChan: 0
badger 2019/10/21 08:34:35 DEBUG: Storing value log head: {Fid:2 Len:30 Offset:3221294}
badger 2019/10/21 08:34:35 DEBUG: Flushing memtable, mt.size=34825 size of flushChan: 0
badger 2019/10/21 08:34:36 DEBUG: Storing value log head: {Fid:3 Len:30 Offset:2415968}
badger 2019/10/21 08:34:36 DEBUG: Flushing memtable, mt.size=34777 size of flushChan: 0
badger 2019/10/21 08:34:36 DEBUG: Storing value log head: {Fid:4 Len:30 Offset:1610642}
badger 2019/10/21 08:34:36 DEBUG: Flushing memtable, mt.size=34757 size of flushChan: 0
badger 2019/10/21 08:34:36 DEBUG: Storing value log head: {Fid:5 Len:30 Offset:805316}
badger 2019/10/21 08:34:36 DEBUG: Flushing memtable, mt.size=34753 size of flushChan: 0
badger 2019/10/21 08:34:36 INFO: Got compaction priority: {level:0 score:1.2 dropPrefix:[]}
badger 2019/10/21 08:34:36 INFO: Running for level: 0
badger 2019/10/21 08:34:36 DEBUG: LOG Compact. Added 3534 keys. Skipped 0 keys. Iteration took: 47.099272ms
badger 2019/10/21 08:34:36 DEBUG: Discard stats: map[]
badger 2019/10/21 08:34:36 INFO: LOG Compact 0->1, del 6 tables, add 1 tables, took 64.409113ms
badger 2019/10/21 08:34:36 INFO: Compaction for level: 0 DONE
badger 2019/10/21 08:34:37 DEBUG: Flushing memtable, mt.size=34895 size of flushChan: 0
badger 2019/10/21 08:34:37 DEBUG: Storing value log head: {Fid:5 Len:30 Offset:5637272}
badger 2019/10/21 08:34:37 DEBUG: Flushing memtable, mt.size=34775 size of flushChan: 0
badger 2019/10/21 08:34:37 DEBUG: Storing value log head: {Fid:6 Len:30 Offset:4831946}
badger 2019/10/21 08:34:37 DEBUG: Storing value log head: {Fid:7 Len:31 Offset:4026628}
badger 2019/10/21 08:34:37 DEBUG: Flushing memtable, mt.size=34753 size of flushChan: 0
badger 2019/10/21 08:34:38 DEBUG: Flushing memtable, mt.size=29077 size of flushChan: 0
badger 2019/10/21 08:34:38 DEBUG: Storing value log head: {Fid:8 Len:31 Offset:2415973}
badger 2019/10/21 08:34:38 DEBUG: Storing value log head: {Fid:9 Len:31 Offset:1610645}
badger 2019/10/21 08:34:38 DEBUG: Flushing memtable, mt.size=34745 size of flushChan: 0
badger 2019/10/21 08:34:38 DEBUG: Storing value log head: {Fid:10 Len:31 Offset:805317}
badger 2019/10/21 08:34:38 DEBUG: Flushing memtable, mt.size=34945 size of flushChan: 0
badger 2019/10/21 08:34:38 INFO: Got compaction priority: {level:0 score:1.2 dropPrefix:[]}
badger 2019/10/21 08:34:38 INFO: Running for level: 0
badger 2019/10/21 08:34:38 DEBUG: LOG Compact. Added 3603 keys. Skipped 0 keys. Iteration took: 40.49591ms
badger 2019/10/21 08:34:38 DEBUG: LOG Compact. Added 3367 keys. Skipped 0 keys. Iteration took: 29.324284ms
badger 2019/10/21 08:34:38 DEBUG: Discard stats: map[]
badger 2019/10/21 08:34:38 INFO: LOG Compact 0->1, del 7 tables, add 2 tables, took 84.556191ms
badger 2019/10/21 08:34:38 INFO: Compaction for level: 0 DONE
badger 2019/10/21 08:34:39 DEBUG: Flushing memtable, mt.size=34643 size of flushChan: 0
badger 2019/10/21 08:34:39 DEBUG: Storing value log head: {Fid:10 Len:31 Offset:5637285}
badger 2019/10/21 08:34:39 DEBUG: Flushing memtable, mt.size=34795 size of flushChan: 0
badger 2019/10/21 08:34:39 DEBUG: Storing value log head: {Fid:11 Len:31 Offset:4831957}
badger 2019/10/21 08:34:39 DEBUG: Flushing memtable, mt.size=34803 size of flushChan: 0
badger 2019/10/21 08:34:39 DEBUG: Storing value log head: {Fid:12 Len:31 Offset:4026629}
badger 2019/10/21 08:34:40 DEBUG: Storing value log head: {Fid:13 Len:31 Offset:3221301}
badger 2019/10/21 08:34:40 DEBUG: Flushing memtable, mt.size=34793 size of flushChan: 0
badger 2019/10/21 08:34:40 DEBUG: Storing value log head: {Fid:14 Len:31 Offset:2415973}
badger 2019/10/21 08:34:40 DEBUG: Flushing memtable, mt.size=34805 size of flushChan: 0
badger 2019/10/21 08:34:40 INFO: Got compaction priority: {level:0 score:1 dropPrefix:[]}
badger 2019/10/21 08:34:40 INFO: Running for level: 0
badger 2019/10/21 08:34:40 DEBUG: LOG Compact. Added 3603 keys. Skipped 0 keys. Iteration took: 44.412168ms
badger 2019/10/21 08:34:40 DEBUG: LOG Compact. Added 3607 keys. Skipped 0 keys. Iteration took: 39.613965ms
badger 2019/10/21 08:34:40 DEBUG: LOG Compact. Added 2705 keys. Skipped 0 keys. Iteration took: 28.006555ms
badger 2019/10/21 08:34:40 DEBUG: Discard stats: map[]
badger 2019/10/21 08:34:40 INFO: LOG Compact 0->1, del 7 tables, add 3 tables, took 127.699034ms
badger 2019/10/21 08:34:40 INFO: Compaction for level: 0 DONE
badger 2019/10/21 08:34:43 INFO: DropAll called. Blocking writes...
badger 2019/10/21 08:34:43 INFO: Writes flushed. Stopping compactions now...
badger 2019/10/21 08:34:43 INFO: Deleted 3 SSTables. Now deleting value logs...
badger 2019/10/21 08:34:43 INFO: Value logs deleted. Creating value log file: 0
badger 2019/10/21 08:34:43 INFO: Deleted 15 value log files. DropAll done.
==================
WARNING: DATA RACE
Write at 0x00c174a2fc50 by goroutine 40:
github.com/dgraph-io/ristretto.(*Cache).collectMetrics()
/home/pawan0201/go/src/github.com/dgraph-io/ristretto/cache.go:287 +0x55
github.com/dgraph-io/ristretto.(*Cache).Clear()
/home/pawan0201/go/src/github.com/dgraph-io/ristretto/cache.go:241 +0x14e
github.com/dgraph-io/badger.(*DB).dropAll()
/home/pawan0201/go/src/github.com/dgraph-io/badger/db.go:1472 +0x556
github.com/dgraph-io/badger.(*DB).DropAll()
/home/pawan0201/go/src/github.com/dgraph-io/badger/db.go:1428 +0x44
github.com/dgraph-io/badger.TestDropAllWithPendingTxn.func4()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:236 +0x6f
Previous read at 0x00c174a2fc50 by goroutine 253:
github.com/dgraph-io/ristretto.(*Cache).Get()
/home/pawan0201/go/src/github.com/dgraph-io/ristretto/cache.go:167 +0x103
github.com/dgraph-io/badger/table.(*Table).block()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/table.go:364 +0x164c
github.com/dgraph-io/badger/table.(*Iterator).next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:296 +0x216
github.com/dgraph-io/badger/table.(*Iterator).next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:311 +0x6a6
github.com/dgraph-io/badger/table.(*Iterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:367 +0x70
github.com/dgraph-io/badger/table.(*ConcatIterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/table/iterator.go:485 +0x59
github.com/dgraph-io/badger/y.(*MergeIterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/y/iterator.go:216 +0xca
github.com/dgraph-io/badger.(*Iterator).parseItem()
/home/pawan0201/go/src/github.com/dgraph-io/badger/iterator.go:647 +0x98e
github.com/dgraph-io/badger.(*Iterator).Next()
/home/pawan0201/go/src/github.com/dgraph-io/badger/iterator.go:560 +0x20c
github.com/dgraph-io/badger.TestDropAllWithPendingTxn.func3()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:206 +0x157
Goroutine 40 (running) created at:
github.com/dgraph-io/badger.TestDropAllWithPendingTxn()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:233 +0x428
testing.tRunner()
/usr/local/go/src/testing/testing.go:865 +0x163
Goroutine 253 (running) created at:
github.com/dgraph-io/badger.TestDropAllWithPendingTxn()
/home/pawan0201/go/src/github.com/dgraph-io/badger/managed_db_test.go:198 +0x3cf
testing.tRunner()
/usr/local/go/src/testing/testing.go:865 +0x163
==================
badger 2019/10/21 08:34:43 INFO: Resuming writes
badger 2019/10/21 08:34:44 INFO: Got compaction priority: {level:0 score:1.73 dropPrefix:[]}
--- FAIL: TestDropAllWithPendingTxn (9.40s)
managed_db_test.go:216: Got number of keys: 10000
managed_db_test.go:216: Got number of keys: 10000
managed_db_test.go:220: Got error during key lookup: Key not found
testing.go:809: race detected during execution of test
I was reading through the code to see how you implemented "ristretto" and I came up to this line: https://github.com/dgraph-io/ristretto/blob/master/cache.go#L201.
c.onEvict(item.key, item.val, item.cost)
I might be wrong, because I don't yet fully understand the code involved, but shouldn't it be:
c.onEvict(victim.key, victim.val, victim.cost)
package main
import (
"fmt"
"math/rand"
"strconv"
"github.com/dgraph-io/ristretto"
)
const (
maxCnt = 1e7
maxLoop = 1e8
)
func main() {
cahchePool, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1 * 1e7,
MaxCost: 200 * (1 << 20), // allocate 200M, but running it need 2GB, why?(when i run this program, it kill by OOM)
BufferItems: 64,
Metrics: true,
})
if err != nil {
panic(err)
}
key := "def48b5abb6388d3cbbb18e550f47b4cYB6eRI3cK1VN2qCEHp8kvuMuH20dq10cYDcG2e"
for i := 0; i < maxLoop; i++ {
suffix := strconv.Itoa(rand.Intn(maxCnt))
cahchePool.Set(key+suffix, suffix, int64(len(key+suffix)))
if (i % maxCnt) == 0 {
fmt.Println(cahchePool.Metrics)
}
}
cnt := 0
for i := 0; i < maxCnt; i++ {
if _, found := cahchePool.Get(key + strconv.Itoa(i)); found {
cnt++
}
}
fmt.Println("cnt:", cnt, "\n", cahchePool.Metrics)
}
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 to Set
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 running runtime.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
):
time: 1s alloc: 1033.72 MiB hits: 43 misses: 2614
time: 2s alloc: 1035.74 MiB hits: 71 misses: 3230
time: 3s alloc: 1038.76 MiB hits: 98 misses: 3210
time: 4s alloc: 1048.77 MiB hits: 87 misses: 3236
time: 5s alloc: 1065.79 MiB hits: 109 misses: 3229
...
time: 45s alloc: 5893.52 MiB hits: 642 misses: 2773
time: 46s alloc: 6049.54 MiB hits: 704 misses: 2689
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.
It sounded to me that if the value calculated by new entries is higher than the lowest entry in a random sample, the new entry gets admitted. Otherwise not.
What I wonder is if it the value is calculated based off on the frequency from LFU. If so, won't the number of discarded items go up over time? Imagine if there are only reads for a minute or 10minutes, the frequency of most items increase and they gain a higher value than any new incoming entries.
I'm not confident in how the value is calculated, so I apologies if this is way off. Also curious if you have metrics for discarded (new items that was never added) metrics?
Hi!
Do you have any plans to add operations having read-your-writes consistency at any point in the future?
Thanks!
Currently it is stated that
// For example, if you expect your cache to hold 1,000,000 items when full,
// NumCounters should be 10,000,000 (10x). Each counter takes up 4 bits, so
// keeping 10,000,000 counters would require 5MB of memory.
over here. However this behaviour isnt correct when utlising the cache. When utilising the cache with the following config
cache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 10000, // number of keys to track frequency of (10000).
MaxCost: 1000, // maximum cost of cache (1000 objects).
BufferItems: 64, // number of keys per Get buffer.
Cost: func(value interface{})int64{
return 1
},
})
After the cache is filled with the first 1000 objects and is at its maximum capacity, any new items cannot be added into it. All my following Set
fail. None of the old items currently in the cache are evicted to make way for new ones. This is however resolved if the NumCounters
is set the same as the maximum size of the cache(1000 objects).
cache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1000, // number of keys to track frequency of (1000).
MaxCost: 1000, // maximum cost of cache (1000 objects).
BufferItems: 64, // number of keys per Get buffer.
Cost: func(value interface{})int64{
return 1
},
})
The above allowed the cache to work as normal, with majority of my Sets succeeding and my hit ratio staying at 0.94 instead of rapidly dropping. Is there anything else that needs to be done to get the cache working with 10x the number of keys or is this a gap in the documentation ?
Please add TTL and loadData function into config so it can auto reload an item if it is expired.
TestRingLossy
fails on master with drain error
Run
while go clean -testcache && go test -v -race -run='TestRingLossy' -timeout=1m; do :; done
to reproduce the failure
=== RUN TestRingLossy
--- FAIL: TestRingLossy (0.01s)
ring_test.go:57: drain error
FAIL
exit status 1
FAIL github.com/dgraph-io/ristretto 0.012s
Lines 41 to 59 in ae326c3
# github.com/dgraph-io/ristretto/bench
src/github.com/dgraph-io/ristretto/bench/bench.go:325:10: undefined: ristretto.PolicyLog
Found by gopkgs.io
An attacker can prevent new items from being accepted by the policy by artificially raising the frequency of the victim.
Hello there ๐ thanks for developing ristretto!
This is more of a general question rather than a issue: is there a way of creating a persistent snapshot for a running cache?
I've been trying with gob.NewEncoder(buffer).Encode(cache)
but I have an error gob: type ristretto.Metrics has no exported fields
which is (apparently) the intended behaviour of gob package (reference).
Any ideas if this is possible?
Thanks
assume Key A, Key B
A(keyHash) A(Confilct) equal B(keyHash) B(Confilct)
In this case, do I have to double check the value every time?
Thanks
Please provide another member onEvict
in the Config
struct which would be a function that would be called on eviction if it is present. This is needed so that users would be able to do some more, extra house-keeping from their side.
The current and only release is old. Go get will fetch this old version by default, without the TTL functionality. Go get will thus fetch a version that does not comply with the README.
Can you publish a release that matches the documentation?
Thank you.
As brought up in the performance Gopher channel, since we're using interface{}
for keys, this call to keyToHash
is probably letting the interface escape to heap.
Solution: switch over to string
or []byte
for public API calls.
In the readme, the godoc link does not point to the correct address:
[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/golang-standards/project-layout)
Current address - http://godoc.org/github.com/golang-standards/project-layout
The ci pipeline introduced in #48 run tests only on go 1.12
Tests should run on latest stable version (1.13) also.
This is more a "design question" than an actual "issue", however given its implications I think it is an important question which impacts either the design or the usage constraints.
Given that the KeyToHash
(1) function supports string
(and []byte
), and it returns uint64
, it is possible that there are hash collisions.
However the cache.Get
(2) method doesn't check if the found entry (if any) actually has the given key. (In fact the store
doesn't even support storing the key.)
(1) https://github.com/dgraph-io/ristretto/blob/master/z/z.go#L20
(2) https://github.com/dgraph-io/ristretto/blob/master/cache.go#L83
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.