quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.58k stars 2.63k forks source link

Quarkus Caching - Allow per item expiry time. #32606

Open TonyWeston opened 1 year ago

TonyWeston commented 1 year ago

Description

Currently, quarkus caching only invalidates entries based on a fixed time since they were added, or accessed. Provide an additional API that can specify when an entry is to be expired. This would be useful, for example, for when caching a resource fetched using the JaxRS client, based on the Cache-control header.

Implementation ideas

If the cache is accessed directly, there is the method:

Cache.get("{key}" , (key)->{
   // code to get value;
   return value;
});

This could be enhanced by returning a wrapped value, where needed:

Cache.get("{key}" , ()->{
      // code to get value,  
      // (if via a remote server, could parse the max-age from the cache-control header, into expiryTime)

      return Cache.result(value, expiryTime);
});
quarkus-bot[bot] commented 1 year ago

/cc @gwenneg (cache)

mkouba commented 1 year ago

I'm not sure this will be possible with Caffeine (the default implementation) but you can invalidate the entry manually as a workaround, i.e. something like:

@Inject 
Vertx vertx;

@CacheName("my-cache")
Cache cache;

@Inject 
FooService service;

Uni<Foo> getFoo() {
   return cache.getAsync("foo", key -> {
      return service.findFoo().invoke(foo -> vertx.setTimer(foo.getExpiryTime(), () -> cache.invalidate("foo").await().indefinitely()));
   });
} 

Alternatively, you can inject the default ScheduledExecutorService and use the schedule() method instead of Vertx#setTimer().

TonyWeston commented 1 year ago

This is from the caffeine github, for interface com.github.benmanes.caffeine.cache.Policy - expireVariably appears to fit the use case of having per-entry expiration times.. ?

image

rmanibus commented 1 year ago

one thing the caffeine api allow is to set an expiration policy based on the payload.

package com.github.benmanes.caffeine.cache;

public interface Expiry<K, V> {

  /**
   * Specifies that the entry should be automatically removed from the cache once the duration has
   * elapsed after the entry's creation. To indicate no expiration an entry may be given an
   * excessively long period, such as {@code Long#MAX_VALUE}.
   * <p>
   * <b>Note:</b> The {@code currentTime} is supplied by the configured {@link Ticker} and by
   * default does not relate to system or wall-clock time. When calculating the duration based on a
   * time stamp, the current time should be obtained independently.
   *
   * @param key the key represented by this entry
   * @param value the value represented by this entry
   * @param currentTime the current time, in nanoseconds
   * @return the length of time before the entry expires, in nanoseconds
   */
  long expireAfterCreate(K key, V value, long currentTime);

  /**
   * Specifies that the entry should be automatically removed from the cache once the duration has
   * elapsed after the replacement of its value. To indicate no expiration an entry may be given an
   * excessively long period, such as {@code Long#MAX_VALUE}. The {@code currentDuration} may be
   * returned to not modify the expiration time.
   * <p>
   * <b>Note:</b> The {@code currentTime} is supplied by the configured {@link Ticker} and by
   * default does not relate to system or wall-clock time. When calculating the duration based on a
   * time stamp, the current time should be obtained independently.
   *
   * @param key the key represented by this entry
   * @param value the value represented by this entry
   * @param currentTime the current time, in nanoseconds
   * @param currentDuration the current duration, in nanoseconds
   * @return the length of time before the entry expires, in nanoseconds
   */
  long expireAfterUpdate(K key, V value, long currentTime, @NonNegative long currentDuration);

  /**
   * Specifies that the entry should be automatically removed from the cache once the duration has
   * elapsed after its last read. To indicate no expiration an entry may be given an excessively
   * long period, such as {@code Long#MAX_VALUE}. The {@code currentDuration} may be returned to not
   * modify the expiration time.
   * <p>
   * <b>Note:</b> The {@code currentTime} is supplied by the configured {@link Ticker} and by
   * default does not relate to system or wall-clock time. When calculating the duration based on a
   * time stamp, the current time should be obtained independently.
   *
   * @param key the key represented by this entry
   * @param value the value represented by this entry
   * @param currentTime the current time, in nanoseconds
   * @param currentDuration the current duration, in nanoseconds
   * @return the length of time before the entry expires, in nanoseconds
   */
  long expireAfterRead(K key, V value, long currentTime, @NonNegative long currentDuration);
}

We could imagine a similar API for the quarkus cache. This way depending of the backend we could either set a cache wide policy (suitable for caffeine) or leverage the policy to compute the expiration before updating an item and send it to the backend.

rmanibus commented 1 year ago

also I am thinking, it is possible to wrap objects in caffeine using a POJO similar to

public class CaffeineWrapper<T> {
    long expiration;
    T data;
}

then using a custom Expiry we could set the expiration based on the CaffeineWrapper.expiration, since we have access to the payload.

rmanibus commented 1 year ago

not sure if the best way to go is

    <K, V> Uni<V> get(K key, Function<K, V> valueLoader);

    <K, V> Uni<V> get(K key, Function<K, V> valueLoader, Duration expiresIn);

https://github.com/rmanibus/quarkus/commit/aa102ed837847b3430a53c0c7fec058144c135b5

or

    <K, V> Uni<V> get(K key, Function<K, CacheValue<V>> valueLoader);

This second approach seems preferable since the expiration is computed only if the cache is set, however its introducing a breaking change

https://github.com/rmanibus/quarkus/commit/8a4bfc77d4575e110a022954a61a707b918754f8