Caffeine Cache - Specify expiry for an entry
Asked Answered
G

3

11

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.

Gavrah answered 11/5, 2020 at 2:31 Comment(0)
A
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));
Abbotson answered 11/5, 2020 at 2:48 Comment(14)
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?Gavrah
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 for you.Abbotson
Hi, is it possible to get expiry for an entry? and override if needNewmark
@Newmark VarExpiration#getExpiresAfter(key) and setExpiresAfter(key, duration).Abbotson
Hi, is there any package/plugin available to apply the same approach for the NodeJs project?Childhood
@Childhood There is a TypeScript cache and 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.Abbotson
Thank you @BenManes, I hope this will help me.Childhood
private LoadingCache<String, Object> cache = Caffeine.newBuilder() .expireAfter(new Expiry<String, Object>() { @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); } <br/> @BenManes How is the .getExpiresOn() defined?Cabbageworm
@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.Abbotson
@Bean public Cache<String, Oauth2Token> sapOauth2Cache(){ return Caffeine.newBuilder() .initialCapacity(100) .maximumSize(1000) .expireAfter(new CustomExpiryForOauth()) .build(); }Rheum
@BenManes by configuring the expireAfter we specify a policy with varexpiration, which can then further be utilised for method put(K key, V value, Duration duration). Did I got that right?Fishgig
@DivyanshuSingh exactlyAbbotson
@BenManes Probably a noob question, but one more thing which I can't seem to get is What are Key & Graph? Expiry is a generic interface, where are these Key & Graph types defined?Fishgig
@DivyanshuSingh Those are just placeholders for the generic types, K/V, as examples.Abbotson
H
1

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;
  }

}
Hokkaido answered 3/2, 2023 at 13:57 Comment(1)
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.Eliathas
M
1

If anyones still looking for this, here is one comfy wrapper using Ben Manes earlier response. (in kotlin though, should be easily convertible to java)

class DynamicTTLCache<K, V>(
initialCapacity: Int,
maxCapacity: Long,
private val defaultTTLInMillis: Long = Long.MAX_VALUE) {

inner class CacheValue(
    val value: V,
    val ttlInMillis: Long
)

val cache: Cache<K, CacheValue> = Caffeine.newBuilder()
    .initialCapacity(initialCapacity)
    .expireAfter(object : Expiry<K, CacheValue> {
        override fun expireAfterCreate(k: K?, v: DynamicTTLCache<K, V>.CacheValue?, currentTime: Long): Long {
            return v?.ttlInMillis?.let { TimeUnit.MILLISECONDS.toNanos(it) } ?: defaultTTLInMillis
        }
        override fun expireAfterUpdate(k: K?, v: DynamicTTLCache<K, V>.CacheValue?,
                                       currentTime: Long, currentDuration: Long): Long {
            return currentDuration
        }
        override fun expireAfterRead(k: K?, v: DynamicTTLCache<K, V>.CacheValue?,
                                     currentTime: Long, currentDuration: Long): Long {
            return currentDuration
        }
    })
    .maximumSize(maxCapacity)
    .build()

fun put(key: K, value: V, ttlInMillis: Long?) {
    cache.put(key, CacheValue(value, ttlInMillis?: defaultTTLInMillis))
}

fun getIfPresent(key: K): V? {
    return cache.getIfPresent(key)?.value
}}
Monopteros answered 29/2 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.