Closed lathspell closed 4 years ago
You can set a custom expiration policy, e.g. https://github.com/ben-manes/caffeine/wiki/Eviction#time-based
If set, some additional functionality is provided through cache.policy().
Does that solve your problems?
I don't think so. The policy is for the whole cache object whereas I was looking for a maxAge parameter per access.
I'd like to implement a REST endpoint that servers usually cached data but if the clients want, they can set a "Cache-Control: max-age=42" header which I would parse and then only serve from cache if the cached object is there no longer than 42 seconds. Else I would refresh the cache.
Pseudocode:
public class LoadingCache {
public V getWithMaxAge(K key, long maxAge) {
val cached = get(key)
if (cached.insertedAt.isAfter(LocalDateTime.now().minusSeconds(maxAge))) {
return cached.data
} else {
refresh(key)
return get(key).data
}
}
}
.... client ...
val cache = Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build {...}
val maybeOld = cache.get(key) // at most 30 min cached (default)
val fresher = cache.getWithMaxAge(key, 300) // at most 5 min cached
val recent = cache.getWithMaxAge(key, 0) // not cached at all
The policy can be per-entry if using expireAfter(Expiry)
, but for calculating the new duration after an operation. In that case you might read, check if it was expired, if not then calculate a new duration, and return the value. It makes sense when the external resource dictates the lifetime to abide to, rather than each caller.
In your case I guess you can do this similar to your example logic,
val value = cache.getIfPresent(key)
if (value == null) {
return cache.get(key)
} else if (value.hasExpired(maxAge)) {
cache.asMap().remove(key, value)
return cache.get(key)
} else {
return value
}
Note that max-age
is a response header, so that fits within the expireAfter(expiry)
model we support.
You might consider to set a different header, like min-fresh
or max-stale
, if you want client-side control over the server's caching behavior. However that can be problematic if a client is always invalidating the cache by a hostile setting, causing a cache stampede. Usually you want the resource owner to dictate, so the Expiry
callback works well for those cases.
According to the MDN page you linked "max-age" can be used in both, the request or the response!
"min-fresh"/"max-stale" have a slightly different meaning. I do not know if my cache entry is "fresh" i.e. unmodified until I retrieve it from the original source to compare it but it's that retrieval I want to minimize using a cache.
I use something like the workaround that you suggested. Just thought that it would be a nice addition if you don't know what new features you could add :)
Oh you're right, it is request and response 😄
Do you think there is anything smarter we could do within the cache? I think we could only perform that workaround ourselves, which means it is not super helpful. A expiration-only method would have to live under cache.policy().expireVariably()
, which resolves through an Optional
and loses context of being a loading cache. I think doing this as an extension in user code, like you showed, is the cleanest.
It would be helpful if the API would at least allow the user to query the insertion-date of an object.
In my workaround I currently I do not store the object itself but a wrapper class that contains just a date and the actual data. But for the expire polices you must alreay have that date somewhere in the internal data structues.
We do, but it's not super friendly.
Optional<Duration> duration = cache.policy().expireVariably()
.flatMap(policy -> policy.getExpiresAfter(key));
Closing because I think there is nothing more intelligent that we can offer, but please reopen if you have ideas or requests! 😄
I like to provide a Cache that works like the HTTP Header "Cache-Control: max-age=30" which would use cached results provided that they are at most 30s old and creates a new result.
So far I couldn't find something like
cache.get(key, maxAge)
in the API.An alternative would be a getter that returns the cache entry with not only the object but also its meta information so that we could write