-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Bug Report
Current Behavior
Using a CacheFrontEnd created from io.lettuce.core.support.caching.ClientSideCaching.create(CacheAccessor, StatefulRedisConnection) . We have experienced the cache going stale, i.e. not updated even if writes are sent to Redis.
Identified why this happens. Modified the current implementation of ClientSideCaching.get().
// Copied MapCacheAccessor and made two small adjustments needed below:
// getRedisInProgress -> Just a temporary value signaling redis operation in progress. E.g. "REDIS_IN_PROGRESS", if V type is String.
// putWithReturn -> Same as put, but return the previous value (atomically).
@Override
public V get(K key) {
V value = cacheAccessor.get(key);
if (value == null) {
cacheAccessor.put(key, cacheAccessor.getRedisInProgressPlaceHolder()); // new
value = redisCache.get(key);
if (value != null) {
final V beforePut = cacheAccessor.putWithReturn(key, value); // edited
if (beforePut != cacheAccessor.getRedisInProgressPlaceHolder()) { // new
System.out.println("Issue detected! For key: " + key); // new
} // new
}
}
return value;
}Now there's a temporary value set in the cache until redis get is complete. However, sometimes the log above is triggered, meaning that the cache entry has been updated in between. This can happen because of an invalidation and it can lead to the cache going stale. Consider this scenario:
- get is called, no cache entry, so Redis get called. Tracking table on redis server updated.
- Thread executing in get() above is slowed down (e.g. thread preemption / contention on the cache used) before putting value into cache.
- Key is updated in redis. So an invalidation is fired. Thread executes in DefaultRedisCache.addInvalidationListener callback and cache entry is evicted.
- Thread that was slowed down in step 2 continues, and adds it's value to the cache.
We will end up with a stale cache. Cause the cache has an entry for this key so we will never request it again, unless the value is evicted. But it will never be evicted cause in the view of the redis server the invalidation was after so it's already considered invalidated. The log get's triggered for me, and I verifed that when I update this keys value in redis, our service calling get on the cache still gets the old value.
Environment
- Lettuce version(s): 6.4.2 (but latest code in question looks the same)
- Redis version: 6.2.6
Possible Solution
If the key is evicted when the race is detected in the code snippet above it should be resolved. I.e. evict where the log is currently. All null value checks should probably also consider the "getRedisInProgressPlaceHolder" value as null.
Additional context
In our case, we were hitting the limit of keys in tracking table (tracking-table-max-keys), so invalidates due to full table were frequent. Therefore I could reproduce frequently. This is of course not the recommended environment. But nontheless, the race above would be possible with just normal writes.