Skip to content

Commit

Permalink
Merge pull request #31865 from cescoffier/redis-cache
Browse files Browse the repository at this point in the history
Implement a backend for Quarkus Cache using Redis
  • Loading branch information
geoand authored Mar 23, 2023
2 parents 7ab6009 + 1e19dad commit 286cf2c
Show file tree
Hide file tree
Showing 54 changed files with 3,082 additions and 101 deletions.
4 changes: 2 additions & 2 deletions .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
},
{
"category": "Cache",
"timeout": 55,
"test-modules": "infinispan-cache-jpa, infinispan-client, cache",
"timeout": 60,
"test-modules": "infinispan-cache-jpa, infinispan-client, cache, redis-cache",
"os-name": "ubuntu-latest"
},
{
Expand Down
15 changes: 15 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2808,6 +2808,11 @@
<artifactId>quarkus-cache-deployment-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache-runtime-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-google-cloud-functions</artifactId>
Expand Down Expand Up @@ -5856,12 +5861,22 @@
<artifactId>quarkus-redis-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache-deployment</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Qute -->
<dependency>
Expand Down
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1786,6 +1786,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-deployment</artifactId>
Expand Down
151 changes: 151 additions & 0 deletions docs/src/main/asciidoc/cache-redis-reference.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Redis Cache
:extension-status: preview
include::_attributes.adoc[]
:categories: data
:summary: Use Redis as the Quarkus cache backend

By default, Quarkus Cache uses Caffeine as backend.
It's possible to use Redis instead.

include::{includes}/extension-status.adoc[]

== Redis as cache backend

When using Redis as the backend for Quarkus cache, each cached item will be stored in Redis:

- The backend uses the _<default>_ Redis client (if not configured otherwise), so make sure it's configured (or use the xref:redis-dev-services.adoc[redis dev service])
- the Redis key is built as follows: `cache:$cache-name:$cache-key`, where `cache-key` is the key the application uses.
- the value is encoded to JSON if needed


== Use the Redis backend

First, you need to add the `quarkus-redis-cache` extension to your project:

[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"]
.pom.xml
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
</dependency>
----

[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"]
.build.gradle
----
implementation("io.quarkus:quarkus-redis-cache")
----

Then, use the `@CacheResult` and others cache annotations as explained in the xref:cache.adoc[Quarkus Cache guide]:

[source, java]
----
@GET
@Path("/{keyElement1}/{keyElement2}/{keyElement3}")
@CacheResult(cacheName = "expensiveResourceCache")
public ExpensiveResponse getExpensiveResponse(@PathParam("keyElement1") @CacheKey String keyElement1,
@PathParam("keyElement2") @CacheKey String keyElement2, @PathParam("keyElement3") @CacheKey String keyElement3,
@QueryParam("foo") String foo) {
invocations.incrementAndGet();
ExpensiveResponse response = new ExpensiveResponse();
response.setResult(keyElement1 + " " + keyElement2 + " " + keyElement3 + " too!");
return response;
}
@POST
@CacheInvalidateAll(cacheName = "expensiveResourceCache")
public void invalidateAll() {
}
----

[[redis-cache-configuration-reference]]
== Configure the Redis backend

The Redis backend uses the `<default>` Redis client.
See the xref:redis-reference.adoc[Redis reference] to configure the access to Redis.

TIP: In dev mode, you can use the xref:redis-dev-services.adoc[Redis Dev Service].

If you want to use another Redis for your cache, configure the `client-name` as follows:

[source, properties]
----
quarkus.cache.redis.client-name=my-redis-for-cache
----

When writing to Redis or reading from Redis, Quarkus needs to know the type.
Indeed, the objects need to be serialized and deserialized.
For that purpose, you may need to configure type (class names) of the key and value you want to cache.
At build time, Quarkus tries to deduce the types from the application code, but that decision can be overridden using:

[source, properties]
----
# Default configuration
quarkus.cache.redis.key-type=java.lang.String
quarkus.cache.redis.value-type=org.acme.Person
# Configuration for `expensiveResourceCache`
quarkus.cache.redis.expensiveResourceCache.key-type=java.lang.String
quarkus.cache.redis.expensiveResourceCache.value-type=org.acme.Supes
----

You can also configure the time to live of the cached entries:

[source, properties]
----
# Default configuration
quarkus.cache.redis.ttl=10s
# Configuration for `expensiveResourceCache`
quarkus.cache.redis.expensiveResourceCache.ttl=1h
----

If the `ttl` is not configured, the entry won't be evicted.
You would need to invalidate the values using the `@CacheInvalidateAll` or `@CacheInvalidate` annotations.

The following table lists the supported properties:

include::{generated-dir}/config/quarkus-cache-redis.adoc[opts=optional, leveloffset=+1]

== Configure the Redis key

By default, the Redis backend stores the entry using the following keys: `cache:$cache-name:$cache-key`, where `cache-key` is the key the application uses.
So, you can find all the entries for a single cache using the Redis `KEYS` command: `KEYS cache:$cache-name:*`

The `cache:$cache-name:` segment can be configured using the `prefix` property:


[source, properties]
----
# Default configuration
quarkus.cache.redis.prefix=my-cache
# Configuration for `expensiveResourceCache`
quarkus.cache.redis.expensiveResourceCache.prefix=my-expensive-cache
----

In these cases, you can find all the keys managed by the default cache using `KEYS my-cache:*`, and all the keys managed by the `expensiveResourceCache` cache using: `KEYS my-expensive-cache:*`.

== Enable optimistic locking

The access to the cache can be _direct_ or use https://redis.io/docs/manual/transactions/#optimistic-locking-using-check-and-set[optimistic locking].
By default, optimistic locking is disabled.

You can enable optimistic locking using:
[source, properties]
----
# Default configuration
quarkus.cache.redis.use-optimistic-locking=true
# Configuration for `expensiveResourceCache`
quarkus.cache.redis.expensiveResourceCache.use-optimistic-locking=true
----

When used, the key is _watched_ and the _SET_ command is executed in a transaction (`MULTI/EXEC`).
9 changes: 8 additions & 1 deletion docs/src/main/asciidoc/cache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ Since the weather forecast is updated once every twelve hours, caching the servi

We'll do that using a single Quarkus annotation.

[NOTE]
====
In this guide, we use the default Quarkus Cache backend (Caffeine).
You can use Redis instead.
Refer to the xref:cache-redis-reference.adoc[Redis cache backend reference] to configure the Redis backend.
====

== Solution

We recommend that you follow the instructions in the next sections and create the application step by step.
Expand Down Expand Up @@ -736,7 +743,7 @@ properties in the `application.properties` file. By default, caches do not perfo
You need to replace `cache-name` in all the following properties with the real name of the cache you want to configure.
====

include::{generated-dir}/config/quarkus-cache-config-group-cache-config-caffeine-config.adoc[opts=optional, leveloffset=+1]
include::{generated-dir}/config/quarkus-cache-cache-config.adoc[opts=optional, leveloffset=+1]

Here's what your cache configuration could look like:

Expand Down
3 changes: 3 additions & 0 deletions docs/src/main/asciidoc/redis-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ import io.quarkus.redis.datasource.RedisDataSource;

More details about the various APIs offered by the quarkus-redis extension are available in the <<apis>> section.

[NOTE]
To use Redis as a cache backend, refer to the xref:cache-redis-reference.adoc[Redis Cache Backend reference].

[[apis]]
== One extension, multiple APIs

Expand Down
4 changes: 4 additions & 0 deletions extensions/cache/deployment-spi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache-runtime-spi</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.cache.deployment.spi;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.cache.CacheManagerInfo;

/**
* A build item that makes sure a {@link CacheManagerInfo} is available at runtime for consideration as the cache backend
*/
public final class CacheManagerInfoBuildItem extends MultiBuildItem {

private final CacheManagerInfo info;

public CacheManagerInfoBuildItem(CacheManagerInfo info) {
this.info = info;
}

public CacheManagerInfo get() {
return info;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import io.quarkus.cache.deployment.exception.UnsupportedRepeatedAnnotationException;
import io.quarkus.cache.deployment.exception.VoidReturnTypeTargetException;
import io.quarkus.cache.deployment.spi.AdditionalCacheNameBuildItem;
import io.quarkus.cache.deployment.spi.CacheManagerInfoBuildItem;
import io.quarkus.cache.runtime.CacheInvalidateAllInterceptor;
import io.quarkus.cache.runtime.CacheInvalidateInterceptor;
import io.quarkus.cache.runtime.CacheManagerRecorder;
Expand Down Expand Up @@ -238,17 +239,26 @@ private List<Throwable> validateKeyGeneratorsDefaultConstructor(CombinedIndexBui

@BuildStep
@Record(RUNTIME_INIT)
SyntheticBeanBuildItem configureCacheManagerSyntheticBean(CacheNamesBuildItem cacheNames,
CacheManagerRecorder cacheManagerRecorder, Optional<MetricsCapabilityBuildItem> metricsCapability) {
void cacheManagerInfos(BuildProducer<CacheManagerInfoBuildItem> producer,
Optional<MetricsCapabilityBuildItem> metricsCapability, CacheManagerRecorder recorder) {
producer.produce(new CacheManagerInfoBuildItem(recorder.noOpCacheManagerInfo()));
producer.produce(new CacheManagerInfoBuildItem(recorder.getCacheManagerInfoWithoutMetrics()));
if (metricsCapability.isPresent() && metricsCapability.get().metricsSupported(MICROMETER)) {
// if we include this unconditionally the native image building will fail when Micrometer is not around
producer.produce(new CacheManagerInfoBuildItem(recorder.getCacheManagerInfoWithMicrometerMetrics()));
}
}

boolean micrometerSupported = metricsCapability.isPresent() && metricsCapability.get().metricsSupported(MICROMETER);
@BuildStep
@Record(RUNTIME_INIT)
SyntheticBeanBuildItem configureCacheManagerSyntheticBean(List<CacheManagerInfoBuildItem> infos,
CacheNamesBuildItem cacheNames, Optional<MetricsCapabilityBuildItem> metricsCapability,
CacheManagerRecorder cacheManagerRecorder) {

Supplier<CacheManager> cacheManagerSupplier;
if (micrometerSupported) {
cacheManagerSupplier = cacheManagerRecorder.getCacheManagerSupplierWithMicrometerMetrics(cacheNames.getNames());
} else {
cacheManagerSupplier = cacheManagerRecorder.getCacheManagerSupplierWithoutMetrics(cacheNames.getNames());
}
boolean micrometerSupported = metricsCapability.isPresent() && metricsCapability.get().metricsSupported(MICROMETER);
Supplier<CacheManager> cacheManagerSupplier = cacheManagerRecorder.resolveCacheInfo(
infos.stream().map(CacheManagerInfoBuildItem::get).collect(toList()), cacheNames.getNames(),
micrometerSupported);

return SyntheticBeanBuildItem.configure(CacheManager.class)
.scope(ApplicationScoped.class)
Expand Down
1 change: 1 addition & 0 deletions extensions/cache/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
<module>deployment</module>
<module>deployment-spi</module>
<module>runtime</module>
<module>runtime-spi</module>
</modules>
</project>
40 changes: 40 additions & 0 deletions extensions/cache/runtime-spi/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-cache-runtime-spi</artifactId>
<name>Quarkus - Cache - Runtime SPI</name>

<dependencies>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>mutiny</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>


</project>
Loading

0 comments on commit 286cf2c

Please sign in to comment.