0

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?

6
  • 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 Commented Dec 28, 2015 at 12:16
  • 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? Commented Dec 28, 2015 at 12:22
  • I am actually +1 with Jean-Baptiste but I gave you a few more pointers below. Commented Dec 28, 2015 at 12:27
  • 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) Commented Dec 28, 2015 at 17:10
  • 1
    @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. Commented Dec 28, 2015 at 20:51

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Great answer, thank you @stéphane-nicoll. I think ExpiringMap is what I was looking for, since semantically cache is not probably the most suitable thing that should be used in my scenario.
I think part of the confusion is that Guava (and Caffeine) evict expired entries during periodic maintenance using O(1) lists. This approach had better behavior than code similar to ExpiringMap using a dedicated thread + O(k) priority queue + weak references. The tradeoff was to not support per-entry expiration, which we found was seldomly useful and would result in a poor API.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.