Skip to content

Commit

Permalink
Merge pull request quarkusio#34527 from cescoffier/redis-cache-expire…
Browse files Browse the repository at this point in the history
…-after-read

Implement expire-after-access for the redis cache
  • Loading branch information
cescoffier authored Jul 5, 2023
2 parents ea13bc3 + 81b4f7e commit 86d5083
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 70 deletions.
6 changes: 3 additions & 3 deletions docs/src/main/asciidoc/cache-redis-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ You can also configure the time to live of the cached entries:
[source, properties]
----
# Default configuration
quarkus.cache.redis.ttl=10s
quarkus.cache.redis.expire-after-write=10s
# Configuration for `expensiveResourceCache`
quarkus.cache.redis.expensiveResourceCache.ttl=1h
quarkus.cache.redis.expensiveResourceCache.expire-after-write=1h
----

If the `ttl` is not configured, the entry won't be evicted.
If the `expire-after-write` 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public CacheManager get() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf(
"Building Redis cache [%s] with [ttl=%s], [prefix=%s], [classOfItems=%s]",
cacheInfo.name, cacheInfo.ttl, cacheInfo.prefix,
cacheInfo.name, cacheInfo.expireAfterAccess, cacheInfo.prefix,
cacheInfo.valueType);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.cache.redis.runtime;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -32,7 +33,7 @@
* This class is an internal Quarkus cache implementation using Redis.
* Do not use it explicitly from your Quarkus application.
*/
public class RedisCacheImpl<K, V> extends AbstractCache implements RedisCache {
public class RedisCacheImpl extends AbstractCache implements RedisCache {

private static final Map<String, Class<?>> PRIMITIVE_TO_CLASS_MAPPING = Map.of(
"int", Integer.class,
Expand All @@ -48,8 +49,8 @@ public class RedisCacheImpl<K, V> extends AbstractCache implements RedisCache {
private final Redis redis;

private final RedisCacheInfo cacheInfo;
private final Class<V> classOfValue;
private final Class<K> classOfKey;
private final Class<?> classOfValue;
private final Class<?> classOfKey;

private final Marshaller marshaller;

Expand All @@ -70,21 +71,20 @@ private static Redis determineRedisClient(Optional<String> redisClientName) {
}
}

@SuppressWarnings("unchecked")
public RedisCacheImpl(RedisCacheInfo cacheInfo, Vertx vertx, Redis redis, Supplier<Boolean> blockingAllowedSupplier) {
this.vertx = vertx;
this.cacheInfo = cacheInfo;
this.blockingAllowedSupplier = blockingAllowedSupplier;

try {
this.classOfKey = (Class<K>) loadClass(this.cacheInfo.keyType);
this.classOfKey = loadClass(this.cacheInfo.keyType);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to load the class " + this.cacheInfo.keyType, e);
}

if (this.cacheInfo.valueType != null) {
try {
this.classOfValue = (Class<V>) loadClass(this.cacheInfo.valueType);
this.classOfValue = loadClass(this.cacheInfo.valueType);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to load the class " + this.cacheInfo.valueType, e);
}
Expand Down Expand Up @@ -290,7 +290,7 @@ public Uni<?> apply(List<String> listOfKeys) {
var req = Request.cmd(Command.DEL);
boolean hasAtLEastOneMatch = false;
for (String key : listOfKeys) {
K userKey = computeUserKey(key);
Object userKey = computeUserKey(key);
if (predicate.test(userKey)) {
hasAtLEastOneMatch = true;
req.arg(marshaller.encode(key));
Expand All @@ -315,7 +315,7 @@ String computeActualKey(String key) {
}
}

K computeUserKey(String key) {
Object computeUserKey(String key) {
String prefix = cacheInfo.prefix != null ? cacheInfo.prefix : "cache:" + getName();
if (!key.startsWith(prefix + ":")) {
return null; // Not a key handle by the cache.
Expand Down Expand Up @@ -354,21 +354,32 @@ private Uni<Void> watch(RedisConnection connection, byte[] keyToWatch) {
.replaceWithVoid();
}

private static <X> Uni<X> doGet(RedisConnection connection1, byte[] encodedKey1, Class<X> clazz,
private <X> Uni<X> doGet(RedisConnection connection, byte[] encoded, Class<X> clazz,
Marshaller marshaller) {
return connection1.send(Request.cmd(Command.GET).arg(encodedKey1))
.map(new Function<Response, X>() {
@Override
public X apply(Response r) {
return marshaller.decode(clazz, r);
}
});
if (cacheInfo.expireAfterAccess.isPresent()) {
Duration duration = cacheInfo.expireAfterAccess.get();
return connection.send(Request.cmd(Command.GETEX).arg(encoded).arg("EX").arg(duration.toSeconds()))
.map(new Function<Response, X>() {
@Override
public X apply(Response r) {
return marshaller.decode(clazz, r);
}
});
} else {
return connection.send(Request.cmd(Command.GET).arg(encoded))
.map(new Function<Response, X>() {
@Override
public X apply(Response r) {
return marshaller.decode(clazz, r);
}
});
}
}

private Uni<Void> set(RedisConnection connection, byte[] key, byte[] value) {
Request request = Request.cmd(Command.SET).arg(key).arg(value);
if (cacheInfo.ttl.isPresent()) {
request = request.arg("EX").arg(cacheInfo.ttl.get().toSeconds());
if (cacheInfo.expireAfterWrite.isPresent()) {
request = request.arg("EX").arg(cacheInfo.expireAfterWrite.get().toSeconds());
}
return connection.send(request).replaceWithVoid();
}
Expand Down Expand Up @@ -403,7 +414,7 @@ public V get() {
}
}

private static class GetFromConnectionSupplier<V> implements Supplier<Uni<? extends V>> {
private class GetFromConnectionSupplier<V> implements Supplier<Uni<? extends V>> {
private final RedisConnection connection;
private final Class<V> clazz;
private final byte[] encodedKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ public class RedisCacheInfo {
/**
* The default time to live of the item stored in the cache
*/
public Optional<Duration> ttl = Optional.empty();
public Optional<Duration> expireAfterAccess = Optional.empty();

/**
* The default time to live to add to the item once read
*/
public Optional<Duration> expireAfterWrite = Optional.empty();

/**
* the key prefix allowing to identify the keys belonging to the cache.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,23 @@ public static Set<RedisCacheInfo> build(Set<String> cacheNames, RedisCachesBuild
RedisCacheRuntimeConfig defaultRuntimeConfig = runtimeConfig.defaultConfig;
RedisCacheRuntimeConfig namedRuntimeConfig = runtimeConfig.cachesConfig.get(cacheInfo.name);

if (namedRuntimeConfig != null && namedRuntimeConfig.expireAfterAccess.isPresent()) {
cacheInfo.expireAfterAccess = namedRuntimeConfig.expireAfterAccess;
} else if (defaultRuntimeConfig.expireAfterAccess.isPresent()) {
cacheInfo.expireAfterAccess = defaultRuntimeConfig.expireAfterAccess;
}

if (namedRuntimeConfig != null && namedRuntimeConfig.expireAfterWrite.isPresent()) {
cacheInfo.expireAfterWrite = namedRuntimeConfig.expireAfterWrite;
} else if (defaultRuntimeConfig.expireAfterAccess.isPresent()) {
cacheInfo.expireAfterWrite = defaultRuntimeConfig.expireAfterWrite;
}

// Handle the deprecated TTL
if (namedRuntimeConfig != null && namedRuntimeConfig.ttl.isPresent()) {
cacheInfo.ttl = namedRuntimeConfig.ttl;
cacheInfo.expireAfterWrite = namedRuntimeConfig.ttl;
} else if (defaultRuntimeConfig.ttl.isPresent()) {
cacheInfo.ttl = defaultRuntimeConfig.ttl;
cacheInfo.expireAfterWrite = defaultRuntimeConfig.ttl;
}

if (namedRuntimeConfig != null && namedRuntimeConfig.prefix.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,28 @@
@ConfigGroup
public class RedisCacheRuntimeConfig {
/**
* The default time to live of the item stored in the cache
* The default time to live of the item stored in the cache.
*
* @deprecated Use {@link #expireAfterWrite} instead.
*/
@ConfigItem
@Deprecated
public Optional<Duration> ttl;

/**
* Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after
* the entry's creation, or the most recent replacement of its value.
*/
@ConfigItem
Optional<Duration> expireAfterWrite;

/**
* Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after
* the last access of its value.
*/
@ConfigItem
Optional<Duration> expireAfterAccess;

/**
* the key prefix allowing to identify the keys belonging to the cache.
* If not set, use "cache:$cache-name"
Expand Down
Loading

0 comments on commit 86d5083

Please sign in to comment.