Redis Cache
Spring Data Redis provides an implementation of Spring Framework’s Cache Abstraction in the org.springframework.data.redis.cache
package.
To use Redis as a backing implementation, add RedisCacheManager
to your configuration, as follows:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
RedisCacheManager
behavior can be configured with RedisCacheManager.RedisCacheManagerBuilder
, letting you set the default RedisCacheManager
, transaction behavior, and predefined caches.
RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
.transactionAware()
.withInitialCacheConfigurations(Collections.singletonMap("predefined",
RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
.build();
As shown in the preceding example, RedisCacheManager
allows custom configuration on a per-cache basis.
The behavior of RedisCache
created by RedisCacheManager
is defined with RedisCacheConfiguration
.
The configuration lets you set key expiration times, prefixes, and RedisSerializer
implementations for converting to and from the binary storage format, as shown in the following example:
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(1))
.disableCachingNullValues();
RedisCacheManager
defaults to a lock-free RedisCacheWriter
for reading and writing binary values.
Lock-free caching improves throughput.
The lack of entry locking can lead to overlapping, non-atomic commands for the Cache
putIfAbsent
and clean
operations, as those require multiple commands to be sent to Redis.
The locking counterpart prevents command overlap by setting an explicit lock key and checking against presence of this key, which leads to additional requests and potential command wait times.
Locking applies on the cache level, not per cache entry.
It is possible to opt in to the locking behavior as follows:
RedisCacheManager cacheManager = RedisCacheManager
.build(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory))
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
...
By default, any key
for a cache entry gets prefixed with the actual cache name followed by two colons (::
).
This behavior can be changed to a static as well as a computed prefix.
The following example shows how to set a static prefix:
// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixCacheNameWith("(͡° ᴥ ͡°)");
The following example shows how to set a computed prefix:
// computed key prefix
RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);
The cache implementation defaults to use KEYS
and DEL
to clear the cache. KEYS
can cause performance issues with large keyspaces.
Therefore, the default RedisCacheWriter
can be created with a BatchStrategy
to switch to a SCAN
-based batch strategy.
The SCAN
strategy requires a batch size to avoid excessive Redis command round trips:
RedisCacheManager cacheManager = RedisCacheManager
.build(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
...
The |
The following table lists the default settings for RedisCacheManager
:
Setting | Value |
---|---|
Cache Writer |
Non-locking, |
Cache Configuration |
|
Initial Caches |
None |
Transaction Aware |
No |
The following table lists the default settings for RedisCacheConfiguration
:
Key Expiration | None |
---|---|
Cache |
Yes |
Prefix Keys |
Yes |
Default Prefix |
The actual cache name |
Key Serializer |
|
Value Serializer |
|
Conversion Service |
|
By default |
Redis Cache Expiration
The implementation of time-to-idle (TTI) as well as time-to-live (TTL) varies in definition and behavior even across different data stores.
In general:
-
time-to-live (TTL) expiration - TTL is only set and reset by a create or update data access operation. As long as the entry is written before the TTL expiration timeout, including on creation, an entry’s timeout will reset to the configured duration of the TTL expiration timeout. For example, if the TTL expiration timeout is set to 5 minutes, then the timeout will be set to 5 minutes on entry creation and reset to 5 minutes anytime the entry is updated thereafter and before the 5-minute interval expires. If no update occurs within 5 minutes, even if the entry was read several times, or even just read once during the 5-minute interval, the entry will still expire. The entry must be written to prevent the entry from expiring when declaring a TTL expiration policy.
-
time-to-idle (TTI) expiration - TTI is reset anytime the entry is also read as well as for entry updates, and is effectively and extension to the TTL expiration policy.
Some data stores expire an entry when TTL is configured no matter what type of data access operation occurs on the entry (reads, writes, or otherwise). After the set, configured TTL expiration timeout, the entry is evicted from the data store regardless. Eviction actions (for example: destroy, invalidate, overflow-to-disk (for persistent stores), etc.) are data store specific. |
Time-To-Live (TTL) Expiration
Spring Data Redis’s Cache
implementation supports time-to-live (TTL) expiration on cache entries.
Users can either configure the TTL expiration timeout with a fixed Duration
or a dynamically computed Duration
per cache entry by supplying an implementation of the new RedisCacheWriter.TtlFunction
interface.
The |
If all cache entries should expire after a set duration of time, then simply configure a TTL expiration timeout with a fixed Duration
, as follows:
RedisCacheConfiguration fiveMinuteTtlExpirationDefaults =
RedisCacheConfiguration.defaultCacheConfig().enableTtl(Duration.ofMinutes(5));
However, if the TTL expiration timeout should vary by cache entry, then you must provide a custom implementation of the RedisCacheWriter.TtlFunction
interface:
enum MyCustomTtlFunction implements TtlFunction {
INSTANCE;
@Override
public Duration getTimeToLive(Object key, @Nullable Object value) {
// compute a TTL expiration timeout (Duration) based on the cache entry key and/or value
}
}
Under-the-hood, a fixed |
Then, you can either configure the fixed Duration
or the dynamic, per-cache entry Duration
TTL expiration on a global basis using:
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
.build();
Or, alternatively:
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(MyCustomTtlFunction.INSTANCE);
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaults)
.build();
Of course, you can combine both global and per-cache configuration using:
RedisCacheConfiguration predefined = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(MyCustomTtlFunction.INSTANCE);
Map<String, RedisCacheConfiguration> initialCaches = Collections.singletonMap("predefined", predefined);
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(fiveMinuteTtlExpirationDefaults)
.withInitialCacheConfigurations(initialCaches)
.build();
Time-To-Idle (TTI) Expiration
Redis itself does not support the concept of true, time-to-idle (TTI) expiration. Still, using Spring Data Redis’s Cache implementation, it is possible to achieve time-to-idle (TTI) expiration-like behavior.
The configuration of TTI in Spring Data Redis’s Cache implementation must be explicitly enabled, that is, is opt-in.
Additionally, you must also provide TTL configuration using either a fixed Duration
or a custom implementation of the TtlFunction
interface as described above in Redis Cache Expiration.
For example:
@Configuration
@EnableCaching
class RedisConfiguration {
@Bean
RedisConnectionFactory redisConnectionFactory() {
// ...
}
@Bean
RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.enableTimeToIdle();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaults)
.build();
}
}
Because Redis servers do not implement a proper notion of TTI, then TTI can only be achieved with Redis commands accepting expiration options.
In Redis, the "expiration" is technically a time-to-live (TTL) policy.
However, TTL expiration can be passed when reading the value of a key thereby effectively resetting the TTL expiration timeout, as is now the case in Spring Data Redis’s Cache.get(key)
operation.
RedisCache.get(key)
is implemented by calling the Redis GETEX
command.
The Redis |
In order to achieve true time-to-idle (TTI) expiration-like behavior in your Spring Data Redis application, then an entry must be consistently accessed with (TTL) expiration on every read or write operation.
There are no exceptions to this rule.
If you are mixing and matching different data access patterns across your Spring Data Redis application (for example: caching, invoking operations using |