Caffeine provides a higher performance local cache than JHipster's default provider. This is in regards to both concurrency and hit rates.
The default provider may only be used as a local cache in JHipster's implementation ("... cannot work as a distributed cache"). Therefore the alternative provider should have a positive net impact.
Spring Cache includes direct support against Caffeine's native api. This has a potential limitation where Spring's property setting only allows for a single configuration (e.g. all caches have the same maximum size), rather than configured per name. This was how Spring wrote their Guava provider and Caffeine's was a direct evolution of that code. There are alternative configurators or the Java bean configuration to bypass this.
Spring Cache and Hibernate offer JCache integrations, which requires the additional caffeine-jcache dependency. This may be the simplest option to support both.
I should have some free cycles coming up, which means that I can try to do this work if given some guidance and if there is interest. However, I have not used Spring since the 1.x heydays so I lack familiarity in that respect.
Hi Ben, I remember we talked about this a few years ago, I have no idea why this hasn't been done! Anyway, big +1 from me: I'll do my best to help, but that shouldn't be very hard.
+1 Caffeine makes the most sense as default cache.
Sorry, I got swamped building out EDI integration (the XML of the `70s). This is still in my backlog, though.
@ben-manes Are you still expecting working on this?
I got busy and did try later on. However there are many templates to update and I'm not familiar with the code, so I didn't feel comfortable sending a PR based on blind updates. If someone else wants to take this over this that would be great.
The easier is to fork a jhipster sample app and then add your changes.
Then if you need help to include your changes in the template we can help you...
Do you want to give a try?
@ben-manes Maybe you can give us a link to a repo that showcases a spring boot app with caffeine enabled. It would be good to see a configuration that follows best practices according to you. Then we can add a bounty reward on this issue to have someone from the community do the implementation.
@PierreBesson I believe this tutorial is how Spring Boot intends it to be used. The Spring support was taken from the older Guava provider, so older tutorials with should be equivalent. I think the best practices are Spring Cache specific, not provider-based.
I haven't used Spring since the early days, prior to their annotation-based caching. I use the cache APIs directly (tutorial), treating it like a Java Collection (data structure) rather than a framework (e.g. database ORM). In that way the usage doesn't feel very magical, just using Map.computeIfAbsent
and friends. The configuration is defined in the application's config file and injected via Guice.
The cache itself doesn't manage any threads / disk / network - it's just additions wrapping ConcurrentHashMap
. The best practices are mostly around concurrency as with anything in j.u.c., with perhaps a few extra details.
I don't think the integration should be very difficult, judging from my earlier attempts. I did feel like I was blindly hacking unfamiliar code that I wasn't trying to understand, just copy-and-paste equivalents, which is what dissuaded me from completing this.
I am fine closing this and waiting until a user opens it, who should be more familiar with the work involved. I thought it looked like something I could do over my commute, but in retrospect should have tried it and sent a PR if successful, rather than opening early.
@jdubois @PierreBesson @DanielFran @pascalgrimaud : If you guys are okay, I am willing to work on this issue; it will take me some time to understand all the nuances here, but I am willing to give it a try. Is that fine? 馃槃
@SudharakaP feel free to do it! As it would a useful improvement, and as this has been opened for too long, I'm adding a bug bounty to this.
@jdubois : Wonderful, thanks. Will start working on it today 馃憤
@jdubois @ben-manes : Do you know any particular configuration properties or default values we need to set for better performance?
I've done the preliminary part of hooking up Caffeine. For now I've set the setExpireAfterAccess
and setMaximumSize
to mimic the existing Ehcache configuration. I'll commit the changes after I do some final testing. 馃槃
It depends on if you want to use the JCache or not, I suppose.
In the non-JCache world, CaffeineSpec is the configuration that can be externalized to a file. Other details are instance based, like the CacheLoader
. Spring Cache uses the native APIs directly in their adapter, which offers more flexibility for bean-style configuration.
For JCache the configuration can be externalized via Typesafe Config, with definition defined in reference.conf. That gets deserialized into that CaffeineConfiguration class that you pointed to.
JCache is nice if you want to integrate with other frameworks, specifically Hibernate. However it's pretty horrible from a user's perspective if using their APIs and I certainly wouldn't promote it for normal application development. I think you're already using JCache for the other caching providers so that makes a lot of sense. I don't know if you are replacing their configuration files (like ehcache.xml
) with your own or using theirs if available, in which case Typesafe Config
is configuration library that I ended up using.
@ben-manes : Thanks for the detailed explanation 馃憤 . Yes, we use JCache right now (via our CacheConfiguration and yes we replace their configuration with our own.
Maybe I was a bit unclear on my previous post, I was just looking for some default values for the fields in reference.conf so that I can put them in our configuration since I couldn't find an example configuration anywhere (for example the use of store-by-value
). Let me first finish coding this off and I think I'll have some more questions. 馃槃
oh I see, thanks!
Ehcache's time-to-live-seconds
is our expireAfterWrite
and their time-to-idle
is our expireAfterAccess`.
Note that we set store-by-value
to be disabled, whereas JCache defaults that to be enabled in MutableConfiguration
class. It makes things ungodly slow and not very practical, most of the time.
@jdubois @ben-manes : I've created the pull requests relevant to the change. Please let me know if you have any suggestions or comments. :smile:
There's one other part I need to do; update the documentation on our webpage. I'll do that tomorrow. :smile:
@jdubois : Inspired from this comment should we still support Ehcache ? In the commits I replaced Ehcache with Caffeine since I thought that's what this is about (plus since Caffeine is more performant in every sense). But maybe I am wrong; if we still need to support Ehcache as a secondary option let me know; I have to change the code a bit.
@SudharakaP Please see my comment at https://github.com/jhipster/jhipster/pull/383#issuecomment-526151344
@pmverma @DanielFran : With regard to the discussion here, https://github.com/jhipster/jhipster/pull/383#issuecomment-526151344 ; thanks for the clarification, I'll change it this evening so that Caffeine becomes the default option not replace Ehcache. Sorry for the misunderstanding. :smile:
Sorry @SudharakaP my idea is that we support both! In fact, I would keep ehcache by default as it's more widely used and known by Java developers, and I see Caffeine as an optimization for people who know what they do
@jdubois : No problem; I'll change this to add Caffeine as an option. 馃憤
I've changed things and it all looks good to me. Let me know if you guys see any issues 馃槃
Couple of things; I've configured Caffeine configuration to mimic the Ehcache configuration from the users perspective; that is the user has to configure time-to-live-seconds
and max-entries
as before. Now we can change these to Caffeine specific property names as well as include more of these properties in our properties file (currently I've only added support for the two above). Do you think this is something we should do or is it just that we give the user a basic Caffeine configuration and if they want to add more stuff they could do it themselves? In other words, I can try to add as much properties as possible from the Caffeine configuration to our properties files. What's your take on this? 馃槃
@DanielFran @jdubois : I see that you've been assigning labels to stuff and doing some housekeeping. Just wondering do you guys need any help with bug triaging or any other stuff that needs attention? I have a lot of free time these days and could help out if you'd like. 馃槃
@ben-manes : Hi Ben, Now that I've completed the integration; I was just going through the documentation part for Caffeine. It's almost ready except I was wondering where to get a logo of Caffeine to include in our homepage under the Server Side Options
section.
Do you by any chance have a logo which you can provide us (hope you don't mind putting the logo in the first place)? 馃槃
That's really great @SudharakaP, thanks for doing all of this. 馃槃
There isn't a logo, but I did borrow an image from Wikipedia for the caffeine molecule. That has feels a bit like different systems connected together / communicating. What do you think about using something like it with the project name?
@ben-manes : Sure that would work; we do use the name for every project in our webpage. Let me do a sample and show you. 馃憤
@ben-manes : How about this; you agree?
lgtm 馃樅
@ben-manes : Note that the main blob about Caffeine is; https://github.com/jhipster/jhipster.github.io/blob/32ee53cadd45b8fd542043be04be6b8de0a44f84/pages/using_cache.md#caching-with-caffeine
Please let us know if you have any additional suggestions on what to add there.
Thanks @SudharakaP, that seems pretty good.
It is not very clear why one might consider Caffeine over Ehcache, e.g. is it legacy for backwards compatibility or an improvement? That's not really JHipster's role to advocate one way or another, but does offer hints like Ehcache is ... a perfect solution for "normal" monoliths
or Infinispan is a highly performant caching solution
. I think the wordings should either shed an implied bias and be boring (like Spring's documentation), or if allowed then I'd ask for a little marketing spin too. 馃槃
Oh that reminds me, JCache does not have an explicit hook for integrating with dependency injectors, except that they have a Factory<T>
with a default expectation of using ClassLoader.loadClass
for resolution. There is a hook in Caffeine's provider to delegate to the DI in TypesafeConfigurator
, which you may want to use? Not really sure, actually.
Thanks @SudharakaP, that seems pretty good.
You are welcome 馃槃
It is not very clear why one might consider Caffeine over Ehcache, e.g. is it legacy for backwards compatibility or an improvement? That's not really JHipster's role to advocate one way or another, but does offer hints like
Ehcache is ... a perfect solution for "normal" monoliths
orInfinispan is a highly performant caching solution
. I think the wordings should either shed an implied bias and be boring (like Spring's documentation), or if allowed then I'd ask for a little marketing spin too. 馃槃
I agree; how about your readme line; "Caffeine is a high performance, near optimal caching library" might be a good choice. Or do you have something else in mind?
Oh that reminds me, JCache does not have an explicit hook for integrating with dependency injectors, except that they have a
Factory<T>
with a default expectation of usingClassLoader.loadClass
for resolution. There is a hook in Caffeine's provider to delegate to the DI inTypesafeConfigurator
, which you may want to use? Not really sure, actually.
Hmm.... 馃槃 I am trying to understand what you meant here (I have to do some more research on JCache internals; I don't have a deep knowledge as you do about JCache and how it hooks up with other providers); let me do some more investigation on this. I am sure we can improve on this more, but for now I think this is a good starting solution. 馃槃
I agree; how about your readme line; "Caffeine is a high performance, near optimal caching library" might be a good choice. Or do you have something else in mind?
Perfect :)
I am trying to understand what you meant here
JCache has features that are usually externalized into a configuration file, since the Java code is supposed to be provider agnostic. This means that things like cache loader, listeners, etc. are specified by the class name and loaded by the provider, which itself is accessed by a static singleton. Being static-based is not natural for dependency injectors like Spring and Guice which wrap all instances within a container's context. Since the JCache provider is then loading these feature classes, its not being done by the DI to inject dependencies.
In the JCacheGuiceTest, I have a hook to resolve these FQN classes through the DI. Then when the config has a class name, it can resolve by delegation. I haven't seen other JCache providers offer a hook and, since JCache is super unpopular, it typically hasn't been an issue.
static final class GuiceFactoryCreator implements FactoryCreator {
final Injector injector;
@Inject
GuiceFactoryCreator(Injector injector) {
this.injector = injector;
}
@Override
@SuppressWarnings("unchecked")
public <T> Factory<T> factoryOf(String className) {
try {
Class<T> clazz = (Class<T>) Class.forName(className);
return injector.getProvider(clazz)::get;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
static final class CaffeineJCacheModule extends AbstractModule {
@Override protected void configure() {
requestStaticInjection(TypesafeConfigurator.class);
bind(FactoryCreator.class).to(GuiceFactoryCreator.class);
}
}
I think we forgot to add the option at https://start.jhipster.tech/
@SudharakaP : can you help to do it plz ?
It's related to this project: https://github.com/jhipster/jhipster-online
@pascalgrimaud : Yes, I completely missed that. Will add this to my queue. :smile:
@pascalgrimaud : I've added it; let me know if you see any issues. :smile:
Most helpful comment
oh I see, thanks!
Ehcache's
time-to-live-seconds
is ourexpireAfterWrite
and theirtime-to-idle
is our expireAfterAccess`.Note that we set
store-by-value
to be disabled, whereas JCache defaults that to be enabled inMutableConfiguration
class. It makes things ungodly slow and not very practical, most of the time.