DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Implement Hibernate Second-Level Cache With NCache
  • Scaling Databases With EclipseLink And Redis
  • Redis Is Not Just a Cache
  • Scaling in Practice: Caching and Rate-Limiting With Redis and Next.js

Trending

  • How to Convert XLS to XLSX in Java
  • Why Documentation Matters More Than You Think
  • Optimize Deployment Pipelines for Speed, Security and Seamless Automation
  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  1. DZone
  2. Data Engineering
  3. Data
  4. Hibernate, Redis, and L2 Cache Performance

Hibernate, Redis, and L2 Cache Performance

Redis is a great platform for caching. Unfortunately, free options for integrating it into Hibernate as L2 cache are lacking. Let's see if we can fix that.

By 
Clayton Long user avatar
Clayton Long
·
Jul. 27, 21 · Analysis
Likes (3)
Comment
Save
Tweet
Share
14.3K Views

Join the DZone community and get the full member experience.

Join For Free

database caching

Source: Getty Images


Hibernate is the de-facto standard for ORM (Object-Relational Mapping) in Java. But, what happens when Hibernate doesn't offer the performance you need? That's where cache comes into play. 

By default, Hibernate offers L1 cache. It also allows L2 cache to be configured. L1 cache is session cache, which is to say that it is cache that's maintained independently by each session. Before querying the database for an object, the session first checks the L1 cache to see if the object is already available (i.e. it has been recently accessed). Hibernate's optional L2 cache is at the SessionFactory level; it spans sessions - and in the case of distributed cache, it can also span JVMs.

L2 Cache Options

Out of the box, Hibernate has L2 cache support for JCache and EHCache. JCache, being the Java caching standard has several implementations, including Hazelcast, Coherence and Infinispan to name a few. Both EHCache and JCache implementations can support local and distributed caching, with Terracotta being a popular choice for distributed caching with EHCache.

Aside from Hibernate's out-of-the-box L2 cache implementations for JCache and EHCache, there are also other L2 caching integrations. One such integration is Redisson, which offers Hibernate L2 caching support for Redis.

When L2 Caching Is Needed

Read heavy applications can especially benefit from additional caching. This is because cache provides the most benefit for successive reads. If there is a lot of locality when fetching entities from the database, that is a great case for L2 caching.

L2 caching would not be as beneficial if the application was geared more towards serving presentation (e.g. web page fragments) with a high degree of temporal locality and very little interaction with underlying data, save to generate that presentation. In that case, caching at the controller or the service level would be more impactful than database caching, which could be taken out of the critical path.

However, with more and more emphasis on single-page web applications that fetch data from services and render their presentation on the client (browser), there is an increased emphasis on data speed, and therefore appropriate use of L2 cache. It should, however, be noted that although L2 cache may help performance (especially for data read-heavy applications with high temporal locality), it is not a substitute for good design. It may, however, help a well-engineered system perform even better.

Redis as a Potential Solution

Redis is an in-memory data structure store that can be easily distributed. Redis claims 8x the throughput and 50% the latency compared to popular NoSQL solutions. It's safe to say that as a distributed data source, Redis is darn fast. Of course, actual speeds will be affected by network bandwidth and proximity to the application(s) interacting with Redis.

Given Redis's purported performance and how simple it is to set up and use, one might think that there would be a selection of Hibernate L2 Redis integrations to choose from. However, it appears as if there is only one: Redisson. That's not to say there aren't other Java-based Redis cache clients. But only Redisson provides a library for Hibernate L2 cache integration.

Digging a little deeper into Redisson, however, it seems as if local cache support, its most compelling feature, only has cursory support with its open source (non-paid) offering. Also, it appears that the performance of Redisson without local caching lags a bit behind other popular Java-based Redis clients like Jedis. Even with local caching, Redisson's read performance edge only exists after the local cache hit rate climbs near 50%.

Local Cache or No Local Cache?

I'm going to be the Devil's advocate here and make the claim that for database caching, local cache is not all it's cracked up to be. I base that claim on what you should be trying to achieve with L2 caching and how local cache is managed. The purpose of L2 caching is to improve the performance of data reads by shifting the burden from the database to a faster (and simpler) shared data retrieval mechanism. 

The purpose of L2 caching is to improve the performance of data reads by shifting the burden from the database to a faster (and simpler) shared data retrieval mechanism.

Given that, an L2 caching solution should be (1) faster and less taxed by reads than the database it is supporting and (2) simple to use, scale and deploy. Redis definitely fits those requirements. And the distributed nature of Redis means that as a cache store, its data can be easily shared.

Local cache adds additional complexity with the promise of more performance, but only after a sufficient amount of data is cached locally. If that data is cached locally, then it's at least in part managed by the JVM, which likely means additional memory overhead for the application. That begs the question, "Is local cache worth the additional complexities and overhead it brings if I already have a reasonably fast distributed caching solution?" I think in some cases that answer is yes - for example, if reads were frequent, updates were infrequent and the cache could be sufficiently pre-loaded. In most other cases, however, I think that answer is no.

Using Redis as L2 Cache

