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 support for cache expire/refresh on condition #536

Closed
StephenFlavin opened this issue Apr 16, 2021 · 5 comments
Closed

Add support for cache expire/refresh on condition #536

StephenFlavin opened this issue Apr 16, 2021 · 5 comments

Comments

@StephenFlavin
Copy link

StephenFlavin commented Apr 16, 2021

It would be useful to be able to have a custom condition that causes expiry of a cache entry.

For my use case I have a app which streams data from kafka and maintains an in memory buffer which is flushed (written to a zip file) based on two criteria time and size.

I'm currently handling this logic manually and was looking to tidy up the code by using a LoadingCache making use of the RemovalListener to write the files however its currently not possible to expire entries for custom conditions.

e.g.

LoadingCache<Key, List<Value>> cache = Caffeine.newBuilder()
    .maximumSize(...)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .expireOnCondition((key, value) -> value.size() >= 10000)
    .removalListener(new BatchWriter())
    .build(...); 
@ben-manes
Copy link
Owner

ben-manes commented Apr 16, 2021

How well could expireAfter(expiry) fit your use-case? That returns a duration, which may be zero to indicate immediate expiration. For example,

LoadingCache<Key, List<Value>> cache = Caffeine.newBuilder()
    .expireAfter(new Expiry<Key, List<Value>>() {
      public long expireAfterCreate(Key key, List<Value> values, long currentTime) {
        return TimeUnit.MINUTES.toNanos(5);
      }
      public long expireAfterUpdate(Key key, List<Value> values, 
          long currentTime, long currentDuration) {
        return (values.size() >= 10_000) ? 0L : TimeUnit.MINUTES.toNanos(5);
      }
      public long expireAfterRead(Key key, List<Value> values,
          long currentTime, long currentDuration) {
        return currentDuration;
      }
    })
    .build(key -> new ArrayList<Value>());

The expiration time is calculated during a cache operation, so modifying the value has to inform the cache for reevaluation. This can be done atomically using a compute, e.g.

cache.asMap().compute(key, (k, values) -> {
  if (values == null) {
    values = new ArrayList<>();
  }
  values.add(...);
  return value;
});

I am not sure if you want the 5m TTL reset when an update occurs. I suspect you want it to be a 5m since creation or 10,000 entries. In that case an expireAfter is required, where it would return currentDuration on the update case if within the size threshold.

@StephenFlavin
Copy link
Author

Ah awesome, I didn't think this would be possible without code changes to the library, this should do the trick for my refractor however I do think having some syntactic sugar of expireOnCondition in the builder would be nice.

Big thanks for the suggestion 🙂

@ben-manes
Copy link
Owner

In that case it is composing multiple policies within the builder, which gets messy. I'd prefer to have a builder on Expiry, e.g. how Comparator.comparing(...).thenComparing(...) can succinctly define the strategy. Something like that would make it easier and clearer to define the conditions in a composable fashion.

@ben-manes
Copy link
Owner

Closing for now, but open to API improvements if someone wants to sketch out improvements to Expiry.

@StephenFlavin
Copy link
Author

Thanks again 🙏

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

No branches or pull requests

2 participants