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

embedded cache refactoring #10989

Merged
merged 5 commits into from
Nov 1, 2023

Conversation

vlad-diachenko
Copy link
Contributor

What this PR does / why we need it:
refactored embedded cache to allow using it for any types, not only for string -> []byte.
Also, the new implementation allows passing the callback that will be called for the removed entry.

…tring to []byte. Also, the new implementation allows passing the callback that will be called for the removed entry.

Signed-off-by: Vladyslav Diachenko <[email protected]>
@vlad-diachenko vlad-diachenko requested a review from a team as a code owner October 21, 2023 21:27
Copy link
Contributor

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work @vlad-diachenko! Left a couple thoughts

c.entries = make(map[string]*list.Element)
if c.onEntryRemoved != nil {
for _, entry := range c.entries {
castedEntry := entry.Value.(*cacheEntry[K, V])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check that this cast succeeds, otherwise the next line will panic

Copy link
Contributor

@dannykopping dannykopping Oct 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hhmm, do we really want to call this function for every item once Stop() is called? That could be quite slow. Is it necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if the value must be properly closed, we need to call on onEntryRemoved callback. In our case, we will use this function to remove the data from the disk.
It's not always necessary if you run your pod against ephemeral storage, but if you run Loki on our local machine, then we need to clean up the data from the disk.
If it becomes unnecessary for some cases in the future, we can add an additional toggle to disable calling this function during the stop.
wdyt?

Copy link
Contributor

@dannykopping dannykopping Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you storing on disk?

One way you could solve this efficiently is to generate a unique ID, and store all your objects on disk under a directory with that ID. When the cache is Stop()-ed, you can delete the directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you storing on disk?

we will store extracted bloom-blocks on the disk. and we need to be able to delete the directory with this block if entry is evicted from the cache.

One way you could solve this efficiently is to generate a unique ID, and store all your objects on disk under a directory with that ID. When the cache is Stop()-ed, you can delete the directory.

yes, we could do it but it will require having different callbacks, one for onEntryRemoved and one more for onStop... anyway, for now, such cache with onEntryRemoved callback will be used only for bloom-blocks so it does not affect rest of the implementation because the callback will be nil there...
Also, even if it will be a thousand of the directories to delete, it should not take a lot of time.
wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use memcached from the beginning? memcached is an LRU...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what will be better to store the blocks on the disk or download them from the Memcache every time. we expect these blocks to be big enough, so we want to store them on a local disk because it might be just faster...
Let's say if the query touches 100GB of data, then it might be necessary to download about 1 GB of blocks, it might take some time ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, we can jump on a call tomorrow or later today to discuss.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, let's discuss it tomorrow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed execution onEntryRemoved callback during the cache stop a6f7a43

pkg/storage/chunk/cache/embeddedcache.go Show resolved Hide resolved
c.lock.Lock()
defer c.lock.Unlock()

for k, v := range c.entries {
entry := v.Value.(*cacheEntry)
entry := v.Value.(*cacheEntry[K, V])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though the cache value should not be of a different type, we should still check the cast result:

Suggested change
entry := v.Value.(*cacheEntry[K, V])
entry, ok := v.Value.(*cacheEntry[K, V])
if ok && time.Since(entry.updated) > ttl {
c.remove(k, v, expiredReason)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asked question below ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the type is not expected, a panic seems appropriate

@vlad-diachenko
Copy link
Contributor Author

hey @chaudum , @dannykopping
. I have a question about the need to assert the type of entry from the cache.
The underlying cache is a private field, so, nobody can put the entries from outside and currently, it’s only possible to do using Store function that forces the entries to have types [K, V] .
I am not sure that we need to assert if the type is casted properly because it means that somebody produced the bug in the cache itself, and this bug also is not caught by the test…. and I do not know if the check of the type will help us here…. maybe it’s better to panic because in this case, I do not think that application can operate
wdyt?

@vlad-diachenko
Copy link
Contributor Author

@chaudum @dannykopping what do you think about the PR? can we merge it?

@vlad-diachenko vlad-diachenko enabled auto-merge (squash) October 25, 2023 07:16
@vlad-diachenko vlad-diachenko enabled auto-merge (squash) November 1, 2023 14:53
@vlad-diachenko vlad-diachenko merged commit 45b10ef into main Nov 1, 2023
3 checks passed
@vlad-diachenko vlad-diachenko deleted the vlad.diachenko/embedded-cache-refactoring branch November 1, 2023 15:42
rhnasc pushed a commit to inloco/loki that referenced this pull request Apr 12, 2024
**What this PR does / why we need it**:
refactored embedded cache to allow using it for any types, not only for
`string -> []byte`.
Also, the new implementation allows passing the callback that will be
called for the removed entry.

---------

Signed-off-by: Vladyslav Diachenko <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants