Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement expire-after-access for the redis cache #34527

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
geoand marked this conversation as resolved.
Show resolved Hide resolved
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