spring-boot-multilevel-cache-starter
spring-boot-multilevel-cache-starter copied to clipboard
Spring Boot multi-level caching with Redis and Caffeine
trafficstars
Spring Boot multi-level cache starter
Opinionated version of multi-level caching for Spring Boot with Redis as L2 (remote) cache and Caffeine as L1 (local) cache with a Circuit Breaker pattern for L2 cache calls.
This version does not allow setting most of the local cache properties in favor of managing local cache expiry by itself.
Use cases
Suitable for
- Microservices working with immutable cached entities under low latency requirements
- The goal is to not only reduce the number of calls to external service but also reduce the number of calls to Redis
Not a good fit for
- Mutable cached entities
- Entities with short time to live (< 5 minutes)
- Cases when entities in local cache must outlive entities in distributed cache
- Consider using only local cache instead
- Cases when all calls to Redis must be synchronized with distributed locks
Ideas
- Use well-known Spring primitives for implementation
- Microservices environment needs to fit the requirement of fault tolerance:
- Redis calls covered by Resilience4j Circuit Breaker which allows falling back to use local cache at the cost of increased latency and more calls to external services.
- Redis TTL behaves similar to
expireAfterWritein Caffeine which allows us to set randomized expiry time for local cache:- This is useful to ensure that local cache entries will expire earlier for a higher chance to hit Redis instead of performing external call.
- This also implicitly reduces the load on the Redis by spreading calls to it over time.
- In the case of Redis connection errors, randomized expiry and Circuit Breaker will help to mitigate thundering herd problem.
- Expiry randomization follows the rule:
(time-to-live / 2) * (1 ± ((expiry-jitter / 100) * RNG(0, 1))), for example:- If
spring.cache.multilevel.time-to-liveis1h - And
spring.cache.multilevel.local.expiry-jitteris50(percents) - Then entries in local cache will expire in approximately
15-45m:
- If
(1h / 2) * (1 ± ((50 / 100) * RNG(0, 1))) ->
30m * (1 ± MAXRNG(0.5)) ->
30m * RANGE(0.5, 1.5) ->
15-45m
Usage
Maven
<dependency>
<groupId>io.github.suppierk</groupId>
<artifactId>spring-boot-multilevel-cache-starter</artifactId>
<version>3.2.5.2</version>
</dependency>
Gradle
implementation 'io.github.suppierk:spring-boot-multilevel-cache-starter:3.2.5.2'
Default configuration
spring:
data:
redis:
host: ${HOST:localhost}
port: ${PORT:6379}
cache:
type: redis
# These properties are custom
multilevel:
# Redis properties
time-to-live: 1h
use-key-prefix: false
key-prefix: ""
topic: "cache:multilevel:topic"
# Local Caffeine cache properties
local:
max-size: 2000
expiry-jitter: 50
expiration-mode: after-create
# other valid values for expiration-mode: after-update, after-read
# Resilience4j Circuit Breaker properties for Redis
circuit-breaker:
failure-rate-threshold: 25
slow-call-rate-threshold: 25
slow-call-duration-threshold: 250ms
sliding-window-type: count_based
permitted-number-of-calls-in-half-open-state: 20
max-wait-duration-in-half-open-state: 5s
sliding-window-size: 40
minimum-number-of-calls: 10
wait-duration-in-open-state: 2500ms