Skip to content

Commit 022b3f9

Browse files
committed
48031: Add support for multiple cache types (backends)
1 parent 99a703b commit 022b3f9

File tree

11 files changed

+579
-8
lines changed

11 files changed

+579
-8
lines changed

.github/native-tests.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@
9292
},
9393
{
9494
"category": "Cache",
95-
"timeout": 75,
96-
"test-modules": "infinispan-cache-jpa, infinispan-client, cache, redis-cache, redis-devservices, infinispan-cache",
95+
"timeout": 80,
96+
"test-modules": "infinispan-cache-jpa, infinispan-client, cache, redis-cache, redis-devservices, infinispan-cache, cache-multiple-backends",
9797
"os-name": "ubuntu-latest"
9898
},
9999
{

docs/src/main/asciidoc/cache.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,21 @@ The `setExpireAfterAccess`, `setExpireAfterWrite` and `setMaximumSize` methods f
771771
This extension uses https://github.com/ben-manes/caffeine[Caffeine] as its underlying caching provider.
772772
Caffeine is a high performance, near optimal caching library.
773773

774+
== Multiple Backends Simultaneously
775+
776+
This extension can be configured to use multiple backends simultaneously, allowing different caches to leverage either in-memory providers (like Caffeine) or distributed providers (like Redis) as needed, for example:
777+
778+
[source,properties]
779+
----
780+
# default cache backend
781+
quarkus.cache.type=caffeine
782+
783+
# change the cache backend for "weather-cache" to Redis.
784+
quarkus.cache.weather-cache.type=redis
785+
----
786+
787+
NOTE: The syntax for the property name is `quarkus.cache."cache-name".type`. If no explicit backend is defined for a cache, the default backend is used. For details on configuring a specific backend, refer to its guide.
788+
774789
=== Caffeine configuration properties
775790

776791
Each of the Caffeine caches backing up the Quarkus application data caching extension can be configured using the following

extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheBuildConfig.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
import static io.quarkus.runtime.annotations.ConfigPhase.BUILD_AND_RUN_TIME_FIXED;
44

5+
import java.util.Map;
6+
7+
import io.quarkus.runtime.annotations.ConfigDocMapKey;
8+
import io.quarkus.runtime.annotations.ConfigDocSection;
9+
import io.quarkus.runtime.annotations.ConfigGroup;
510
import io.quarkus.runtime.annotations.ConfigRoot;
611
import io.smallrye.config.ConfigMapping;
712
import io.smallrye.config.WithDefault;
13+
import io.smallrye.config.WithParentName;
814

915
@ConfigRoot(phase = BUILD_AND_RUN_TIME_FIXED)
1016
@ConfigMapping(prefix = "quarkus.cache")
@@ -13,8 +19,29 @@ public interface CacheBuildConfig {
1319
String CAFFEINE_CACHE_TYPE = "caffeine";
1420

1521
/**
16-
* Cache type.
22+
* Default cache type (backend provider). If no explicit type is defined for a cache, this type will be used.
1723
*/
1824
@WithDefault(CAFFEINE_CACHE_TYPE)
1925
String type();
26+
27+
/**
28+
* Configuration that allows customizing cache names to use a different type.
29+
*/
30+
@ConfigDocMapKey("cache-name")
31+
@ConfigDocSection
32+
@WithParentName
33+
Map<String, CacheTypeBuildConfig> cacheTypeByName();
34+
35+
/**
36+
* Configuration that allows customizing cache names to use a different type.
37+
*/
38+
@ConfigGroup
39+
interface CacheTypeBuildConfig {
40+
41+
/**
42+
* Cache type to be used for the cache name.
43+
*/
44+
@WithDefault(CAFFEINE_CACHE_TYPE)
45+
String type();
46+
}
2047
}

extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheManagerRecorder.java

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import static io.quarkus.cache.runtime.CacheBuildConfig.CAFFEINE_CACHE_TYPE;
44

55
import java.util.Collection;
6+
import java.util.HashMap;
7+
import java.util.HashSet;
8+
import java.util.Map;
69
import java.util.Set;
710
import java.util.function.Supplier;
811

912
import jakarta.enterprise.inject.spi.DeploymentException;
1013

14+
import io.quarkus.cache.Cache;
1115
import io.quarkus.cache.CacheManager;
1216
import io.quarkus.cache.CacheManagerInfo;
1317
import io.quarkus.cache.runtime.caffeine.CaffeineCacheManagerBuilder;
@@ -26,13 +30,15 @@ public CacheManagerRecorder(CacheBuildConfig cacheBuildConfig, RuntimeValue<Cach
2630
this.cacheConfigRV = cacheConfigRV;
2731
}
2832

29-
public Supplier<CacheManager> resolveCacheInfo(Collection<CacheManagerInfo> infos, Set<String> cacheNames,
33+
private CacheManagerInfo.Context createContextForCacheType(
34+
String cacheType,
3035
boolean micrometerMetricsEnabled) {
31-
CacheConfig cacheConfig = cacheConfigRV.getValue();
32-
CacheManagerInfo.Context context = new CacheManagerInfo.Context() {
36+
return new CacheManagerInfo.Context() {
37+
private final Set<String> cacheNames = new HashSet<>();
38+
3339
@Override
3440
public boolean cacheEnabled() {
35-
return cacheConfig.enabled();
41+
return cacheConfigRV.getValue().enabled();
3642
}
3743

3844
@Override
@@ -42,14 +48,36 @@ public Metrics metrics() {
4248

4349
@Override
4450
public String cacheType() {
45-
return cacheBuildConfig.type();
51+
return cacheType;
4652
}
4753

4854
@Override
4955
public Set<String> cacheNames() {
5056
return cacheNames;
5157
}
5258
};
59+
}
60+
61+
private Map<String, String> mapCacheTypeByCacheName(Set<String> cacheNames) {
62+
Map<String, String> cacheTypeByName = new HashMap<>(cacheNames.size());
63+
for (String cacheName : cacheNames) {
64+
CacheBuildConfig.CacheTypeBuildConfig cacheTypeBuildConfig = cacheBuildConfig.cacheTypeByName().get(cacheName);
65+
// check if the cache type is defined for this cache name "quarkus.cache.<cache-name>.type"
66+
if (cacheTypeBuildConfig != null &&
67+
cacheTypeBuildConfig.type() != null &&
68+
!cacheTypeBuildConfig.type().isEmpty()) {
69+
cacheTypeByName.put(cacheName, cacheTypeBuildConfig.type());
70+
} else {
71+
// if not, use the default cache type defined "quarkus.cache.type"
72+
cacheTypeByName.put(cacheName, cacheBuildConfig.type());
73+
}
74+
}
75+
return cacheTypeByName;
76+
}
77+
78+
private Supplier<CacheManager> findSupplierForType(
79+
CacheManagerInfo.Context context,
80+
Collection<CacheManagerInfo> infos) {
5381
for (CacheManagerInfo info : infos) {
5482
if (info.supports(context)) {
5583
return info.get(context);
@@ -58,6 +86,71 @@ public Set<String> cacheNames() {
5886
throw new DeploymentException("Unknown cache type: " + context.cacheType());
5987
}
6088

89+
private Map<String, Supplier<CacheManager>> createCacheSupplierByCacheType(
90+
Collection<CacheManagerInfo> infos,
91+
Set<String> cacheNames,
92+
boolean micrometerMetricsEnabled) {
93+
94+
Map<String, String> cacheTypeByCacheName = mapCacheTypeByCacheName(cacheNames);
95+
96+
// create one context per cache type with their corresponding list of cache names
97+
Map<String, CacheManagerInfo.Context> contextByCacheType = new HashMap<>();
98+
for (String cacheName : cacheNames) {
99+
contextByCacheType.computeIfAbsent(
100+
cacheTypeByCacheName.get(cacheName),
101+
cacheType -> this.createContextForCacheType(cacheType, micrometerMetricsEnabled))
102+
.cacheNames()
103+
.add(cacheName);
104+
}
105+
106+
// suppliers grouped by cache type
107+
Map<String, Supplier<CacheManager>> suppliersByType = new HashMap<>();
108+
for (Map.Entry<String, CacheManagerInfo.Context> entry : contextByCacheType.entrySet()) {
109+
String cacheType = entry.getKey();
110+
if (!suppliersByType.containsKey(cacheType)) {
111+
suppliersByType.put(cacheType, findSupplierForType(entry.getValue(), infos));
112+
}
113+
}
114+
115+
return suppliersByType;
116+
}
117+
118+
public Supplier<CacheManager> resolveCacheInfo(
119+
Collection<CacheManagerInfo> infos, Set<String> cacheNames,
120+
boolean micrometerMetricsEnabled) {
121+
122+
Map<String, Supplier<CacheManager>> suppliersByType = createCacheSupplierByCacheType(
123+
infos,
124+
cacheNames,
125+
micrometerMetricsEnabled);
126+
127+
return new Supplier<CacheManager>() {
128+
@Override
129+
public CacheManager get() {
130+
if (suppliersByType.size() == 1) {
131+
// if there is only one cache type, return the corresponding cache implementation
132+
return suppliersByType.values().iterator().next().get();
133+
}
134+
// if there are multiple cache types, return a CacheManager implementation that aggregates all caches
135+
136+
// get the cache manager implementation by cache type of each supplier
137+
Map<String, CacheManager> cacheImplByCacheType = new HashMap<>();
138+
for (Map.Entry<String, Supplier<CacheManager>> entry : suppliersByType.entrySet()) {
139+
cacheImplByCacheType.put(entry.getKey(), entry.getValue().get());
140+
}
141+
142+
// put all cache implementations together in a single map indexed by cache name
143+
Map<String, Cache> allCaches = new HashMap<>();
144+
for (CacheManager cacheManager : cacheImplByCacheType.values()) {
145+
for (String cacheName : cacheManager.getCacheNames()) {
146+
cacheManager.getCache(cacheName).ifPresent(cache -> allCaches.put(cacheName, cache));
147+
}
148+
}
149+
return new CacheManagerImpl(allCaches);
150+
}
151+
};
152+
}
153+
61154
public CacheManagerInfo noOpCacheManagerInfo() {
62155
return new CacheManagerInfo() {
63156
@Override

0 commit comments

Comments
 (0)