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

Feature to provide Event of KEY's data which is Altered/changed in Server Assisted Client Side Caching module #4040

Open
atharva29 opened this issue Dec 13, 2024 · 3 comments

Comments

@atharva29
Copy link

atharva29 commented Dec 13, 2024

##Feature to provide Event of KEY's Name which is Altered/changed in Server Assisted Client Side Caching module

Expected behavior

I should get value/data of Key which is changed or altered. By adding this feature, client can prefetch the changed value and update its cache. We can enable this feature using a simple flag, such that if required user can enable this.

Write here what you're expecting ...

Actual behavior

Currently jedis invalidates that changed key & doesn't provide the key's name

Redis / Jedis Configuration

Jedis version:

v5.2.0

Redis version:

7.4.1

Java version:

@sazzad16
Copy link
Collaborator

@atharva29 When enabling server assisted client side caching, you can pass an implementation of Cache interface your own. In that Cache object, you can use the deleteByRedisKeys method to get the Redis keys for invalidation. You may consider deleteByRedisKey to get the keys individually.

You can override our AbstractCache class for easier implementation of Cache.

@2Himanshu
Copy link

Hi @sazzad16 ,
Thanks for the response and the suggestions regarding the custom cache implementation!

I’ve implemented a custom Cache and used the deleteByRedisKey and deleteByRedisKeys methods as you recommended. However, I noticed that the invalidated keys are only being logged when a GET request is made for the key, instead of immediately upon the key's data being altered or invalidated.

I have made the changes in the redis database manually for that key that i have already in my cache but i am not getting key printing in my console.

the deleteByRedisKey and deleteByRedisKeys are triggered when i made get request to them.

below is my code that i have developed

This is CustomCache class which extends AbstractCache

`
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import redis.clients.jedis.csc.AbstractCache;
import redis.clients.jedis.csc.CacheEntry;
import redis.clients.jedis.csc.CacheKey;
import redis.clients.jedis.csc.Cacheable;
import redis.clients.jedis.csc.DefaultCacheable;
import redis.clients.jedis.csc.EvictionPolicy;
import redis.clients.jedis.csc.LRUEviction;

public class CustomCache extends AbstractCache {

protected final Map<CacheKey, CacheEntry> cache;
private final EvictionPolicy evictionPolicy;

protected CustomCache(int maximumSize) {
    this(maximumSize, new HashMap<CacheKey, CacheEntry>());
}

protected CustomCache(int maximumSize, Map<CacheKey, CacheEntry> map) {
    this(maximumSize, map, DefaultCacheable.INSTANCE, new LRUEviction(maximumSize));
}

protected CustomCache(int maximumSize, Cacheable cacheable) {
    this(maximumSize, new HashMap<CacheKey, CacheEntry>(), cacheable, new LRUEviction(maximumSize));
}

protected CustomCache(int maximumSize, Cacheable cacheable, EvictionPolicy evictionPolicy) {
    this(maximumSize, new HashMap<CacheKey, CacheEntry>(), cacheable, evictionPolicy);
}

protected CustomCache(int maximumSize, Map<CacheKey, CacheEntry> map, Cacheable cacheable, EvictionPolicy evictionPolicy) {
    super(maximumSize, cacheable);
    this.cache = map;
    this.evictionPolicy = evictionPolicy;
    this.evictionPolicy.setCache(this);
}

@Override
public int getSize() {
    return cache.size();
}

@Override
public Collection<CacheEntry> getCacheEntries() {
    return cache.values();
}

@Override
public EvictionPolicy getEvictionPolicy() {
    return this.evictionPolicy;
}

@Override
public CacheEntry getFromStore(CacheKey key) {
    return cache.get(key);
}

@Override
public CacheEntry putIntoStore(CacheKey key, CacheEntry entry) {
    return cache.put(key, entry);
}

@Override
public boolean removeFromStore(CacheKey key) {
    return cache.remove(key) != null;
}

@Override
protected final void clearStore() {
    cache.clear();
}

@Override
protected boolean containsKeyInStore(CacheKey cacheKey) {
    return cache.containsKey(cacheKey);
}

@Override
public List<CacheKey> deleteByRedisKey(Object key) {
    // Convert the key to a string if it's a byte array
    String keyString = convertKeyToString(key);
    // Log the invalidated key
    System.out.println("Invalidated key: " + keyString);

    // Call the superclass method to retain its original behavior
    return super.deleteByRedisKey(key);
}

@Override
public List<CacheKey> deleteByRedisKeys(List keys) {
    // Convert the list of keys to strings
    List<String> keyStrings = (List<String>) keys.stream()
            .map(this::convertKeyToString)
            .collect(Collectors.toList());
    // Log the list of invalidated keys
    System.out.println("Invalidated keys: " + keyStrings);

    // Call the superclass method to retain its original behavior
    return super.deleteByRedisKeys(keys);
}

// Helper method to convert keys to a readable string format
private String convertKeyToString(Object key) {
    if (key instanceof byte[]) {
        return new String((byte[]) key);
    }
    return key.toString();
}

}`

This is RedisConfig class which uses CustomCache

`
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.;
import redis.clients.jedis.csc.
;

import java.util.Collection;

@configuration
public class RedisConfig {

@Bean
public Cache cache() {
    // Create CustomCache with valid eviction policy
    return new CustomCache(1000);
}

@Bean
public UnifiedJedis unifiedJedis(Cache cache) {
    // Redis server connection
    HostAndPort node = HostAndPort.from("localhost:6379");
    JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
            .resp3() // Enable RESP3 protocol for client-side caching
            .build();

    return new UnifiedJedis(node, clientConfig, cache);
}

}`

This is RedisController class for making request to redis database

`
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.csc.Cache;

@RestController
@RequestMapping("/api/redis")
public class RedisController {

@Autowired
private UnifiedJedis unifiedJedis;

@Autowired
private Cache cache;

// API to get data from Redis
@GetMapping("/get")
public Object getData(@RequestParam String key) {
	String keyType = unifiedJedis.type(key);
	switch (keyType) {
	case "list":
		// Retrieve all elements from the list
		List<String> subscription = unifiedJedis.lrange(key, 0, -1);
		return subscription;
	case "string":
		// Retrieve string value
		return unifiedJedis.get(key);
	default:
		return "Key not found or unsupported type: " + keyType;
	}
}

 @PostMapping("/set")
    public String setData(@RequestParam String key, @RequestParam String value) {
        unifiedJedis.set(key, value);
        return "Data set successfully in Redis";
    }

}`

@sazzad16
Copy link
Collaborator

By design, it may take as much as next command in the same Connection or on the same key for the cache to be updated.

So it is by design, you don't get the following:

instead of immediately upon the key's data being altered or invalidated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants