Spring boot - Evicting Cache with dynamic TTL period

Viewed 7469

From my microservice(SERVICE-A) I hit a rest api call to another microservice(SERVICE-B) for login and getting a access token and that API will respond with TTL of that token. I need to cache the token till that TTL(seconds) that is responded by SERVICE-B. So my implementation is as below,

@Cacheable("USERTOKEN")
public String getUserToken()
{
  //Hits Service-B
  //Gets token and TTL as a response from Service-B
  //Returns Token or Token with TTL
}

I need to change the above method something as

@Cacheable("USERTOKEN")
public String getUserToken()
{
  //Hits Service-B
  //Gets token and TTL as a response from Service-B
  //Sets expiry time for "USERTOKEN" cache   <-- this needs to be added
  //Returns Token or Token with TTL
}

Even after returning from getUserToken() if it is possible to setExpiryTime for the "USERTOKEN" cache using the TTL returned by getUserToken() it will be fine. We can set Scheduled for eviction but it is going to be a static time period. But here I need it to be set as dynamic value based on the response from Service-B. How can I achieve this.

3 Answers

If you use caffeine cache, you can use varying expiration policy:

From caffeine wiki page:

// Evict based on a varying expiration policy
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .expireAfter(new Expiry<Key, Graph>() {
      public long expireAfterCreate(Key key, Graph graph, long currentTime) {
        // Use wall clock time, rather than nanotime, if from an external resource
        long seconds = graph.creationDate().plusHours(5)
            .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));

which caching service are you using?

if u using Redis cache, they have got many commands for this :

EXPIRE : Set a timeout on key. EXPIREAT : Same as previous but takes an absolute Unix timestamp (seconds since January 1, 1970). TTL : Returns the remaining time to live of a key that has a timeout One important thing you have to know about Expiration on Redis : the timeout value is cleared only when the key is removed or overwritten using SET or GETSET. All others commands (INCR, LPUSH, HMSET, ...) will never change the initial timeout.

Absolute expiration is a native feature of Redis using EXPIRE. To implement a sliding expiration you simply need to reset to timeout value after each command.

A basic way to do this could be

MULTI
GET MYKEY
EXPIRE MYKEY 60
EXEC

If you are using Spring's default implementation you won't be able to set Cache TTL. But with other providers such as EhCache, Gemfire & Guava you can do that but only during configuring Cache Managers. See ehcache Documentation and Spring's Gemfire's Documentation. For Guava see

Like this - For Ehcache.

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
  .with(CacheManagerBuilder.persistence(tmpDir.newFile("myData")))
  .withCache("threeTieredCache",
    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
      ResourcePoolsBuilder.newResourcePoolsBuilder()
        .heap(10, EntryUnit.ENTRIES)
        .offheap(1, MemoryUnit.MB)
        .disk(20, MemoryUnit.MB, true))
      .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofSeconds(20)))
  ).build(false);

Configuration configuration = cacheManager.getRuntimeConfiguration();
XmlConfiguration xmlConfiguration = new XmlConfiguration(configuration);  
String xml = xmlConfiguration.toString(); 

For - Gemfire

<gfe:region-template id="BaseRegionTemplate" initial-capacity="51" load-factor="0.85" persistent="false" statistics="true"
      key-constraint="java.lang.Long" value-constraint="java.lang.String">
    <gfe:cache-listener>
      <bean class="example.CacheListenerOne"/>
      <bean class="example.CacheListenerTwo"/>
    </gfe:cache-listener>
    <gfe:entry-ttl timeout="600" action="DESTROY"/>
    <gfe:entry-tti timeout="300 action="INVLIDATE"/>
  </gfe:region-template>

For - Caffiene or Internal Implementation of Guava

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .refreshAfterWrite(1, TimeUnit.MINUTES)
    .build(key -> createExpensiveGraph(key));
Related