From Memory Cache to Redis Cache

Hi,

I am currently using Memory Cache Client with no issues.
I would like to switch to Redis Cache, but I cannot get it to work.

I’m able to access my Redis instance thru:
x open redis (once opened I change port to match my instance).

I already setup IoC per documentation:

container.Register<IRedisClientsManager>(new RedisManagerPool("localhost:6363"));
container.Register(c => c.Resolve<IRedisClientsManager>().GetCacheClient());

This is how I read from Cache for GetPaged method:

var cacheContainer = CacheDisabled ? null : Cache.Get<Dictionary<string, CommonResponse>>(CACHE_CONTAINER_GET_PAGED());
if (cacheContainer == null) cacheContainer = new Dictionary<string, CommonResponse>();

And this is how I try to write to Cache:

if (!CacheDisabled) Cache.Replace(CACHE_CONTAINER_GET_PAGED(), cacheContainer);

I am not getting any exception and don't know what's wrong as it is working fine with memory cache.

Thank you.

The difference between a Memory Cache vs all other distributed caches is that they need to use serialization, so you need to ensure everything you’re trying to cache is serializable. Redis uses JSON, so check it can be de/serialized with JSON.

Thanks. It seems it is working now.
Aside from not serializing my objects, my error was also to use Cache.Replace instead of Cache.Set.

I refactored my code in order to consume following new methods, so I can now only store strings.

public T CacheGet<T>(string cacheKey) {
    return Cache.Get<string>(cacheKey).FromJson<T>();
}

public void CacheSet(string cacheKey, object value) {
    Cache.Set(cacheKey, value.ToJson(), CacheExpiresIn);
}

Now I have a new issue:
In the following lines, if I inspect value variable I can read: “My Value”:

CacheSet("sample", "My Value");
var value = CacheGet<string>("sample");

However if I go to redis app with x open redis
…and then I type command: get sample, it does not return any value.


As an additional note, I am using redis:alpine version 6 from docker: https://hub.docker.com/_/redis

Thanks for you help.

What are these API wrappers for? Don’t serialize the JSON yourself, it should be up to the cache provider what it uses to serialize the body. Your set is going to be double encoding a JSON string.

What’s a complete stand-alone example of cache set/get entry that fails when using the ICacheClient APIs directly? (i.e. instead of your wrappers).

You are right, then the only problem was to use Cache.Replace instead of Cache.Set.
I think Memory and Redis clients have different behavior for Cache.Replace().

So I already removed my wrappers and everything it’s working fine.

Moving on, when I was using Memory Cache I used to have a static field in my base class: BaseLogic to hold a reference to ICacheClient.

After yesterday’s refactoring I turned this static field to be an instance field (of course I had to refactor in cascade other things).

I would like to get this field back to static, would you recommend to handle Cache like this? I am not sure it could work, maybe connections to Redis are disposed after time?

So in startup I would initialize it like this:

container.Register<IRedisClientsManager>(new RedisManagerPool("localhost:6363"));
container.Register(c => c.Resolve<IRedisClientsManager>().GetCacheClient()); 
BaseLogic.Cache = container.Resolve<ICacheClient>();


The reason I want it to be static is because sometimes I cannot inyect my BaseLogic dependencies because of reference cycle, so with a static Cache I would be still able to clear cache from a static method.

The registered ICacheClient is already accessible statically via the singleton HostContext.Cache, should you need to, you can also access the Memory Cache with HostContext.LocalCache.

Note: it’s only possible to access the Cache statically because it supports registration as a singleton, e,g. you wouldn’t be able to access any transient dependencies statically.

1 Like

Is LocalCache shared across all services, or does each service count with its own LocalCache?

If I do LocalCache.FlushAll() in one service, will it clear all services LocalCache?

It’s a singleton so there’s only 1 per AppHost

1 Like

It is likely Cache.Remove() and Cache.RemoveAll() are not working with Redis.

