Skip to content

Commit 5859fa2

Browse files
committed
[release] bump lib-cache-tiered-redis@1.0.0
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
1 parent bfe363e commit 5859fa2

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

lib-cache-tiered-redis/README.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# lib-cache-tiered-redis
2+
3+
Two-tier caching implementation with Caffeine (L1) and Redis (L2) for distributed caching.
4+
5+
## Installation
6+
7+
Add this dependency to your `build.gradle`:
8+
9+
```gradle
10+
dependencies {
11+
implementation 'io.seqera:lib-cache-tiered-redis:1.0.0'
12+
}
13+
```
14+
15+
## Overview
16+
17+
`lib-cache-tiered-redis` provides a two-level caching strategy that combines the speed of local in-memory caching with the consistency of distributed caching:
18+
19+
- **L1 Cache (Caffeine)**: Fast, in-memory local cache for single-instance performance
20+
- **L2 Cache (Redis)**: Distributed cache shared across multiple instances for consistency
21+
22+
## Features
23+
24+
- 🚀 **Fast Local Access**: L1 cache provides microsecond-level response times
25+
- 🌐 **Distributed Consistency**: L2 cache enables cache sharing across instances
26+
- ⏱️ **TTL Support**: Automatic expiration at both cache levels
27+
- 🔒 **Thread-Safe**: Per-key locking ensures safe concurrent access
28+
- 📦 **MoshiSerializable**: Seamless JSON serialization for cache entries
29+
- 🔧 **Configurable**: Customize cache sizes and prefixes per implementation
30+
31+
## Usage
32+
33+
### Basic Implementation
34+
35+
Extend `AbstractTieredCache` to create your own cache:
36+
37+
```java
38+
@Singleton
39+
public class UserCache extends AbstractTieredCache<String, User> {
40+
41+
public UserCache(L2TieredCache<String, String> l2Cache, MoshiEncodeStrategy<Entry> encoder) {
42+
super(l2Cache, encoder);
43+
}
44+
45+
@Override
46+
protected String getPrefix() {
47+
return "users:v1";
48+
}
49+
50+
@Override
51+
protected int getMaxSize() {
52+
return 10_000;
53+
}
54+
55+
@Override
56+
protected String getName() {
57+
return "user-cache";
58+
}
59+
}
60+
```
61+
62+
### Using Tiered Keys
63+
64+
For complex keys, implement the `TieredKey` interface:
65+
66+
```java
67+
public class UserCacheKey implements TieredKey {
68+
private final String tenantId;
69+
private final String userId;
70+
71+
public UserCacheKey(String tenantId, String userId) {
72+
this.tenantId = tenantId;
73+
this.userId = userId;
74+
}
75+
76+
@Override
77+
public String stableHash() {
78+
return tenantId + ":" + userId;
79+
}
80+
}
81+
```
82+
83+
### Cache Operations
84+
85+
```java
86+
// Simple get/put
87+
cache.put("user123", user, Duration.ofHours(1));
88+
User user = cache.get("user123");
89+
90+
// Get with loader function
91+
User user = cache.getOrCompute("user123",
92+
key -> userService.loadUser(key),
93+
Duration.ofHours(1)
94+
);
95+
96+
// Get with loader returning value and TTL
97+
User user = cache.getOrCompute("user123",
98+
key -> {
99+
User loaded = userService.loadUser(key);
100+
Duration ttl = loaded.isPremium()
101+
? Duration.ofHours(24)
102+
: Duration.ofHours(1);
103+
return new Pair<>(loaded, ttl);
104+
}
105+
);
106+
107+
// Invalidate L1 cache
108+
cache.invalidateAll();
109+
```
110+
111+
### Encoder Configuration
112+
113+
Create a Moshi encoder for your cache entries:
114+
115+
```java
116+
@Singleton
117+
public class UserCacheEncoder extends MoshiEncodeStrategy<AbstractTieredCache.Entry> {
118+
119+
public UserCacheEncoder() {
120+
super(createFactory());
121+
}
122+
123+
private static JsonAdapter.Factory createFactory() {
124+
return PolymorphicJsonAdapterFactory
125+
.of(MoshiSerializable.class, "@type")
126+
.withSubtype(AbstractTieredCache.Entry.class, "Entry")
127+
.withSubtype(User.class, "User");
128+
}
129+
}
130+
```
131+
132+
## Configuration
133+
134+
### Enable Redis
135+
136+
The Redis L2 cache is automatically enabled when the `redis.uri` property is configured:
137+
138+
```yaml
139+
redis:
140+
uri: redis://localhost:6379
141+
```
142+
143+
### Disable L2 Cache (Development)
144+
145+
For local development or testing without Redis, simply omit the `redis.uri` property. The cache will continue to work with only the L1 tier:
146+
147+
```java
148+
// Works without Redis - L1 only
149+
UserCache cache = new UserCache(null, encoder);
150+
```
151+
152+
## How It Works
153+
154+
1. **Cache Hit Path**:
155+
- Check L1 (Caffeine) → if found, return immediately
156+
- On L1 miss, check L2 (Redis) → if found, hydrate L1 and return
157+
- On L2 miss, invoke loader function (if provided)
158+
159+
2. **Cache Write Path**:
160+
- Store in both L1 and L2 with TTL
161+
- L1 expires based on local timestamp
162+
- L2 expires based on Redis TTL
163+
164+
3. **Thread Safety**:
165+
- Per-key locks prevent race conditions
166+
- Multiple keys can be accessed concurrently
167+
- Same key access is serialized
168+
169+
## Best Practices
170+
171+
- ✅ Use appropriate TTLs based on data volatility
172+
- ✅ Set L1 cache size based on available heap memory
173+
- ✅ Use meaningful cache prefixes to avoid key conflicts
174+
- ✅ Implement `TieredKey` for complex key types
175+
- ✅ Handle null values appropriately in loader functions
176+
- ⚠️ Remember that strong consistency is not guaranteed across instances
177+
- ⚠️ L1 invalidation only affects the local instance
178+
179+
## Configuration
180+
181+
### Enabling Redis Caching
182+
183+
The `RedisL2TieredCache` bean is conditionally loaded based on the presence of a `RedisActivator` bean. To enable Redis caching:
184+
185+
1. **For Micronaut applications**, provide a `RedisActivator` bean:
186+
187+
```java
188+
@Singleton
189+
@Requires(property = "redis.uri")
190+
public class AppRedisActivator implements RedisActivator {
191+
}
192+
```
193+
194+
2. **For testing**, use the `@Requires(env=['redis'])` pattern:
195+
196+
```groovy
197+
@Singleton
198+
@Requires(env=['redis'])
199+
class TestRedisActivation implements RedisActivator {
200+
}
201+
```
202+
203+
The `RedisActivator` marker interface provides a clean way to conditionally enable Redis-based components only when Redis infrastructure is available.
204+
205+
## Dependencies
206+
207+
- Caffeine 3.x (L1 cache)
208+
- Jedis 5.x (Redis client)
209+
- Micronaut Context (dependency injection)
210+
- lib-serde-moshi (JSON serialization)
211+
- lib-activator (conditional bean activation)
212+
213+
## License
214+
215+
Apache License 2.0

lib-cache-tiered-redis/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.0.0

0 commit comments

Comments
 (0)