Finbuckle.MultiTenant icon indicating copy to clipboard operation
Finbuckle.MultiTenant copied to clipboard

feat(cache): new fusion cache store project

Open vivaticket-gpatane opened this issue 1 year ago • 3 comments

After using FusionCache for a while, I decided to create this pull request to add a new store that use the library. https://github.com/ZiggyCreatures/FusionCache

Using FusionCache over IDistributedCache offers several advantages:

  • Resilience: FusionCache handles communication issues with Redis by providing fallback mechanisms to in-memory caching, preventing application failures.
  • Performance: It enhances performance by reducing latency, as data is fetched from in-memory before querying Redis.
  • Advanced Features: FusionCache includes built-in features such as retries, timeouts, and cache invalidation, which are not available in IDistributedCache.
  • Non-blocking Configuration: It supports non-blocking operations, minimizing the impact of cache failures on the overall application.

Stores like IDistributedCacheStore should be ignored in case of Redis errors. Currently, especially during local development, I often encounter issues where, if Redis is not running, the communication error generated by IDistributedCache causes my application to return a 500 error.

image

With the FusionCache store, after a Redis error, the application will continue running by moving on to the next store in the chain.

[17:06:10 WRN] FUSION [N=FusionCache I=f0d8f17fd51442d28e30f9427c99513e] (O=0HN6EGLMR9A6P K=__tenant__identifier__it): [DC] an error occurred while getting entry from distributed <s:ZiggyCreatures.Caching.Fusion.FusionCache>
StackExchange.Redis.RedisConnectionException: The message timed out in the backlog attempting to send because no connection became available (5000ms) - Last Connection Exception: UnableToConnect on localhost:6379/Interactive, Initializing/NotStarted, last: NONE, origin: BeginConnectAsync, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 60s, state: Connecting, mgr: 10 of 10 available, last-heartbeat: never, global: 0s ago, v: 2.8.0.27420, command=HMGET, timeout: 5000, inst: 0, qu: 0, qs: 0, aw: False, bw: CheckingForTimeout, last-in: 0, cur-in: 0, sync-ops: 0, async-ops: 1, serverEndpoint: localhost:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: MacBook-Air-di-Giuseppe(SE.Redis-v2.8.0.27420), IOCP: (Busy=0,Free=1000,Min=1,Max=1000), WORKER: (Busy=1,Free=32766,Min=8,Max=32767), POOL: (Threads=8,QueuedItems=0,CompletedItems=245,Timers=16), v: 2.8.0.27420 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)
 ---> StackExchange.Redis.RedisConnectionException: UnableToConnect on localhost:6379/Interactive, Initializing/NotStarted, last: NONE, origin: BeginConnectAsync, outstanding: 0, last-read: 0s ago, last-write: 0s ago, keep-alive: 60s, state: Connecting, mgr: 10 of 10 available, last-heartbeat: never, global: 0s ago, v: 2.8.0.27420
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAndRefreshAsync(String key, Boolean getData, CancellationToken token)
   at Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache.GetAsync(String key, CancellationToken token)
   at ZiggyCreatures.Caching.Fusion.Internals.Distributed.DistributedCacheAccessor.<>c__DisplayClass14_0`1.<<TryGetEntryAsync>b__0>d.MoveNext() in /_/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Async.cs:line 159
--- End of stack trace from previous location ---
   at ZiggyCreatures.Caching.Fusion.Internals.RunUtils.RunAsyncFuncWithTimeoutAsync[TResult](Func`2 asyncFunc, TimeSpan timeout, Boolean cancelIfTimeout, Action`1 timedOutTaskProcessor, CancellationToken token) in /_/src/ZiggyCreatures.FusionCache/Internals/RunUtils.cs:line 43
   at ZiggyCreatures.Caching.Fusion.Internals.Distributed.DistributedCacheAccessor.TryGetEntryAsync[TValue](String operationId, String key, FusionCacheEntryOptions options, Boolean hasFallbackValue, Nullable`1 timeout, CancellationToken token) in /_/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Async.cs:line 158
[17:06:10 INF] Start processing HTTP request GET https://localhost:5201/api/Tenant/it

Notes

Since it is an external library to this project and is available only for Net8, I have created the following projects in which I have added the dependency on the external library.

  1. Finbuckle.MultiTenant.Store.FusionCache
  2. Finbuckle.MultiTenant.Store.FusionCache.Test

Let me know what you think and if it could be integrated as a feature into the project.

vivaticket-gpatane avatar Sep 06 '24 15:09 vivaticket-gpatane

@dotnet-policy-service agree

vivaticket-gpatane avatar Sep 09 '24 07:09 vivaticket-gpatane

Thanks! I will add this to consideration. I am toying with the idea of a side repo or branch for very specific features like this but probably will not merge into the main one since one of the goals is to stay agnostic to non .NET core libraries.

AndrewTriesToCode avatar Oct 04 '24 17:10 AndrewTriesToCode

Thanks! I will add this to consideration. I am toying with the idea of a side repo or branch for very specific features like this but probably will not merge into the main one since one of the goals is to stay agnostic to non .NET core libraries.

ok, it make sense, net 9 will arrive shortly and with it the new cache system, that should be very similar to fusion cache. thank again for your time and for making this amazing library.

vivaticket-gpatane avatar Oct 04 '24 18:10 vivaticket-gpatane

Just a note here: even by depending on the HybridCache abstraction, it would still makes sense to have FusionCache (note: creator here) as the implementation (see here) to get over some HybridCache limitations and have more features.

Hope this helps.

jodydonetti avatar Apr 13 '25 11:04 jodydonetti

Just a note here: even by depending on the HybridCache abstraction, it would still makes sense to have FusionCache (note: creator here) as the implementation (see here) to get over some HybridCache limitations and have more features.

Hope this helps.

Andrew's concern is about not adding external dependencies to the project. To address this, it might be necessary to introduce a new interface, such IHybridCacheStore.

In any case, integrating your library brings more pros than cons.

italian Grazie Jody per la info ma soprattutto per la libreria incredibile che hai creato :) end italian

vivaticket-gpatane avatar Apr 14 '25 07:04 vivaticket-gpatane

Andrew's concern is about not adding external dependencies to the project. To address this, it might be necessary to introduce a new interface, such IHybridCacheStore.

Got it, but just to be sure we're on the same page: the HybridCache abstraction (see: abstract class HybridCache) is now part of .NET itself (see here), at least since .NET 9. For previous versions it's still available/compatible as a separate package, but still a "core" one by Microsoft itself. Maybe it was already clear, but just to be sure.

In this sense I'm not sure there's a need for an additional IHybridCacheStore: having said that, I don't know the codebase so I'm probably missing something 🙂

In any case, integrating your library brings more pros than cons.

Thanks!

italian Grazie Jody per la info ma soprattutto per la libreria incredibile che hai creato :) end italian

Ahah grazie 😬

jodydonetti avatar Apr 14 '25 10:04 jodydonetti

@vivaticket-gpatane @jodydonetti

Thanks for the conversation. I'm not going to merge this in for the reasons discussed but will plan for hybrid cache capability in .NET 10. I encourage you keep your solution available for anyone else searching around for a similar solution.

AndrewTriesToCode avatar May 13 '25 03:05 AndrewTriesToCode