7

I'm trying to further my understanding of the caffeine cache. I was wondering if there is a way to specify a timeout for an entry that's populated in the cache, but have no time based expiry for the rest of the records.

Essentially I would like to have the following interface:

put(key, value, timeToExpiry)// enter a key and value with a specified timeToExpiry

put(key, value) // enter a key value with no timeToExpiry

I can write the interfaces and plumbing, but I'd like to understand how I can configure caffeine for both of the above requirements. I'm also open to have two separate instances of the caffeine cache.

tsar2512
  • 2,826
  • 3
  • 33
  • 61

2 Answers2

9

This can be done by using a custom expiration policy and leverage an unreachable duration. The maximum duration is Long.MAX_VALUE, which is 292 years in nanoseconds. Assuming your record holds when (and if) it expires then you might configure the cache as,

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfter(new Expiry<Key, Graph>() {
      public long expireAfterCreate(Key key, Graph graph, long currentTime) {
        if (graph.getExpiresOn() == null) {
          return Long.MAX_VALUE;
        }
        long seconds = graph.getExpiresOn()
            .minus(System.currentTimeMillis(), MILLIS)
            .toEpochSecond();
        return TimeUnit.SECONDS.toNanos(seconds);
      }
      public long expireAfterUpdate(Key key, Graph graph, 
          long currentTime, long currentDuration) {
        return currentDuration;
      }
      public long expireAfterRead(Key key, Graph graph,
          long currentTime, long currentDuration) {
        return currentDuration;
      }
    })
    .build(key -> createExpensiveGraph(key));
Ben Manes
  • 9,178
  • 3
  • 35
  • 39
  • 2
    thanks for the prompt response, this helps create the initial graph with a 292 year timeout, but would it be possible to also do something along the lines of `put(key,value, timeToExpiry)`, where you specify the time to expiry when you are putting the value in the cache? – tsar2512 May 11 '20 at 03:12
  • 1
    Yes, see `cache.policy()` for these types of ad hoc methods. You can set the default expiry to be infinite and use `VarExpiration#put(key, value, duration)` for an explicit setting. The callback approach is preferred over racy get-load-put because it lets the cache handle [cache stampedes](https://doordash.engineering/2018/08/03/avoiding-cache-stampede-at-doordash/) for you. – Ben Manes May 11 '20 at 03:15
  • 1
    Hi, is it possible to get expiry for an entry? and override if need – psyskeptic Oct 25 '21 at 11:39
  • @psyskeptic `VarExpiration#getExpiresAfter(key)` and `setExpiresAfter(key, duration)`. – Ben Manes Oct 25 '21 at 16:27
  • Hi, is there any `package/plugin` available to apply the same approach for the `NodeJs` project? – Mani Feb 14 '22 at 06:18
  • @Mani There is a TypeScript [cache](https://github.com/aholstenson/transitory) and [timer-wheel](https://github.com/aholstenson/timer-wheel) that ported concepts and code from Caffeine. I don't know that ecosystem, only that this one was inspired by the Java project. – Ben Manes Feb 14 '22 at 06:25
  • Thank you @BenManes, I hope this will help me. – Mani Feb 14 '22 at 06:30
  • `private LoadingCache cache = Caffeine.newBuilder() .expireAfter(new Expiry() { @Override public long expireAfterCreate(String key, Object obj, long currentTime) { if (obj.getExpiresOn() == null) { return Long.MAX_VALUE; } long seconds = obj.getExpiresOn() .minus(System.currentTimeMillis(), MILLIS) .toEpochSecond(); return TimeUnit.SECONDS.toNanos(seconds); } `
    @BenManes How is the .getExpiresOn() defined?
    – Martin Van Nostrand Aug 18 '22 at 13:37
  • 2
    @MartinVanNostrand That example was if you could derive the custom expiration from the value. For example, Google lets you cache address<->geocode resolutions for 30 days. You could store that in your db record and in-memory cache would know to expire to honor that. Similar if an http response etag. That's for you to figure out, else you can give constant values if a static configuration. – Ben Manes Aug 18 '22 at 21:48
0

I am currently working on the subject, I am inspired by this few articles, and I share with you the solution that worked well for me.

@EnableCaching
@Configuration
public class CaffeineConfiguration {

  @Autowired
  private ApplicationProperties applicationProperties;


  @Bean
  public Caffeine caffeineConfig() {
    return Caffeine.newBuilder().expireAfter(new Expiry<String, Object>() {
      @Override
      public long expireAfterCreate(String key, Object value, long currentTime) {
        long customExpiry = applicationProperties.getCache().getEhcache().getTimeToLiveSeconds();
        if (key.startsWith("PREFIX")) {
          customExpiry = 60;
        }
        return TimeUnit.SECONDS.toNanos(customExpiry);
      }

      @Override
      public long expireAfterUpdate(String key, Object value, long currentTime,
          long currentDuration) {
        return currentDuration;
      }

      @Override
      public long expireAfterRead(String key, Object value, long currentTime,
          long currentDuration) {
        return currentDuration;
      }
    });

  }


  @Bean
  public CacheManager cacheManager(Caffeine caffeine) {
    CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
    caffeineCacheManager.getCache(PROVIDER_RESPONSE);
    caffeineCacheManager.getCache(BCU_RESPONSE);
    caffeineCacheManager.setCaffeine(caffeine);
    return caffeineCacheManager;
  }

}
  • 2
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 05 '23 at 20:06