What is Redis client-side caching?

High-performance applications must use every possible method to make things faster and more responsive. One technique Redis uses is server-assisted, client-side caching. This approach efficiently uses available memory on application servers, which are typically distinct from the database nodes running Redis.

So, instead of an application retrieving frequently requested data from Redis every time, Redis client-side caching stores a subset of the data directly in the application server's memory. Since the time needed to access the application server's local memory is orders of magnitude faster than retrieving the same data from a network resource like the Redis service, the app's users get the data much faster.

The benefits of client-side caching for Java

Java developers, in particular, benefit from Redis client-side caching because they can integrate caching directly into their Java applications instead of coding custom solutions.

The speed improvements gained with client-side caching are important for developers because Java apps often depend on low latency. For example, the performance of web services, eCommerce platforms, and real-time data processing solutions would suffer if bottlenecks slow down the flow of data. Client-side caching ensures that frequently requested data is always readily available, and end users then enjoy significantly faster response times.

Client-side caching is also essential for scalability. As a Java application grows, client-side caching helps alleviate the load on backend infrastructure, including data resources like Redis. Caching data on the application server side allows Java developers to reduce expensive queries and networking requests, which allows apps to scale more quickly.

Developers who use Redis are likely familiar with popular Java frameworks like Spring, Hibernate, and Micronaut, all of which make implementing client-side caching easy. This is especially true if developers already have these frameworks in their application stack. However, Redis doesn't natively support Java. That's why developers turn to the Redisson framework and its broad support for local caching.

Client-side caching with Redis on Java using Redisson

Redisson refers to client-side caching as a "local cache." Although the terms are different, the concept is the same. What sets Redisson apart is how it implements the concept, which Java developers will find familiar.

Maps in Java are representations of key-value pair mappings. For Java developers, maps are one of the easiest ways to implement client-side caching. Redisson's RLocalCachedMap interface extends Java's ConcurrentMap interface to support Redis client-side caching. Developers can easily configure a number of cache features with RLocalCachedMap:

  • Maximum cache size
  • Time-to-live (TTL) per cache entry
  • Maximum idle time per cache entry
  • Eviction policies for cache entries
  • Synchronization strategies for cache changes

With these features, Redisson empowers Java developers to create a local cache that enables applications to execute read operations up to 45 times faster than normal. Here's a look at the many local caching options and integrations Redisson provides.

Spring Cache client-side caching with Redis

Redisson provides a Spring Cache implementation via various Spring Cache managers with two critical features: local cache and data partitioning. The latter is important because although a Map object is cluster-compatible, its content isn't scaled or partitioned across multiple Redis master nodes in a cluster. Data partitioning allows you to scale available memory, read/write operations, and entry eviction processes for individual Map instances in a cluster.

