tsar2512
tsar2512

Reputation: 2994

Caffeine Cache - Specify expiry for an entry

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.

Upvotes: 11

Views: 14807

Answers (3)

oga.tatsumi
oga.tatsumi

Reputation: 11

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

Upvotes: 1

Ben HMIDA Bassem
Ben HMIDA Bassem

Reputation: 21

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

}

Upvotes: 1

Ben Manes
Ben Manes

Reputation: 9591

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

Upvotes: 9

Related Questions