StackExchange / StackExchange.Redis

General purpose redis client
https://stackexchange.github.io/StackExchange.Redis/
Other
5.89k stars 1.51k forks source link

Facing long wait times when using Redis as a Entity Framework cache #2541

Closed keshavkaul closed 1 year ago

keshavkaul commented 1 year ago

This is more of me seeking suggestions to mitigate this issue. I'm attaching a dottrace profiler screenshot for more info.

I'm using Easycaching.Redis library that uses this library.

Screenshot 2023-09-06 at 10 14 07 PM

Some context:

  1. I've profiled a single page load.
  2. Single page load has many calls to the sql server database.
  3. Each db call has caching logic that checks redis first.

After adding db caching, the page load performs worst than without integrating redis cache.

Any help is welcome.

slorello89 commented 1 year ago

Hi @keshavkaul,

So not a lot to go off of here (really need to see a fair bit of your code to make a sensible determination), a couple of notes.

  1. If your cache is cold (has nothing in it) your initial page load will ALWAYS be slower when using Redis vs not. That's because you still need to query your SQL database, not to mention make at a minimum 2 full round trips to Redis to check/populate it.
  2. Each time you incur a network round trip, you are adding latency, so if you need to go back and forth to Redis 10 times during a single page load, you will incur a 10x the latency between Redis and your app. I notice that you are using the sync methods here. This means that you are in fact incurring a whole network round trip with each request to Redis. Best practice would be to implement async up and down the stack, and to use the async methods instead of the sync methods. If you do this you can avoid having multiple round trips by awaiting all the tasks that you are producing with these async operations at the end - this allows StackExchange.Redis to implicitly pipeline everything for you. That alone can be a monstrous time saver. I've never used EasyCache.Redis, so I don't know the exact levers you'd need to pull on to make this happen, but I think they are just returning the tasks produced by StackExchange.Redis, so this should be relatively doable. Also Microsoft.Extensions.Caching.StackExchangeRedis is a caching abstraction explicitly for aspnetcore which might be worth trying. Or you could just use the library directly.

How well your app can be optimized with Redis is likely ultimately a data modeling question and a question of your hit rate - e.g. if 100% of your queries are novel (haven't been seen before), simple sets/gets in Redis will not do much for you.

keshavkaul commented 1 year ago

Thank you @slorello89 for the explanation. Makes sense to rethink on the approach I'm trying to take here.

Some more context:

  1. I'm seeing performance problems when there's cold cache and hot cache.
  2. I'm using EFCache to hook into EF pipeline to provide redis caching solution.
  3. I've seen duplicate cache queries being made that along with network round trip and database lookup slows down the system further.

I'm thinking of having a solution that combines redis cache and per request in memory cache like this:

  1. Check if cache key is present in memory, If present, return the cached value.
  2. If key not present in memory, run an async task to fetch the cache from redis and store in memory.
  3. When setting a value in cache, first set the cache in memory and asynchronously set the redis cache.
  4. When invalidating cache, cancel all pending redis async tasks, invalidate in memory cache, invalidate redis cache

Thanks again for your guidance.