-
Notifications
You must be signed in to change notification settings - Fork 498
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
support simple expiring cache #75
base: main
Are you sure you want to change the base?
support simple expiring cache #75
Conversation
…make XXXCache just thread-safe wrapper over XXXLRU, avoid NoLock ugliness
I'm not the right person to review this as I don't know what new API we might want to commit to, but a few comments:
|
|
Hi there,
Yes. Adding variadic arguments for a specific purpose like this means that you cannot add more arguments after the fact, unless you do some kind of type switching, and that has various brittle edges. If it was using an options pattern instead you would simply pass a value to receive expired entries as an option, but could support other options as well in a more future-proof way.
I understand the purpose of the variadic arguments, but I disagree with the approach. Lazy expiration can lead to unpredictable performance/memory utilization as a call to add a value can now result in a lot more work -- work which could have been performed in advance via using a timer with a priority queue, keeping performance more consistent and allowing memory to be freed up when it's actually no longer needed. Timers run in their own goroutine, which is why using a channel to receive evicted values would make sense, although it's not the only approach. Additionally, you're making an assumption that a caller to add a value is the same goroutine as the one that might want to handle an expired value. Considering the caches are thread-safe, that's not a good assumption. In practice, we're using this cache with software that can have tens of thousands of goroutines accessing the cache. It's just as likely that the caller to Add would end up sending the evicted entries to some other goroutine for processing anyways. Also, maybe I missed this as I really only looked at the proposed API changes, so there might be an option here that I missed, but there seems to be no way to turn off this behavior. If a user wants an expiring cache but isn't interested in receiving evicted entries, the tradeoff for lazy expiration is even more problematic. If you have callers register (or request) a channel and close it when they no longer want to receive evictions, not only could you support sending evictions to multiple callers, but you could clean them up when the channel closes, such that if there are no active callers waiting on evictions you simply don't bother with any logic to send them around. |
|
Reconsider this, may be we can avoid varadic arguments of Add() (and all these troubles) by using onEvicted callback similar to what simple LRU does. will reconsider the code during night. Thanks. |
Please review changes to support simple expiring cache:
reuse existing 2q, arc and simple lru for lru eviction, add expiring logic as a wrapper; need changes in current api Add() method to return evicted key/val.
since there are 3000+ packages importing golang-lru, there should be no changes required for user client code. avoid api changes that require user code change; the changes to api Add() method (single change to LRUCache interface) is introduced as optional argument:
Add(key, val interface{}, evictedKeyVal ...*interface{}) (evicted bool)
this is "pull" style api: user/caller code specify receiving arguments to receive results from callee, similar to Reader{ Read(recvBuf []byte)int }:
Add(k,v) //current code, no change
Add(k,v,&evictedKey) //interested in evicted key
Add(k,v,&evictedKey,&evictedVal) //interested in both evicted key and val
following same pattern of simple LRU, separate 2q/arc Cache from LRU, so that XXXCache is just a thread safe wrapper of XXXLRU. this avoids the double locking when ExpiringCache wraps 2Q or ARC.
there are 2 expiring policies similar to Guava's CacheBuilder (https://guava.dev/releases/19.0/api/docs/com/google/common/cache/CacheBuilder.html): ExpireAfterWrite and ExpireAfterAccess
the default cleanup of expired entries is lazy, only when space is needed for new entries (inside Add) or accurate keys (inside Keys) and size (inside Len) are needed; can add background cleanup by a goroutine periodically calling RemoveAllExpired()