FF4J: server side caching for feature store

1.2k Views Asked by At

I am using ff4j-spring-boot-starter with f4j-store-springjdbc to setup my ff4j server. All my other microservices use the endpoints given by ff4j to access this feature store and the response is returned in JSON.

Since the data does not change that often and we are trying to save unnecessary database calls, I am trying to cache my feature store on the server. Also, if the feature flag DB is down (for refresh/maintenance) we still want the other services to successfully start-up using these values from the cache.

We imported ff4j-store-ehcache in our pom.xml

        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RC2</spring-cloud.version>
        <ff4j.version>1.8.7</ff4j.version>
        <odbc.version>19.6.0.0.0</odbc.version>
    </properties>
    <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-spring-boot-starter</artifactId>
            <version>${ff4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-store-springjdbc</artifactId>
            <version>${ff4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-web</artifactId>
            <version>${ff4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-store-ehcache</artifactId>
            <version>${ff4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-webapi-jersey2x</artifactId>
            <version>1.8.11</version>
        </dependency>
        <!-- FF4J dependencies - end -->

Our FeatureCacheProviderEhCache implementation looks like this in FF4jConfig.java looks like this.

@ConditionalOnClass({​​​​ ConsoleServlet.class, FF4jDispatcherServlet.class }​​​​)
public class Ff4jConfig extends SpringBootServletInitializer {​​​​

    @Autowired
    private DataSource dataSource;

    @Bean
    public ServletRegistrationBean<FF4jDispatcherServlet> ff4jDispatcherServletRegistrationBean(
            FF4jDispatcherServlet ff4jDispatcherServlet) {​​​​
        ServletRegistrationBean<FF4jDispatcherServlet> bean = new ServletRegistrationBean<FF4jDispatcherServlet>(
                ff4jDispatcherServlet, "/web-console/*");
        bean.setName("ff4j-console");
        bean.setLoadOnStartup(1);
        return bean;
    }​​​​

    @Bean
    @ConditionalOnMissingBean
    public FF4jDispatcherServlet getFF4jDispatcherServlet() {​​​​
        FF4jDispatcherServlet ff4jConsoleServlet = new FF4jDispatcherServlet();
        ff4jConsoleServlet.setFf4j(getFF4j());
        return ff4jConsoleServlet;
    }​​​​

    @Bean
    public FF4j getFF4j() {​​​​
        FF4j ff4j = new FF4j();
        FF4JCacheManager cacheManager = new FeatureCacheProviderEhCache();
        ff4j.setPropertiesStore(new PropertyStoreSpringJdbc(dataSource));
        ff4j.setFeatureStore(new FeatureStoreSpringJdbc(dataSource));
        ff4j.setEventRepository(new EventRepositorySpringJdbc(dataSource));
        ff4j.cache(cacheManager);
        

        // Enable audit mode
        ff4j.audit(true);

        return ff4j;
    }​​​​

}​​​​

WebConsole reflects the database changes as soon as they are commit and it does not seem like the cache is being hit or that the cache is storing any of the data in it.

I want to be able to use this cache without going to database for every single lookup.

We also tried using InMemoryCacheManager as an alternate to FeatureCacheProviderEhCache, but same results. We do see the clear cache button on our web-console in both the implementations.

Also, is there a better way for me to test if my api calls are actually getting data from cache and not from db, without having to shutdown the db?

Update : After implementing our own FeatureCacheProviderEhCache, and logging in it, I tried to access the api/ff4j and the featureNames is coming in as empty in that response. Please refer to the log :

METHOD=getCacheFeatures, LINENO=160, MSG=getCacheFeatures :: [ name = ff4jCacheFeatures status = STATUS_ALIVE eternal = false overflowToDisk = true maxEntriesLocalHeap = 10000 maxEntriesLocalDisk = 10000000 memoryStoreEvictionPolicy = LRU timeToLiveSeconds = 120 timeToIdleSeconds = 120 persistence = LOCALTEMPSWAP diskExpiryThreadIntervalSeconds = 120 cacheEventListeners: ; orderedCacheEventListeners:  maxBytesLocalHeap = 0 overflowToOffHeap = false maxBytesLocalOffHeap = 0 maxBytesLocalDisk = 0 pinned = false ]
METHOD=listCachedFeatureNames, LINENO=59, MSG=listCachedFeatureNames[]
METHOD=getCacheFeatures, LINENO=160, MSG=getCacheFeatures :: [ name = ff4jCacheFeatures status = STATUS_ALIVE eternal = false overflowToDisk = true maxEntriesLocalHeap = 10000 maxEntriesLocalDisk = 10000000 memoryStoreEvictionPolicy = LRU timeToLiveSeconds = 120 timeToIdleSeconds = 120 persistence = LOCALTEMPSWAP diskExpiryThreadIntervalSeconds = 120 cacheEventListeners: ; orderedCacheEventListeners:  maxBytesLocalHeap = 0 overflowToOffHeap = false maxBytesLocalOffHeap = 0 maxBytesLocalDisk = 0 pinned = false ]

This is the response of the API

The cache is created but it is not storing any values inside it. I am setting the cache the way it says in the first answer. I the logs, when I print listCachedFeatures(), that is printing as empty as well. I am still not able to see featureNames in the cache. Which part am I not configuring correctly?

1

There are 1 best solutions below

1
On

First I would like to confirm that you use the cache properly. ff4j.cache(cacheManager) is what you want to do. Under the hood this is what is happening:

public FF4j cache(FF4JCacheManager cm) {
    FF4jCacheProxy cp = new FF4jCacheProxy(getFeatureStore(), getPropertiesStore(), cm);
    setFeatureStore(cp);
    setPropertiesStore(cp);
    return this;
}
  1. When you edit features or properties values through the web console, FF4j knows cache needs to be evicted and db immediately updated, (cache will be used only for READ).

  2. FeatureCacheProviderEhCache and InMemoryCacheManager work the same way. The first use the JSR107. It is personal preference, maybe using ehcache you can switch to a distribute cache like Terracotta later.

  3. How to know that only cache is used and not the DB (on read) ? You can either create an advice with AOP to decorate the class FeatureCacheProviderEhCache or more basic you copy the class in your project, update the method with proper logs and use your class as a CacheManager.

  4. You may consider to use cache at your micro services level as well (if implemented in Java) and not only in the web console. If you do this, you can save some time by not executing the REST call to get JSON.

  5. EhCache and InMemory cache work as the name stated in memory, meaning 2 micro services may have different values in the cache. If you want consistent caching among microservices (not mandatory) you want to use distributed cache like REDIS or HAZELCAST.