From 788ec0fccab5ec13c985708b2dc21b72726ece12 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 14 Jul 2020 16:57:21 +0200 Subject: [PATCH] Options for replicated cache. --- .../Caching/ReplicatedCache.cs | 35 ++++++++++- .../Caching/ReplicatedCacheOptions.cs | 14 +++++ .../Caching/SimplePubSub.cs | 6 +- .../Config/Domain/InfrastructureServices.cs | 2 + backend/src/Squidex/appsettings.json | 13 +++- .../Apps/Indexes/AppsIndexTests.cs | 4 +- .../Schemas/Indexes/SchemasIndexTests.cs | 4 +- .../Caching/ReplicatedCacheTests.cs | 59 ++++++++++++++++--- 8 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs diff --git a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs b/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs index e9ded4109..81c87e7ba 100644 --- a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs +++ b/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs @@ -7,6 +7,7 @@ using System; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; namespace Squidex.Infrastructure.Caching { @@ -15,6 +16,7 @@ namespace Squidex.Infrastructure.Caching private readonly Guid instanceId = Guid.NewGuid(); private readonly IMemoryCache memoryCache; private readonly IPubSub pubSub; + private readonly ReplicatedCacheOptions options; public class InvalidateMessage { @@ -23,15 +25,22 @@ namespace Squidex.Infrastructure.Caching public string Key { get; set; } } - public ReplicatedCache(IMemoryCache memoryCache, IPubSub pubSub) + public ReplicatedCache(IMemoryCache memoryCache, IPubSub pubSub, IOptions options) { Guard.NotNull(memoryCache, nameof(memoryCache)); Guard.NotNull(pubSub, nameof(pubSub)); + Guard.NotNull(options, nameof(options)); this.memoryCache = memoryCache; this.pubSub = pubSub; - this.pubSub.Subscribe(OnMessage); + + if (options.Value.Enable) + { + this.pubSub.Subscribe(OnMessage); + } + + this.options = options.Value; } private void OnMessage(object message) @@ -44,6 +53,11 @@ namespace Squidex.Infrastructure.Caching public void Add(string key, object? value, TimeSpan expiration, bool invalidate) { + if (!options.Enable) + { + return; + } + memoryCache.Set(key, value, expiration); if (invalidate) @@ -54,6 +68,11 @@ namespace Squidex.Infrastructure.Caching public void Remove(string key) { + if (!options.Enable) + { + return; + } + memoryCache.Remove(key); Invalidate(key); @@ -61,11 +80,23 @@ namespace Squidex.Infrastructure.Caching public bool TryGetValue(string key, out object? value) { + if (!options.Enable) + { + value = null; + + return false; + } + return memoryCache.TryGetValue(key, out value); } private void Invalidate(string key) { + if (!options.Enable) + { + return; + } + pubSub.Publish(new InvalidateMessage { Key = key, Source = instanceId }); } } diff --git a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs b/backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs new file mode 100644 index 000000000..d1d1d9f48 --- /dev/null +++ b/backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Caching +{ + public sealed class ReplicatedCacheOptions + { + public bool Enable { get; set; } + } +} diff --git a/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs b/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs index 7a30c2ddb..21383ee0d 100644 --- a/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs +++ b/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs @@ -10,11 +10,11 @@ using System.Collections.Generic; namespace Squidex.Infrastructure.Caching { - public sealed class SimplePubSub : IPubSub + public class SimplePubSub : IPubSub { private readonly List> handlers = new List>(); - public void Publish(object message) + public virtual void Publish(object message) { foreach (var handler in handlers) { @@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.Caching } } - public void Subscribe(Action handler) + public virtual void Subscribe(Action handler) { Guard.NotNull(handler, nameof(handler)); diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index debaf394e..d855656a8 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -46,6 +46,8 @@ namespace Squidex.Config.Domain config.GetSection("urls")); services.Configure( config.GetSection("exposedConfiguration")); + services.Configure( + config.GetSection("caching:replicated")); services.AddSingletonAs(_ => SystemClock.Instance) .As(); diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index ccf79cf30..dc5c916d4 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -39,15 +39,22 @@ /* * Restrict the surrogate keys to 17KB. */ - "maxSurrogateKeysSize": 8000 + "maxSurrogateKeysSize": 8000, + + "replicated": { + /* + * Set to true to enable a replicated cache for app, schemas and rules. Increases performance but reduces consistency. + */ + "enable": true + } }, "languages": { /* * Use custom langauges where the key is the language code and the value is the english name. */ - "custom": "" - }, + "custom": "" + }, "rules": { /* diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs index e467d224a..38fb6b7aa 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs @@ -44,7 +44,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes A.CallTo(() => grainFactory.GetGrain(userId, null)) .Returns(indexByUser); - var cache = new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub()); + var cache = + new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub(), + Options.Create(new ReplicatedCacheOptions { Enable = true })); sut = new AppsIndex(grainFactory, cache); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs index 834c919ed..434754998 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs @@ -39,7 +39,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes A.CallTo(() => grainFactory.GetGrain(appId.Id, null)) .Returns(index); - var cache = new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub()); + var cache = + new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub(), + Options.Create(new ReplicatedCacheOptions { Enable = true })); sut = new SchemasIndex(grainFactory, cache); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs index b354bb1a7..58a60a841 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs @@ -7,6 +7,7 @@ using System; using System.Threading.Tasks; +using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Xunit; @@ -15,12 +16,13 @@ namespace Squidex.Infrastructure.Caching { public class ReplicatedCacheTests { - private readonly IPubSub pubSub = new SimplePubSub(); + private readonly IPubSub pubSub = A.Fake(options => options.CallsBaseMethods()); + private readonly ReplicatedCacheOptions options = new ReplicatedCacheOptions { Enable = true }; private readonly ReplicatedCache sut; public ReplicatedCacheTests() { - sut = new ReplicatedCache(CreateMemoryCache(), pubSub); + sut = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); } [Fact] @@ -36,7 +38,17 @@ namespace Squidex.Infrastructure.Caching } [Fact] - public async Task Should_not_served_when_expired() + public void Should_not_serve_from_cache_disabled() + { + options.Enable = false; + + sut.Add("Key", 1, TimeSpan.FromMilliseconds(100), true); + + AssertCache(sut, "Key", null, false); + } + + [Fact] + public async Task Should_not_serve_from_cache_when_expired() { sut.Add("Key", 1, TimeSpan.FromMilliseconds(1), true); @@ -48,8 +60,8 @@ namespace Squidex.Infrastructure.Caching [Fact] public void Should_not_invalidate_other_instances_when_item_added_and_flag_is_false() { - var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub); - var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub); + var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); + var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); cache1.Add("Key", 1, TimeSpan.FromMinutes(1), false); cache2.Add("Key", 2, TimeSpan.FromMinutes(1), false); @@ -61,8 +73,8 @@ namespace Squidex.Infrastructure.Caching [Fact] public void Should_invalidate_other_instances_when_item_added_and_flag_is_true() { - var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub); - var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub); + var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); + var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true); cache2.Add("Key", 2, TimeSpan.FromMinutes(1), true); @@ -74,8 +86,8 @@ namespace Squidex.Infrastructure.Caching [Fact] public void Should_invalidate_other_instances_when_item_removed() { - var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub); - var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub); + var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); + var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true); cache2.Remove("Key"); @@ -84,6 +96,35 @@ namespace Squidex.Infrastructure.Caching AssertCache(cache2, "Key", null, false); } + [Fact] + public void Should_send_invalidation_message_when_added_and_flag_is_true() + { + sut.Add("Key", 1, TimeSpan.FromMinutes(1), true); + + A.CallTo(() => pubSub.Publish(A._)) + .MustHaveHappened(); + } + + [Fact] + public void Should_not_send_invalidation_message_when_added_flag_is_false() + { + sut.Add("Key", 1, TimeSpan.FromMinutes(1), false); + + A.CallTo(() => pubSub.Publish(A._)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_not_send_invalidation_message_when_added_but_disabled() + { + options.Enable = false; + + sut.Add("Key", 1, TimeSpan.FromMinutes(1), true); + + A.CallTo(() => pubSub.Publish(A._)) + .MustNotHaveHappened(); + } + private static void AssertCache(IReplicatedCache cache, string key, object? expectedValue, bool expectedFound) { var found = cache.TryGetValue(key, out var value);