Comments (5)
Just to highlight the main-line questions:
- How to configure when App1 [1..3] used several named instances of cache, while App2 [1..2] uses the only one of them (no need to sync the default instance)?
- How to configure notifications to sync cache between apps and instances as fast as possible?
from fusioncache.
Hi @apavelm and thanks for using FusionCache!
Sorry for the delay but I was at the MVP Global Summit and was not able to look into this.
I'll look into this asap and will let you know.
Thanks!
from fusioncache.
Wow, in the end I forgot to answer this, sorry about it 🥲
Now regarding your request: first thing I think there's a small typo here:
foreach (...list of cache instances....)
{
...
var cacheInstanceName = "..some name from the loop..."; // the list includes "mainCache"
var entryOptions = new {}; // cache options for the corresponsing cacheInstanceName
...
services.AddFusionCache(cacheInstanceName)
.WithDefaultEntryOptions();
}
where it should be:
foreach (...list of cache instances....)
{
...
var cacheInstanceName = "..some name from the loop..."; // the list includes "mainCache"
var entryOptions = new {}; // cache options for the corresponsing cacheInstanceName
...
services.AddFusionCache(cacheInstanceName)
.WithDefaultEntryOptions(entryOptions); // PASS THE ENTRY OPTIONS CREATED ABOVE
}
Second: is there a reason why also registering the MemoryDistributedCache
? FusionCache can be used even just as an L1 cache (meaning only the first level, in memory) without having to change the calling sites at all. This means that you can transparently work with it both with an L2 (second level, distributed cache) or without it, again transparently.
I'll continue with the code below with this case in mind, but you can easily change it to get back to your version.
Third: if the different cache instances all point to the same Redis instance but they store different pieces of data with maybe the same cache key, you may get cache key collisions (the cache key "foo" used in cacheA and cacheB will have different memory levels, but the same distributed level).
To avoid this you can simply enable cache key prefix by calling WithCacheKeyPrefix()
on the builder: this will use the cache name as a prefix (plus some separator), or you can even specify a custom prefix by using WithCacheKeyPrefix("MyPrefix")
.
Fourth: by just registering the distributed cache/serializer/backplane at the top you are not actually using them. What you can do is either register them once like you did, but then tell each FusionCache instance to use the registered one, like this:
services.AddFusionCache()
.TryWithRegisteredDistributedCache();
foreach (...list of cache instances....)
{
...
var cacheInstanceName = "..some name from the loop..."; // the list includes "mainCache"
var entryOptions = new {}; // cache options for the corresponsing cacheInstanceName
...
services.AddFusionCache(cacheInstanceName)
.WithDefaultEntryOptions(entryOptions)
.WithRegisteredDistributedCache()
.WithCacheKeyPrefix();
}
or you can simply configure them directly for each FusionCache insance like this:
var redisConnString = configuration.GetConnectionString("RedisConnString");
var b = services.AddFusionCache();
if (!string.IsNullOrWhiteSpace(redisConnString)) {
b.WithDistributedCache(
new RedisCache(Options.Create(new RedisCacheOptions() { Configuration = redisConnString })),
new FusionCacheSystemTextJsonSerializer()
)
.WithCacheKeyPrefix()
.WithBackplane(
new RedisBackplane(new RedisBackplaneOptions() { Configuration = redisConnString })
);
}
foreach (...list of cache instances....)
{
...
var cacheInstanceName = "..some name from the loop..."; // the list includes "mainCache"
var entryOptions = new {}; // cache options for the corresponsing cacheInstanceName
...
b = services.AddFusionCache(cacheInstanceName)
.WithDefaultEntryOptions(entryOptions);
if (!string.IsNullOrWhiteSpace(redisConnString)) {
b.WithDistributedCache(
new RedisCache(Options.Create(new RedisCacheOptions() { Configuration = redisConnString })),
new FusionCacheSystemTextJsonSerializer()
)
.WithCacheKeyPrefix()
.WithBackplane(
new RedisBackplane(new RedisBackplaneOptions() { Configuration = redisConnString })
);
}
}
Then, if you declare an ext method like this:
public static IFusionCacheBuilder MaybeWithRedis(this IFusionCacheBuilder builder, string? connString)
{
if (string.IsNullOrWhiteSpace(connString))
return builder;
return builder.WithDistributedCache(
new RedisCache(Options.Create(new RedisCacheOptions() { Configuration = redisConnString })),
new FusionCacheSystemTextJsonSerializer()
)
.WithCacheKeyPrefix()
.WithBackplane(
new RedisBackplane(new RedisBackplaneOptions() { Configuration = redisConnString })
);
}
You can have a (imho) more readable setup like this:
var redisConnString = configuration.GetConnectionString("RedisConnString");
services.AddFusionCache()
.MaybeWithRedis(redisConnString);
foreach (...list of cache instances....)
{
...
var cacheInstanceName = "..some name from the loop..."; // the list includes "mainCache"
var entryOptions = new {}; // cache options for the corresponsing cacheInstanceName
...
services.AddFusionCache(cacheInstanceName)
.WithDefaultEntryOptions(entryOptions)
.MaybeWithRedis(redisConnString);
}
Regarding how to sync only one of the named caches from a specific app/service: in that single app, just configure the one you need and not the others (but I'm not sure if I understood what you are asking).
Regarding the performance: FusionCache uses the underlying technology you picked, which in this case means Redis for the distributed cache + System.Text.Json as a serializer + Redis again as the backplane.
The perf hit should be microscopic, meaning 90%+ of the cpu time is spent on those pieces and not on extra stuff done by FusionCache itself, although you'll get fail-safe, soft/hard timeouts, etc on top of the basic fetures.
For semi-realtime sync, the Redis backplane uses the Pub/Sub mechanism of Redis itself, so the speed depends on the speed of that which, at least in my experience, is pretty darn fast (we are talking about a couple ms
level speed).
Of course though, if you deploy an app service in northeurope
, the database in eastus
and the Redis cache in japaneast
you may have some slowness, it goes without saying 😅
Hope this helps, please let me know if I missed something.
from fusioncache.
Hi @jodydonetti
Thank you very much for the information, I believe that at least a part of this post should be placed into documentation.
Since March I changed a little the code, so now it looks like:
public static void AddCaching(this IServiceCollection services, IConfiguration configuration)
{
var redisConnString = configuration.GetConnectionString("RedisConnString");
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConnString;
});
services.AddFusionCacheSystemTextJsonSerializer(SystemTextJsonExtension.GetCopyOfDefaultJsonSerializerOptions());
services.AddFusionCacheStackExchangeRedisBackplane(options =>
{
options.Configuration = redisConnString;
});
services.AddCachingConfiguration(configuration);
}
public static void AddCachingConfiguration(this IServiceCollection services, IConfiguration configuration)
{
var cachingSection = configuration.GetSection("Caching");
var cachingSettings = cachingSection.Get<CachingSettings>();
var init = new CacheSectionSettings()
{
CacheDuration = TimeSpan.FromMinutes(10),
FailSafeMaxDuration = TimeSpan.FromMinutes(60),
Jittering = TimeSpan.FromSeconds(10),
FactorySoftTimeout = TimeSpan.FromMilliseconds(100),
FactoryHardTimeout = TimeSpan.FromMilliseconds(1500)
};
var defaultSettings = cachingSettings.Values.GetValueOrDefault("Default", init);
var list = FastEnum.GetValues<CachePartition>();
foreach (CachePartition cachePartition in list)
{
var key = cachePartition.GetEnumMemberValue() ?? "Default";
var settingsOptions = cachingSettings.Values.GetValueOrDefault(key, defaultSettings);
var entryOptions = PrepareOptions(settingsOptions);
services.RegisterCacheInstance(cachePartition, entryOptions);
}
}
public static void RegisterCacheInstance(this IServiceCollection services, CachePartition cachePartition,
FusionCacheEntryOptions options)
{
services.AddFusionCache(); // this line is requried even if it is not used
var key = cachePartition.GetEnumMemberValue() ?? "Default";
services.AddFusionCache(key).WithDefaultEntryOptions(options);
}
private static FusionCacheEntryOptions PrepareOptions(CacheSectionSettings settings)
{
var result = new FusionCacheEntryOptions();
if (settings.CacheDuration.HasValue)
{
result.SetDuration(settings.CacheDuration.Value);
}
if (settings.EagerRefresh.HasValue)
{
result.SetEagerRefresh(settings.EagerRefresh.Value);
}
if (settings.Jittering.HasValue)
{
result.SetJittering(settings.Jittering.Value);
}
if (settings.FailSafeMaxDuration.HasValue || settings.FailSafeThrottleDuration.HasValue)
{
result.SetFailSafe(true, settings.FailSafeMaxDuration, settings.FailSafeThrottleDuration);
}
else
{
result.SetFailSafe(false);
}
if (settings.FactorySoftTimeout.HasValue || settings.FactoryHardTimeout.HasValue)
{
result.SetFactoryTimeouts(settings.FactorySoftTimeout, settings.FactoryHardTimeout);
}
return result;
}
I noticed an interesting thing: Default instance (without custom name = "FusionCache" by default) is required.
Thanks for mentioning WithCacheKeyPrefix
it could be useful for sharing Redis between different environments if I understood the idea.
Please correct me if: we can have several environments and each could have the own cluster of several application server instances, and each instance of the server could have several instances of FusionCache. Just by using the prefix-key.
For our purposes, MemoryCache is OK, but we need to sync the changes between all application instances.
On a sample, Given Application 1 (App1) which has 2 or more instances running hidden by load balancer. What we need is to make sure that FusionCache "Partitions" synced instantly, so C1 from Instance1 to C1 from Instance2, and the same for C2.
So it looks like (simplified)
App1
/ \
Instance1 Instance2
/ \ / \
C1 C2 C1 C2
is there a reason why also registering the MemoryDistributedCache?
Of course, no, it was removed, since Redis became mandatory and no such a backup needed.
I'm sorry, I'm confused a little. in your sample you suggest using "fluent" methods .WithRegisteredDistributedCache()
and .WithBackplane()
while in my code I'm using extension for IServiceCollection
. Please explain the difference, which way is preferable (because I took it from the sample from documentation). So, I assume that according to the semantics, the approach I use is to register Redis as a backplane and serializer globally, isn't it?
Or these lines are just add a possibility to use Redis as a backplane and STJ as a serializer, but I need additionally to specify it against each FusionCache "Partition" ?
services.AddFusionCacheSystemTextJsonSerializer(SystemTextJsonExtension.GetCopyOfDefaultJsonSerializerOptions());
services.AddFusionCacheStackExchangeRedisBackplane(options =>
{
options.Configuration = redisConnString;
});
This part is not clear to me, I'd appreciate any comment on this.
Also, the last question (I hope) about the syncing process.
We have 2 instances of the application (A1-1, A1-2) with cache that is syncing.
Step 0.
A1-1: [cacheKey1, cacheKey2, cacheKey3]
A1-2: [cacheKey1, cacheKey2, cacheKey3]
Step 1a
a new key added to cache on A1-2:
A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4]
A1-2: [cacheKey1, cacheKey2, cacheKey3]
Step 1b
Cache synced
A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4]
A1-2: [cacheKey1, cacheKey2, cacheKey3, cacheKey4]
Step 2
One instance (A1-2) restarted
A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4]
A1-2: []
**Q1: is it correct always? Or A1-2 cache will be fully synced against A1-1 when using Redis as L2 cache? Or any other option?**
Step 3
cacheKey2 was placed on A1-2
A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4]
A1-2: [cacheKey2]
**Q2: will the cache key be refreshed on A1-1 here?**
thank you
from fusioncache.
Hi @apavelm
I noticed an interesting thing: Default instance (without custom name = "FusionCache" by default) is required.
No it shouldn't, why are you saying this? Do you have a minimal repro?
Just to doublecheck and to be sure this is not a problem in the future, I just added this test:
[Fact]
public void CanUseNamedCachesWithoutDefaultCache()
{
var services = new ServiceCollection();
services.AddFusionCache("Foo");
services.AddFusionCache("Bar");
using var serviceProvider = services.BuildServiceProvider();
var cacheProvider = serviceProvider.GetRequiredService<IFusionCacheProvider>();
var fooCache = cacheProvider.GetCache("Foo");
var barCache = cacheProvider.GetCache("Bar");
Assert.NotNull(fooCache);
Assert.NotNull(barCache);
}
It passed first try.
Thanks for mentioning
WithCacheKeyPrefix
it could be useful for sharing Redis between different environments if I understood the idea.
It can be used to share the same Redis instance (which may be costly) with different named caches, different environment, etc.
Basically by setting a custom prefix you can be sure there will not be cache-key collisions.
I'm sorry, I'm confused a little. in your sample you suggest using "fluent" methods
.WithRegisteredDistributedCache()
and.WithBackplane()
while in my code I'm using extension forIServiceCollection
. Please explain the difference
The WithRegisteredXyz()
methods will look for the related registered component in the DI container, whereas with the specific WithXyz()
methods you will provide the instance or the factory.
which way is preferable
It depends, there's no "better" in this case, it's really up to you.
Also, the last question (I hope) about the syncing process.
We have 2 instances of the application (A1-1, A1-2) with cache that is syncing. Step 0. A1-1: [cacheKey1, cacheKey2, cacheKey3] A1-2: [cacheKey1, cacheKey2, cacheKey3] Step 1a a new key added to cache on A1-2: A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4] A1-2: [cacheKey1, cacheKey2, cacheKey3] Step 1b Cache synced A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4] A1-2: [cacheKey1, cacheKey2, cacheKey3, cacheKey4] Step 2 One instance (A1-2) restarted A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4] A1-2: [] **Q1: is it correct always? Or A1-2 cache will be fully synced against A1-1 when using Redis as L2 cache? Or any other option?
I think you made a mistake in Step 1a: based on what you wrote I think A1-2 should be the cache with cacheKey4, right?
Apart from this: think about each local memory cache as on its own, they will have some data in them based on when they started and the request they received since then.
If you add a distributed cache (L2), at every operation of the cache FusionCache will:
- also write to the distributed cache, if it's a write operation
- also read from the distributed cache, if it's a read operation AND the data was not found in the memory cache (L1)
What I'm saying is don't try to picture it as a whole, because that's not how it works: it will get/set data at every operation as necessary.
Step 3
cacheKey2 was placed on A1-2
A1-1: [cacheKey1, cacheKey2, cacheKey3, cacheKey4]
A1-2: [cacheKey2]Q2: will the cache key be refreshed on A1-1 here?
In general yes.
In more details here's what happens when Set()
is called on A1-2 (supposing there's a distributed cache and a backplane):
- data is written to memory cache (L1) on A1-2
- data is also written on the distributed cache
- a backplane notification is sent to the other nodes (in this case A1-1)
- memory cache on A1-1 receives the notification, and it checks if it has a cache entry in the memory cache (L1) for that cache key, and:
- if it's there, it updates it from the distributed cache
- if it's not there, it just ignores it (because it will eventually be taken from the distributed cache if needed, only when asked for it)
Makes sense?
Hope this helps.
from fusioncache.
Related Issues (20)
- [FEATURE] Add a method for list all caches in IFusionCacheProvider (Ex: for dispose purposes) HOT 4
- Possibility of creation of a count variable in distributed cache HOT 2
- [FEATURE] Post factory executed event with possibility to change the cache settings before it be inserted in cache HOT 3
- [BUG] Value is stored for longer than FailSafeMaxDuration HOT 7
- [BUG] AllowBackgroundDistributedCacheOperations serialization and cache operations seem to be happening in the foreground HOT 3
- best way to store FusionCacheEntryOptions in iConfiguration HOT 3
- Nullable `Size` option HOT 3
- [BUG] Deadlock caused by cancellation of eager refresh HOT 8
- [BUG] Slow Performance of FusionCache compared to Microsoft.Extensions.Caching.Memory.MemoryCache HOT 4
- [BUG] FailSafe max duration is somehow ignored HOT 7
- [FEATURE] Optimise dependencies by adding TFM HOT 9
- 💡 Add this project to awesome-italia-opensource HOT 2
- [BUG] Possible bug. FusionCache + EntityFramework (latest) on ASP NET Core 8 (.NET 8) HOT 16
- [FEATURE] 🔺 Add .NET Aspire support HOT 3
- [FEATURE] 🔑 Add DI Keyed Services support HOT 18
- [FEATURE] Fall back Defaults for adaptive caching HOT 4
- [FEATURE] How about add compress support for large entries in distributed cache ? HOT 3
- [FEATURE] Utilize Microsoft.IO.RecyclableMemoryStream in flavor of creating new MemoryStream instances when serializing HOT 12
- [FEATURE] ⚡ Add `PreferSyncSerialization` option HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fusioncache.