0
votes

I have a Spring Boot/MVC app that should store some Simple POJOs sent from users for 15 minutes of time. When this time expires, this object should be removed from ConcurrentHashMap. At first ConcurrentHashMap was something I wanted to implement this feature with, but then I thought maybe leveraging Guava's cache would be a better option, since it has a time-based eviction out of the box.

My service implementation

@CachePut(cacheNames = "teamConfigs", key = "#authAccessDto.accessToken")
@Override
public OAuthAccessDto saveConfig(OAuthAccessDto authAccessDto) {
    return authAccessDto;
}

@Override
@Cacheable(cacheNames = "teamConfigs")
public OAuthAccessDto getConfig(String accessToken) {
    // we assume that the cache is already populated
    return null;
}

As you can see, we save data with saveConfig and then when we need to retrieve it, we call getConfig.

Cache configuration in Spring boot is the following (yml file):

spring:
  cache:
    cache-names: teamConfigs
    guava:
      spec: expireAfterWrites=900s

However, after reading Guava's cache doc https://github.com/google/guava/wiki/CachesExplained I found that Guava can clean up caches even before the time defined in expireAfterWrites elapses (and even before it runs out of memory).

How can I configure Guava Cache to keep objects until the time expires (considering it did not run out of memory). Maybe I should opt for some other solution?

1
I think I will go for http session, where I will store this POJO and that will persist across requests and will get removed when the session expires - Sergei Ledvanov
The doc says "Expire entries after the specified duration has passed since the entry was created, or the most recent replacement of the value". What makes you think it would evict entries before the duration has passed? - JB Nizet
I am actually +1 with Jean-Baptiste but I gave you a few more pointers below. - Stephane Nicoll
Warning: the cache may evict entries before this limit is exceeded -- typically when the cache size is approaching the limit. - this makes me think that cache is not a reliable place that should be used as a storage where I can put object once and guaranteed get it later (before it's evicted on a time basis) - Sergei Ledvanov
@Sergei that only applies to size based eviction, not time based, and it's for reasonable technical reasons. Guavas cache will never do time based expiration early, though it may do it late. - Louis Wasserman

1 Answers

2
votes

I don't know about Guava but you could use any JSR-107 compliant provider and a simple configuration that would look like this:

@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
    return cm -> {
        cm.createCache("teamConfigs", new MutableConfiguration<>()
            .setExpiryPolicyFactory(CreatedExpiryPolicy     
                .factoryOf(new Duration(MINUTES, 15)));
    };
}

Caffeine (a rewrite of Guava with Java8) has a JSR-107 provider so you could use that. Maybe that version does not exhibit what you experience with Guava? If so, support is expected in Spring Boot 1.4 but you can give the JSR-107 support a try right now.

If you don't want to use that, you could give expiringmap a try. It does not have any Cache implementation in the Spring abstraction but since it's a Map you can easily wrap it, something like on the top of my head:

@Bean
public Cache teamConfigsCache() {
    Map<Object, Object> map = ExpiringMap.builder()
        .expiration(15, TimeUnit.MINUTES)
        .build();
    return new ConcurrentMapCache("teamConfigs", map , true);
}

If Spring Boot discovers at least a Cache bean in your configuration, it auto-creates a CacheManager implementation that wraps them. You can force that behaviour via the spring.cache.type property.