Reputation: 2994
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
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
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
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