Skip to content

Commit

Permalink
Introduce a Redis based Cache implementation
Browse files Browse the repository at this point in the history
Co-authored-by: Georgios Andrianakis <[email protected]>
  • Loading branch information
cescoffier and geoand committed Mar 16, 2023
1 parent c3a832d commit f969d6e
Show file tree
Hide file tree
Showing 39 changed files with 2,712 additions and 7 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
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5856,12 +5856,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
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 the Redis as 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 try 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 list the supported properties:

include::{generated-dir}/config/quarkus-redis-cache.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`).
7 changes: 7 additions & 0 deletions 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
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 xref: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
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ public boolean equals(Object obj) {
public String toString() {
return "CompositeCacheKey" + Arrays.toString(keyElements);
}

public Object[] getKeyElements() {
return keyElements;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
import io.smallrye.mutiny.Uni;

/**
* This class is an internal Quarkus cache implementation. Do not use it explicitly from your Quarkus application. The public
* This class is an internal Quarkus cache implementation using Redis. Do not use it explicitly from your Quarkus application.
* The public
* methods signatures may change without prior notice.
*/
public class CaffeineCacheImpl extends AbstractCache implements CaffeineCache {
Expand Down
1 change: 1 addition & 0 deletions extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<module>mailer</module>
<module>grpc</module>
<module>redis-client</module>
<module>redis-cache</module>

<!-- Data access and validation -->
<module>transaction-annotations</module>
Expand Down
111 changes: 111 additions & 0 deletions extensions/redis-cache/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?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">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>quarkus-redis-cache-deployment</artifactId>

<name>Quarkus - Redis Cache - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-client-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-redis-cache</artifactId>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit4-mock</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</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>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>test-redis</id>
<activation>
<property>
<name>test-containers</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Loading

0 comments on commit f969d6e

Please sign in to comment.