lettuce icon indicating copy to clipboard operation
lettuce copied to clipboard

Client-side caching clarifications

Open sebarys opened this issue 2 years ago • 3 comments

Hello

I see that Lettuce support client side caching https://github.com/lettuce-io/lettuce-core/issues/1281 - it is great, thanks for your effort to implement it :)

As I couldn't find more examples than in mentioned above ticket I have two questions:

  • what is client side cache behaviour on Redis connection drop? I would like to keep high consistency so preferred would be to flush cache on connection drop. Is it possible to configure in such way (if it is not default behaviour)?
  • is it possible to set any TTL for client side cache entries? I know that protocol should propagate invalidation signals, anyway I would like to invalidate cache entires after some long TTL (few hours). Is it sth that I can configure or it is sth that I would need implement externally?

Thanks for answers in advance!

Regards

sebarys avatar May 18 '22 10:05 sebarys

hi Sebarys,

I got the same requirement to set TTL for client cached items. And I found a way to do that as below:

  1. Write your own cache accessor by implements interface "io.lettuce.core.support.caching.CacheAccessor".

  2. Use Guava or Caffeine cache to implements the methods "public V get(K key)"....., you need to initial Guava or Caffeine cache in contructor like: public CaffeineCacheMap() { this.cache = Caffeine.newBuilder(). expireAfterWrite(clientSideCacheExpireTimeMinutes, TimeUnit.MINUTES). maximumSize(clientSideCacheMaxNumber). build(); }

    @Override
    

    public V get(K key) { debugLogger.log(CLASS_NAME,"get(key)", "get cache with key [" + key + "]"); return cache.getIfPresent(key); }

    @Override public void put(K key, V value) { debugLogger.log(CLASS_NAME, "put(key,value)", "put cache with key [" + key + "] and value [******]"); cache.put(key,value); }

    @Override public void evict(K key) { debugLogger.log(CLASS_NAME,"evict(key)", "remove cache for key [" + key + "]"); cache.invalidate(key); }

  3. put your own CacheAccessor as parameter when trying to enable client-caching for Luttuce StatefulRedisConnection. like: lettuceClient = (LettuceClient) GlobalRedisClientFactory.getClient(); StatefulRedisConnection<String, String> redisConnection = lettuceClient.unwrap(); frontend = ClientSideCaching.enable(cacheMap, redisConnection, TrackingArgs.Builder.enabled()); the "cacheMap" is an instance of your own implemented CacheAccessor.

But still, we still need to find a way to clean caches when "reconnection" happen since reconnection will make redis caching tracing not working anymore.

dkrcharlie avatar May 24 '22 06:05 dkrcharlie

You can register a connection listener with the Redis Client to react to connect/disconnect events and issue commands after connecting.

mp911de avatar May 24 '22 09:05 mp911de

Thanks @dkrcharlie and @mp911de ! So current implementation works only to first connection issues with Redis?

sebarys avatar May 24 '22 10:05 sebarys

Yep.

mp911de avatar Nov 22 '22 10:11 mp911de

Hi @sebarys

How have you dealt with the issue that the current implementation only works for the first connection?

GuyKomari avatar May 04 '23 18:05 GuyKomari

You can register a connection listener with the Redis Client to react to connect/disconnect events and issue commands after connecting.

Hi, i got a problem here. when i try to enable tracking in the event listener, the command always get timeout... here is my code:

@Slf4j
@Component
@RequiredArgsConstructor
public class CacheTrackingPlugin {

    private final Cache<String, String> cache; // Caffeine cache

    private final LettuceConnectionFactory connectionFactory;

    @PostConstruct
    public void run() {
        AbstractRedisClient absClient = connectionFactory.getNativeClient();
        Assert.isInstanceOf(RedisClient.class, absClient);
        RedisClient client = (RedisClient) absClient;
        Assert.notNull(client, "client should not be null");

        // add connection event listener
        client.addListener(new RedisConnectionStateListener() {
            @Override
            @SuppressWarnings(value = {"unchecked", "rawtypes"})
            public void onRedisConnected(RedisChannelHandler<?, ?> connection, SocketAddress socketAddress) {
                log.info("========onRedisConnected========");
                cache.invalidateAll();
                StatefulRedisConnection conn = (StatefulRedisConnection) connection;
                // enable tracking manually -> always timeout !!!
                conn.sync().clientTracking(TrackingArgs.Builder.enabled());
                log.info("========Enable Tracking========");
                // add listener, to invalidate cache objects
                conn.addListener(message -> {
                    if (message.getType().equals("invalidate")) {
                        List<Object> content = message.getContent(StringCodec.UTF8::decodeKey);
                        List<String> keys = (List<String>) content.get(1);
                        log.info("invalidated keys: {}", keys);
                        keys.forEach(cache::invalidate);
                    }
                });
            }
            @Override
            public void onRedisDisconnected(RedisChannelHandler<?, ?> connection) { }
            @Override
            public void onRedisExceptionCaught(RedisChannelHandler<?, ?> connection, Throwable cause) { }
        });
        // connect to redis
        client.connect();
    }
}

did i miss someting ? :(

Su57 avatar Sep 14 '23 10:09 Su57