Redis is fast, distributed and easy to setup. Redisson provides a Redis L2 cache integration. Let's take a look at how that might work with a Spring Boot application.

First, start the Redis server. If you have Redis installed locally, just type redis-server and that should be enough to start Redis in standalone mode. Next, add the Reddison dependency and configure Redisson as the L2 caching provider in the Spring Boot application config (e.g. application.yml).

In the Maven pom.xml:

XML
 
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-hibernate-53</artifactId>
    <version>3.15.6</version>
</dependency>


In the application.yml:

YAML
 
spring.jpa.hibernate.cache.use_second_level_cache: true
spring.jpa.hibernate.cache.region.factory_class: org.redisson.hibernate.RedissonRegionFactory
spring.jpa.hibernate.cache.redisson.config: /redisson.yaml


The redisson.yaml file referenced above should be in the classpath. It is detailed here.

Finally, any entities that are to be cached in Hibernate's L2 cache must be declared as Cacheable as follows.

Java
 
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  @Column(unique = true)
  @NotNull
  private String username;

  @Column(name = "first_name")
  private String firstName;

  @Column(name = "last_name")
  private String lastName;
}


Not too bad. However, there are a few issues to note. First, Redisson's configuration is external to Spring's - and that is not optional. Second, we are not getting local caching, which is Redisson's big differentiator. We could specify org.redisson.hibernate.RedissonLocalCachedRegionFactory, which would give us local caching. But in that implementation, local caching is only available via Maps, 100% managed by the JVM. If you want to step up to a more robust local caching solution, then you'll have to get the paid version of Redisson.

A New Alternative

It's true that Redisson provides the only known integration for Redis as Hibernate L2 cache. But there's nothing preventing us from writing our own Redis L2 cache integration using, say, Jedis. Using Jedis, we could have a faster solution without local cache and we could piggyback off of Hibernate's configuration - everything could be in Spring Boot's application.yml config. The only question is how hard is it to implement?

Turns out that it's not that hard. 

Here's the basic rundown of what needs to be done to write a Hibernate L2 cache integration in Hibernate 5.

  1. Implement a class that extends org.hibernate.cache.spi.support.RegionFactoryTemplate
  2. Add that class to the Hibernate configuration spring.jpa.hibernate.cache.region.factory_class: com.mycompany.JedisRegionFactory

That's it!

And since we get to define how the properties are set, we can also make the configuration in application.yml look something like this.


YAML
 
spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          region:
            factory_class: com.mycompany.JedisRegionFactory
          redis:
            standalone:
            host: localhost
            port: 6379


No need for additional/external configuration!

See Redis L2 Cache Integration Using Jedis for additional details and a complete working example.

Some Jedis Gotchas

The biggest Jedis gotcha is that it only supports String data types. All the keys must be Strings and all the objects stored must also be Strings. Not surprisingly, Hibernate supplies keys as objects and of course, the data stored (and retrieved) is in the form of objects. So, storing and retrieving objects from Jedis requires some conversion. This can be achieved in a number of ways. But before keys can be used to query using Jedis, they must be converted to Strings, and before objects are stored by Jedis, they must also be converted to Strings. Conversely, when objects are retrieved from Jedis, they must be converted from their String form into their original object form.

Probably the easiest way to achieve this conversion is through the serialization and deserialization of objects. Objects can be serialized into bytes and those bytes can then be base64 encoded to become visible Strings. The reverse can be done to convert stored Strings into their original object form.

Example:

Java
 
static String convertObjectToString(Object obj) throws IOException {
  try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos)){
    out.writeObject(obj);
    out.flush();
    return Base64.getEncoder().encodeToString(bos.toByteArray());
  }
}

static Object convertStringToObject(String str) throws IOException, ClassNotFoundException {
  try (ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(str.getBytes())); ObjectInput in = new ObjectInputStream(bis)){
    return in.readObject();
  }
}


As stated previously, a complete working example and additional details can be found in Redis L2 Cache Integration Using Jedis.

Performance Considerations

By all accounts, Jedis is a pretty snappy Redis client. However, it relies solely on Redis. A major performance consideration must therefore be the latency between Redis and the Jedis client. 

Prior to fetching from the database, Hibernate will first check its session cache (L1), then its L2 cache, then it will query the database. In an extreme situation, excessive latency between Redis and the Jedis client could make database queries even slower than they were prior to using L2 cache. A good design must target very low latency between Redis and the Jedis client, keeping them in close proximity to each other.

Conclusions

Redis's speed and simplicity make it a good candidate to support Hibernate's L2 caching functionality. Unfortunately, available integration options are limited. However, Hibernate does make it relatively simple to implement Redis L2 caching using the popular Java Redis client, Jedis. By leveraging Jedis and Hibernate's L2 cache support,  simple and performant (and free) Redis L2 caching is possible.

Cache (computing) Redis (company) Hibernate Database

Opinions expressed by DZone contributors are their own.

Related

  • Implement Hibernate Second-Level Cache With NCache
  • Scaling Databases With EclipseLink And Redis
  • Redis Is Not Just a Cache
  • Scaling in Practice: Caching and Rate-Limiting With Redis and Next.js

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!