I set a break point and inspected cache keys to double check they match with Redis and they do.

When I switch back to Memory Cache, my method starts working well.

Thank you.

This is the implementation for Redis ICacheClient RemoveAll():

What’s a stand-alone example that doesn’t work?

Wow, I didn’t expect to get a quick answer on Sunday at night. Thank you.

My original code was like this:

Cache.RemoveAll(Cache.GetKeysStartingWith(CACHE_GET_PAGED()));

Then I tried with:

var cacheKeys_getPaged = Cache.GetKeysStartingWith(CACHE_GET_PAGED());
foreach (var cacheKey in cacheKeys_getPaged)
{
    Cache.Remove(cacheKey);
}

Where Cache.GetKeysStartingWih(…) is certainly getting the keys I want to remove, and they do exist in Redis.

I tried with both versions of Redis: 2.8.17 and 6.0.5

I need a standalone example I can run that repro’s the issue.

What are the keys being returned, before & after running Cache.RemoveAll()

Maybe my keys are so big?

This is before Remove attempt, scope variables display cacheKeys_getPaged assigned in line 222.

Then, after loop, I am using Debug Console to retrieve Cache.GetKeysStartingWith(CACHE_GET_PAGED()), which at this point should be empty but get same result as above.

Here in Redis, we can see one of the keys to be removed:

I need a verifiable repro to identify the issue.

This basic repro works as expected:

using System;
using ServiceStack;
using ServiceStack.Text;
using ServiceStack.Redis;
using ServiceStack.DataAnnotations;

var redisManager = new RedisManagerPool("localhost:6379");
var redis = redisManager.GetClient();
redis.FlushAll();

var cache = redisManager.GetCacheClient();

cache.Set("siafracc_QUERY_Deposit__Query_Deposit_10_1", "A");
cache.Set("siafracc_QUERY_Deposit__0_1___CUSTOM", "A");

var keys = cache.GetKeysStartingWith("siafracc_QUERY_Deposit");

cache.RemoveAll(keys);

var newKeys = cache.GetKeysStartingWith("siafracc_QUERY_Deposit");

"oldKeys:".Print();
keys.PrintDump();

"newKeys:".Print();
newKeys.PrintDump();

Which prints:

oldKeys:
[
	siafracc_QUERY_Deposit__Query_Deposit_10_1,
	siafracc_QUERY_Deposit__0_1___CUSTOM
]
newKeys:
[
	
]

Please update this Gistlyn example with an example that repro’s the issue.

After you create a repro, save your gist & post the link here so I can run it.

This is similar to my current structure and here it seems to work fine.
https://gistlyn.com/?gist=6d8c9c68197aa29a7bd64cfe02b6bc53&collection=2cc6b5db6afd3ccb0d0149e55fdb3a6a

Gystlin keeps compiling, but it works fine in my local environment.

Redis client is removing as expected. I will continue to look into my system to find out the issue.

I was able to reproduce the error:

https://gistlyn.com/?gist=7a3016a3b7121af091fa1de71b844b91&collection=2cc6b5db6afd3ccb0d0149e55fdb3a6a

It has to do with: WithPrefix

I’m still seeing the same result, did you forget to save?

OK, here you go:

https://gistlyn.com/?gist=6d8c9c68197aa29a7bd64cfe02b6bc53&collection=2cc6b5db6afd3ccb0d0149e55fdb3a6a

The issue here is because the keys returned from GetKeysStartingWith() include the prefix so when you pass the prefixed keys to RemoveAll it’s adding another prefix resulting in trying to remove keys that don’t exist.

This existing behavior is the same in Redis & Memory Cache Client WithPrefix as seen in this gist.

As this issue is more likely to occur than using conflicting keys with the same prefix I’ve updated CacheClientWithPrefix to only add prefix to keys when it doesn’t already exist in this commit.

This change is available from v5.9 that’s now available on MyGet.

1 Like