Open blackarctic opened 2 years ago
Happy to make a PR for this, but want to ensure interest first.
From a user's perspective of memoizee it makes sense. Would be useful for some use-cases like memoizing connections and other resources that don't technically change but are expensive to maintain.
@blackarctic great thanks for this proposal. Still, wouldn't combining maxAge
with max
address this use case reliably?
Having that setup only max of last accessed items will be kept but for no longer than maxAge.
Also @blackarctic can you outline what is the specific use case?
I don’t believe the existing combination accomplishes this goal. Combing maxAge
and max
says “I want to keep a max of x items in the cache for no longer than y milliseconds after first access”. Using just maxAgeSinceAccess
says “I want to keep any number of items in the cache as long as they have been accessed in the last y milliseconds”. Combining maxAgeSinceAccess
and max
would give us “I want to keep a max of x items in the cache for no longer than y milliseconds after last access”.
As mentioned in the description, this use case of keeping items in the cache since last access (as opposed to first access) makes much more sense to me than maxAge
, since we likely don't want to expire a memoized value that is actively being used. preFetch
also doesn't make much sense as we likely have no need to recompute the value. We simply want to remove inactive items from the cache and keep active items in the cache.
this use case of keeping items in the cache since last access (as opposed to first access)
This explains what you want, but does not describe the use case you have (what are you caching? Why last access dictates the freshness of a cached value, and not the retrieval timestamp ?)
Note it's a very first request like that (in near 9 years of this package being around). Either we're addressing a very rare use case, or this use case can be addressed in a better way.
Sure. Let’s say you want to memoize the conversion from any number of objects from shape A to shape B. You want to keep the object references the same so preFetch isn’t going to work because that will create new object references. You also don’t know how many you will have so you can’t use max. You can use maxAge but after x amount of time, the object references will be dropped and you will get bugs because you need the object references to persist. What we actually want is to persist the object references as long as they are actively being accessed.
I apologize, it’s hard for me to come up with specific examples because I feel almost every memoization example that uses maxAge is better served with maxAgeSinceAccess. Simple reason is don’t remove cached items that are actively being used.
I often memoize observables which keep subscribed to some resource, such as redis events or to a websocket. Such resource never changes so the goal of memoization is to just keep it active for as long as it's needed.
maxAge
is best for cases when things generally change over time such as:
maxAgeSinceAccess
would not make any sense. These numbers need to be expired to generate a new value.But if you're memoizing:
maxAgeSinceAccess
will always be better.What we actually want is to persist the object references as long as they are actively being accessed.
@blackarctic Why in this specific case, it's ok to assume that if object wasn't accessed for at least X period, it's safe to remove from cache, and it's not safe to remove if it's the least recently accessed object from all cached objects when count of them reached Y count?
To me it looks a great max
(LRU) use case, still, your description feels purely theoretical, Can you describe a real-world case you're dealing with?
I often memoize observables which keep subscribed to some resource, such as redis events or to a websocket. Such resource never changes so the goal of memoization is to just keep it active for as long as it's needed.
Thanks @Rush for your input. Wouldn't relying on weak
option work best here? In that case, it'll be memoized as long as it's used by the consumers.
Otherwise is this a real need you have currently, or more a nice to have thing, that you assume you may benefit in some cases?
@medikoo Sorry for the late reply. Looking back into this.
This situation I had in mind is a frontend render cycle, where max
is not helpful because we can't limit by an arbitrary number since we can't know this arbitrary number beforehand. Essentially, in the frontend render cycle, we can actually safely assume that if our maxAgeSinceAccess
is longer than it takes our render cycle to complete (including promises to resolve) then we can safely remove those references and all in the render cycle will have the same object. In this case, a maxAgeSinceAccess
of a minute or two should be more than enough. With maxAgeSinceAccess
pushing back the expiration on each access, it is impossible to have the memoized function return multiple references during the render cycle.
Render Cycle (using maxAgeSinceAccess)
-> const a = getMemoizedValue() - 3ms --> Pushes back the expiration
-> someOtherFunction() - 4ms
-> const b = getMemoizedValue() - 3ms --> getMemoizedValue()'s cache has not expired. a is **equal** to b
Render Cycle (using maxAge)
-> const a = getMemoizedValue() - 3ms --> Does not push back the expiration
-> someOtherFunction() - 4ms
-> const b = getMemoizedValue() - 3ms --> getMemoizedValue()'s cache has expired. a is **not equal** to b
@blackarctic thanks for the explanations, still it still looks blurry to me.
Can you explain the use case without referring to the current memoizee API and without focusing on an eventual solution?
What exactly is the "Render Cycle" ? What is the "arbitrary number" you refer to?
(Also in memoizee maxAge
as far as I remember is counted since function result resolved and not since the function was invoked)
This would function similarly to
maxAge
but the cache would expire x milliseconds from last access as opposed to x milliseconds from the first access.For example:
This makes much more sense to me than
maxAge
, since we likely don't want to expire a memoized value that is actively being used.preFetch
also doesn't make much sense as we likely have no need to recompute the value. We simply want to remove inactive items from the cache.