Spring Cache configuration can be done through YAML config files, with support for several local cache options, including:

  • storeCacheMiss: Defines whether to store a cache miss into the local cache. The default value is false.
  • storeMode: This defines the store mode of cache data. The available options are LOCALCACHE (which stores data in the local cache only) and LOCALCACHE_REDIS (which stores data in both Redis and the local cache).
  • cacheProvider: Defines the Cache provider used as the local cache store. Available options are REDISSON (which uses Redisson's implementation) and CAFFEINE (which uses the standard Redis Caffeine implementation).
  • evictionPolicy: This defines the local cache eviction policy. The options are LFU (evicts least frequently used items), LRU (evicts least recently used items), SOFT (uses weak references), WEAK (uses soft references), and NONE (no eviction).
  • cacheSize: If cache size is 0 then the local cache is unbounded.

Hibernate client-side caching with Redis

Hibernate is a popular mapping framework for Java that saves developers from many manual data processing tasks. It uses a multi-level caching scheme. The first level cache is only available for the current session, and the second persists across sessions. The third level is known as a query cache, which stores the results of a particular database query.

Redisson provides various Hibernate CacheFactories, including those with support for local caching:

  • RedissonRegionFactory implements a basic Hibernate cache.
  • RedissonLocalCachedRegionFactory (Redisson PRO only) implements a Hibernate cache with support for local caching.

Redisson's Hibernate support implements all four Hibernate cache strategies:

  • READ_ONLY: Only data that will not change once inside the cache is used.
  • NONSTRICT_READ_WRITE: The cache is updated after a transaction modifies an entity in the database. It cannot guarantee strong consistency but can guarantee eventual consistency.
  • READ_WRITE: Guarantees strong consistency using "soft" locks that maintain control of the entity until the transaction is complete.
  • TRANSACTIONAL: Ensures data integrity by using distributed XA transactions. Updates are guaranteed to be either committed or rolled back to both Redis and the local cache.

See this gist for a complete Hibernate local cache configuration.

MyBatis client-side caching with Redis

MyBatis is a Java persistence framework that allows Java developers to map methods to database queries, such as SQL statements or stored procedures. While MyBatis includes a basic Redis Cache extension, Redisson's MyBatis support offers data partitioning in cluster mode, making it the only choice for distributed applications that use MyBatis client-side caching with Redis.

Configurations can be made per cache instance, including these settings:

  • timeToLive defines the time to live per cache entry.
  • maxIdleTime defines the max idle time per cache entry.
  • maxSize defines the max size of entries amount stored in Redis.
  • localCacheProvider is a cache provider used as a local cache store. REDISSON and CAFFEINE providers are available. Default value: REDISSON.
  • localCacheEvictionPolicy is the local cache eviction policy. LFU, LRU, SOFT, WEAK, and NONE eviction policies are available.
  • localCacheSize sets the local cache size. If the size is 0, then the local cache is unbounded.

See this gist for configuration examples.

JCache client-side caching with Redis

JCache is the standard caching API in Java. Developers use JCache to temporarily cache Java objects using the CachingProvider interface and manage the cache with the CachingManager interface. Here is a sample YAML configuration that shows all the available options for Redisson's implementation of the JCache API for Redis:

// Defines whether to store a cache miss into the local cache.
// Default value is false.
.storeCacheMiss(false);

// Defines store mode of cache data.
// Follow options are available:
// LOCALCACHE - store data in local cache only and use Redis only for data update/invalidation.
// LOCALCACHE_REDIS - store data in both Redis and local cache.
.storeMode(StoreMode.LOCALCACHE_REDIS)

// Defines Cache provider used as a local cache store.
// Follow options are available:
// REDISSON - uses Redisson own implementation
// CAFFEINE - uses Caffeine implementation
.cacheProvider(CacheProvider.REDISSON)

// Defines local cache eviction policy.
// Follow options are available:
// LFU - Counts how often an item was requested. Those that are used least often are discarded first.
// LRU - Discards the least recently used items first
// SOFT - Uses weak references; entries are removed by GC
// WEAK - Uses soft references; entries are removed by GC
// NONE - No eviction
.evictionPolicy(EvictionPolicy.NONE)

// If the cache size is 0, the local cache is unbounded.
.cacheSize(1000)

// Used to load missed updates during any connection failures to Redis. 
// Since local cache updates can't be done in the absence of a connection to Redis, 
// Follow reconnection strategies are available:
// CLEAR - Clear the local cache if the map instance has been disconnected for a while.
// LOAD - Store the invalidated entry hash in the invalidation log for 10 minutes
//        Cache keys for stored invalidated entry hashes will be removed 
//        if LocalCachedMap instance has been disconnected less than 10 minutes
//        or the whole cache will be cleaned otherwise.
// NONE - Default. No reconnection handling
.reconnectionStrategy(ReconnectionStrategy.NONE)

// Used to synchronize local cache changes.
// Follow sync strategies are available:
// INVALIDATE - Default. Invalidate cache entry across all LocalCachedMap instances on map entry change
// UPDATE - Insert/update cache entry across all LocalCachedMap instances on map entry change
// NONE - No synchronizations on map changes
.syncStrategy(SyncStrategy.INVALIDATE)

// time to live for each map entry in the local cache
.timeToLive(10000)
// or
.timeToLive(10, TimeUnit.SECONDS)

// max idle time for each map entry in the local cache
.maxIdle(10000)
// or
.maxIdle(10, TimeUnit.SECONDS);

A simple local cache config example—with the cache eviction policy, TTL, maximum idle time, and maximum cache size—might look like this:


LocalCacheConfiguration config = new LocalCacheConfiguration<>();
.setEvictionPolicy(EvictionPolicy.LFU)
.setTimeToLive(48, TimeUnit.MINUTES)
.setMaxIdle(24, TimeUnit.MINUTES);
.setCacheSize(1000);
       
CacheManager manager = Caching.getCachingProvider().getCacheManager();
Cache cache = manager.createCache("myCache", config);

Micronaut Cache client caching with Redis

Micronaut is an open-source Java virtual machine (JVM) framework for building apps with service-based architectures. It offers caching annotations, and thanks to its low memory consumption and nimble performance, Micronaut is often used as a local cache.

Redisson makes it easy to integrate Redis with Micronaut. See the Redisson-Micronaut documentation for how to add the dependency to a Java project. After that, setting up a local cache is as simple as defining the Redisson instance:

@Inject
private RedissonClient redisson;


Then, you define the cache:


@Singleton 
@CacheConfig("my-cache1") 
public class CarsService {


@Cacheable
public List listAll() {
// ...
}
    
@CachePut(parameters = {"type"}) 
public List addCar(String type, String description) {
// ...
}
    
@CacheInvalidate(parameters = {"type"}) 
public void removeCar(String type, String description) {
// ...
}    
}

See this gist for a configuration with two caches using different features and settings.

Quarkus Cache client-side caching with Redis

Quarkus is a full-stack framework for high-performance, containerized applications. Its default caching mechanism is the Java-native Caffeine, which doesn't support clusters or distributed applications. But with Redisson, Quarkus can serve several cache functions, including acting as a local cache.

After adding the redisson-quarkus dependency to a Java project, a simple configuration establishes a Quarkus-based local cache, as in this example:

quarkus.cache.type=redisson
# possible values for localcache: localcache, localcache_v2, clustered_localcache
quarkus.cache.redisson.implementation=localcache


# Default configuration for all caches
quarkus.cache.redisson.expire-after-write=5s
quarkus.cache.redisson.expire-after-access=1s
quarkus.cache.redisson.cache-size=100
quarkus.cache.redisson.eviction-policy=LFU
quarkus.cache.redisson.time-to-live=10s
quarkus.cache.redisson.max-idle=5s


# Configuration for `sampleCache` cache
quarkus.cache.redisson.sampleCache.expire-after-write=100s
quarkus.cache.redisson.sampleCache.expire-after-access=10s
quarkus.cache.redisson.sampleCache.cache-size=100
quarkus.cache.redisson.sampleCache.eviction-policy=LFU
quarkus.cache.redisson.sampleCache.time-to-live=10s
quarkus.cache.redisson.sampleCache.max-idle=5s

JSON Store client-side caching with Redis

JSON Store is a fast, document-oriented storage system perfect for Java apps that work with and manage documents. Redisson implements JSON Store with a local cache option, making it available to distributed applications.

This configuration displays the options available during the creation of a JSON Store local cache:

// Defines codec used for key
.keyCodec(codec)

// Defines codec used for JSON value
.valueCodec(codec)

// Defines whether to store a cache miss into the local cache.
// Default value is false.
.storeCacheMiss(false);

// Defines store mode of cache data.
// Follow options are available:
// LOCALCACHE - store data in local cache only and use Redis only for data update/invalidation.
// LOCALCACHE_REDIS - store data in both Redis and local cache.
.storeMode(StoreMode.LOCALCACHE_REDIS)

// Defines Cache provider used as a local cache store.
// Follow options are available:
// REDISSON - uses Redisson own implementation
// CAFFEINE - uses Caffeine implementation
.cacheProvider(CacheProvider.REDISSON)

// Defines local cache eviction policy.
// Follow options are available:
// LFU - Counts how often an item was requested. Those that are used least often are discarded first.
// LRU - Discards the least recently used items first
// SOFT - Uses soft references; entries are removed by GC
// WEAK - Uses weak references; entries are removed by GC
// NONE - No eviction
.evictionPolicy(EvictionPolicy.NONE)

// If the cache size is 0, the local cache is unbounded.
.cacheSize(1000)

// Defines strategy for loading missed local cache updates after Redis connection failure.
//
// Follow reconnection strategies are available:
// CLEAR - Clear the local cache if the map instance has been disconnected for a while.
// NONE - Default. No reconnection handling
.reconnectionStrategy(ReconnectionStrategy.NONE)

// Defines local cache synchronization strategy.
//
// Follow sync strategies are available:
// INVALIDATE - Default. Invalidate cache entry across all RLocalCachedJsonStore instances on map entry change
// UPDATE - Insert/update cache entry across all RLocalCachedJsonStore instances on map entry change
// NONE - No synchronizations on map changes
.syncStrategy(SyncStrategy.INVALIDATE)

// time to live for each entry in the local cache
.timeToLive(Duration.ofSeconds(10))

// max idle time for each entry in the local cache
.maxIdle(Duration.ofSeconds(10));

// Defines how to listen to the expired event sent by Redis upon this instance deletion
//
// Follow expiration policies are available:
// DONT_SUBSCRIBE - Don't subscribe an expired event
// SUBSCRIBE_WITH_KEYEVENT_PATTERN - Subscribe on expire event using __keyevent@*:expired pattern
// SUBSCRIBE_WITH_KEYSPACE_CHANNEL - Subscribe on expire event using __keyspace@N__:name channel
.expirationEventPolicy(ExpirationEventPolicy.SUBSCRIBE_WITH_KEYEVENT_PATTERN)

To learn more about the Redis client-side caching options offered by Redisson and Redisson PRO, visit the Redisson website today.

Similar terms