Is it possible to set a different specification per cache using caffeine in spring boot?
Asked Answered
A

4

48

I have a simple sprint boot application using spring boot 1.5.11.RELEASE with @EnableCaching on the Application Configuration class.

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

application.properties

spring.cache.type=caffeine
spring.cache.cache-names=cache-a,cache-b
spring.cache.caffeine.spec=maximumSize=100, expireAfterWrite=1d

Question

My question is simple, how can one specify a different size/expiration per cache. E.g. perhaps it's acceptable for cache-a to be valid for 1 day. But cache-b might be ok for 1 week. The specification on a caffeine cache appears to be global to the CacheManager rather than Cache. Am I missing something? Perhaps there is a more suitable provider for my use case?

Amphioxus answered 17/4, 2018 at 18:27 Comment(4)
We mirrored the Guava adapter, and I don't know why it was restricted to global configurations. There is a pull request to add this. In the past, the Spring team have recommended using Java configuration as a workaround. Try asking @snicollPlaton
@BenManes thanks for this, are you able to point me to an example using Java config at all? Does it still use Caffeine as an underlying manager? I'm actually not precious about which impl I use, I "just" want one that can do this ;)Amphioxus
Maybe one of these links help? 1, 2, 3Platon
Most useful answer from @Stephane Nicoll Multiple Caffeine LoadingCaches added to Spring CaffeineCacheManagerSkantze
O
32

This is your only chance:

@Bean
public CaffeineCache cacheA() {
    return new CaffeineCache("CACHE_A",
            Caffeine.newBuilder()
                    .expireAfterAccess(1, TimeUnit.DAYS)
                    .build());
}

@Bean
public CaffeineCache cacheB() {
    return new CaffeineCache("CACHE_B",
            Caffeine.newBuilder()
                    .expireAfterWrite(7, TimeUnit.DAYS)
                    .recordStats()
                    .build());
}

Just expose your custom caches as beans. They are automatically added to the CaffeineCacheManager.

Ogden answered 7/2, 2019 at 10:16 Comment(3)
For use with @Cacheable, I had to manually register these with the CacheManager using cacheManager.registerCustomCache(name, cache.getNativeCache());Nirvana
Check if you have @EnableCaching in yourc configuration. It should not be necessary to register the cache manually.Ogden
@membersound, And how if you have multiple CaffeineCacheManager?Freezing
A
6

Instead of using SimpleCacheManager, you can use registerCustomCache() method of CaffeineCacheManager. Below is an example:

CaffeineCacheManager manager = new CaffeineCacheManager();

manager.registerCustomCache(
    "Cache1",
    Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterAccess(6, TimeUnit.MINUTES)
        .build()
);

manager.registerCustomCache(
    "Cache2",
    Caffeine.newBuilder()
        .maximumSize(2000)
        .expireAfterAccess(12, TimeUnit.MINUTES)
        .build()
);
Anhanhalt answered 8/8, 2022 at 12:41 Comment(0)
H
5

I converted my initial PR into a separate tiny project.

To start using it just add the latest dependency from Maven Central:

<dependency>
    <groupId>io.github.stepio.coffee-boots</groupId>
    <artifactId>coffee-boots</artifactId>
    <version>2.0.0</version>
</dependency>

Format of properties is the following:

coffee-boots.cache.spec.myCache=maximumSize=100000,expireAfterWrite=1m

If no specific configuration is defined, CacheManager defaults to Spring's behavior.

His answered 3/7, 2019 at 20:36 Comment(1)
Thanks for this. I was about to start doing some custom coding, and this solves my problem exactly.Nudnik
J
5

I config multiple cache manager like this

@Primary
@Bean
public CacheManager template() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager(CACHE_TEMPLATE);
    cacheManager.setCaffeine(caffeineCacheBuilder(this.settings.getCacheExpiredInMinutes()));
    return cacheManager;
}

@Bean
public CacheManager daily() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager(CACHE_TEMPLATE);
    cacheManager.setCaffeine(caffeineCacheBuilder(24 * 60));
    return cacheManager;
}

And use the cache normally

@Cacheable(cacheManager = "template")
@Override
public ArrayList<FmdModel> getData(String arg) {
    return ....;
}

Update

It look like the above code has a big mistake. So I change to

@Configuration
@Data
@Slf4j
@ConfigurationProperties(prefix = "caching")
public class AppCacheConfig {


    //This cache spec is load from `application.yml` file
    // @ConfigurationProperties(prefix = "caching")
    private Map<String, CacheSpec> specs;

    @Bean
    public CacheManager cacheManager(Ticker ticker) {
        SimpleCacheManager manager = new SimpleCacheManager();
        if (specs != null) {
            List<CaffeineCache> caches = specs.entrySet().stream()
                    .map(entry -> buildCache(entry.getKey(), entry.getValue(), ticker)).collect(Collectors.toList());
            manager.setCaches(caches);
        }
        return manager;
    }

    private CaffeineCache buildCache(String name, CacheSpec cacheSpec, Ticker ticker) {
        log.info("Cache {} specified timeout of {} min, max of {}", name, cacheSpec.getTimeout(), cacheSpec.getMax());
        final Caffeine<Object, Object> caffeineBuilder = Caffeine.newBuilder()
                .expireAfterWrite(cacheSpec.getTimeout(), TimeUnit.MINUTES).maximumSize(cacheSpec.getMax())
                .ticker(ticker);
        return new CaffeineCache(name, caffeineBuilder.build());
    }

    @Bean
    public Ticker ticker() {
        return Ticker.systemTicker();
    }
}

This AppCacheConfig class allow you to define many cache spec as you prefer. And you can define cache spec in application.yml file

caching:
  specs:
    template:
      timeout: 10 #15 minutes
      max: 10_000
    daily:
      timeout: 1440 #1 day
      max: 10_000
    weekly:
      timeout: 10080 #7 days
      max: 10_000
    ...:
      timeout: ... #in minutes
      max:
      

But still, this class has a limitation that we can only set timeout and max size only. because of CacheSpec class

@Data
public class CacheSpec {

    private Integer timeout;
    private Integer max = 200;

}

Therefore, If you like to add more config parameters, you are to add more parameters on CacheSpec class and set the Cache configuration on AppCacheConfig.buildCache function.

Hope this help!

Josefjosefa answered 8/4, 2020 at 8:9 Comment(2)
what is the big mistake? it seems to work for me here... i defined one bean as primary and the other manager then has to be but in cachable annotation as you did..?Blindheim
thanks for this implemetation i don't know if everyone need it but in my case i have to add spring.main.allow-bean-definition-overriding=true in the resources/application.properties also in the configuration example you put timeout: 10 #15 minutes , but is actually 10 minutesOuzel

© 2022 - 2024 — McMap. All rights reserved.