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

Add expirable LRU implementation #68

Closed
wants to merge 1 commit into from

Conversation

paskal
Copy link
Contributor

@paskal paskal commented May 9, 2020

My attempt at expirable cache implementation, similar to what was done in #41. I've tried to make new cache as close as possible to the existing one in terms of code style and behavior.

The only difference which I did leave in place is that size=0 on this type of cache means unlimited, which makes sense with the expirable cache.

Original work was done in lcw but it turned out that I don't need it there and I thought it would be nice to add my changes to upstream (this repo).

@paskal
Copy link
Contributor Author

paskal commented May 12, 2020

@jefferai would you be so kind as to look into it and\or include other reviewers who might review it as well?

@paskal
Copy link
Contributor Author

paskal commented May 20, 2020

Gentle ping for @jefferai

@paskal
Copy link
Contributor Author

paskal commented Jun 14, 2020

Another gentle ping for @jefferai.

@jefferai
Copy link
Member

@paskal do be honest I don't think I'm the right person to review this. I don't have a need for it so I also don't know what is needed in such an implementation that we would feel comfortable committing to in terms of API.

@paskal
Copy link
Contributor Author

paskal commented Jun 20, 2020

I'll close this one as I don't want to maintain the fork on my own and I do understand your concern regarding committing for new API support. I hope if someone will need exactly this thing, he or she will retrieve this code and will be able to use it.

@paskal paskal closed this Jun 20, 2020
}

// noEvictionTTL - very long ttl to prevent eviction
const noEvictionTTL = time.Hour * 24 * 365 * 10
Copy link
Member

Choose a reason for hiding this comment

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

If a TTL of 0 is defined as turning TTL off, why do you need this at all? Couldn't the check below simply be if res.ttl != 0 && ....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Current expiration logic is very straightforward: ent.Value.(*expirableEntry).expiresAt = now.Add(c.ttl)

Considering zero a special case after the cache creation would complicate the code I believe. I would rather have this extra complexity on cache instance setup rather than inside it's logic. I haven't thought of any more elegant alternative, but it definetly doesn't mean it's not possible to find it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jefferai would you be so kind as to look into discussions in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jefferai gentle ping


sync.Mutex
items map[interface{}]*list.Element
evictList *list.List
Copy link
Member

Choose a reason for hiding this comment

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

Rather than using a list where you have to keep scanning through, I think you'd be better off using a priority queue. See https://github.com/hashicorp/vault/tree/master/sdk/queue and the Golang doc it links to. That way you simply add items to the queue where the sorting is by expiration time and only have to keep track of the first item in the queue in terms of expiration. This would also let you get rid of Close; a simple one-off timer would be the only goroutine you need to keep checking the first element.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jefferai, thanks for your thoughts! I understand the benefits of using a heap, but I don't understand how it would change anything.

removeElement is constant complexity now, it stays the same.

DeleteExpired complexity stays the same, as to delete all expired elements we either need to traverse whole dataset (O(N)) or go trough sorted list until we find first non-expired element (O(N*logN)) or check top element of the heap until we find a non-expiring element, which I believe would be exact same complexity as sorted list, as single heap rebalance would be O(logN) multiplied by N elements we want to delete (e.g. whole dataset during lifetime of the program).

If we want to have deleteExpired call in a goroutine, we need ability to call Close() to kill it. If I understand you correctly and you want me to not to call deleteExpired by timer, I don't understand which alternative you propose: even if we set one-time timer on new entry which will wait for it to expire and then clean it up, or clean up everything expired so far, we still want the ability to kill it explicitly.

Would you be so kind as to elaborate on what you want to see changed in that code behavior and what's the benefit of using heap for it?

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 think it's better to split this discussion into two separate threads: is there any way to get rid of the Close method (which is only possible if we won't have any goroutines spawned as I think) and is there a way to make this code faster.

@jefferai jefferai reopened this Jun 21, 2020
@jefferai
Copy link
Member

@paskal Left some comments. I think it could benefit from a bit of restructuring that would clean up the API and be more efficient and clear.

@paskal paskal requested a review from jefferai August 11, 2020 20:43
@paskal
Copy link
Contributor Author

paskal commented Oct 12, 2020

@jefferai could you please look into it once more?

@paskal
Copy link
Contributor Author

paskal commented Dec 7, 2020

I don't think it will be merged. I've created a fork with expirable cache added, released as v0.6.0: github.com/paskal/golang-lru@v0.6.0. Closing this PR.

@paskal paskal closed this Dec 7, 2020
@paskal paskal deleted the expirable_LRU branch December 7, 2020 19:27
@Dentrax
Copy link

Dentrax commented Nov 12, 2022

This is a great job @paskal! Kudos. Almost 2+ years passed. I was about to implement my own expiable LRU using golang-lru until saw your comment.

Pinging @armon @jefferai for awareness. Looking forward to seeing this implementation in hashicorp/golang-lru.

@jefferai
Copy link
Member

Sure, I'd love to have this in golang-lru,

@paskal
Copy link
Contributor Author

paskal commented Nov 14, 2022

Created #113 based on the current master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants