Browse Source

Options for replicated cache.

pull/546/head
Sebastian 6 years ago
parent
commit
788ec0fcca
  1. 35
      backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs
  2. 14
      backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs
  3. 6
      backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs
  4. 2
      backend/src/Squidex/Config/Domain/InfrastructureServices.cs
  5. 13
      backend/src/Squidex/appsettings.json
  6. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs
  7. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs
  8. 59
      backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs

35
backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs

@ -7,6 +7,7 @@
using System; using System;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
namespace Squidex.Infrastructure.Caching namespace Squidex.Infrastructure.Caching
{ {
@ -15,6 +16,7 @@ namespace Squidex.Infrastructure.Caching
private readonly Guid instanceId = Guid.NewGuid(); private readonly Guid instanceId = Guid.NewGuid();
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly IPubSub pubSub; private readonly IPubSub pubSub;
private readonly ReplicatedCacheOptions options;
public class InvalidateMessage public class InvalidateMessage
{ {
@ -23,15 +25,22 @@ namespace Squidex.Infrastructure.Caching
public string Key { get; set; } public string Key { get; set; }
} }
public ReplicatedCache(IMemoryCache memoryCache, IPubSub pubSub) public ReplicatedCache(IMemoryCache memoryCache, IPubSub pubSub, IOptions<ReplicatedCacheOptions> options)
{ {
Guard.NotNull(memoryCache, nameof(memoryCache)); Guard.NotNull(memoryCache, nameof(memoryCache));
Guard.NotNull(pubSub, nameof(pubSub)); Guard.NotNull(pubSub, nameof(pubSub));
Guard.NotNull(options, nameof(options));
this.memoryCache = memoryCache; this.memoryCache = memoryCache;
this.pubSub = pubSub; this.pubSub = pubSub;
this.pubSub.Subscribe(OnMessage);
if (options.Value.Enable)
{
this.pubSub.Subscribe(OnMessage);
}
this.options = options.Value;
} }
private void OnMessage(object message) private void OnMessage(object message)
@ -44,6 +53,11 @@ namespace Squidex.Infrastructure.Caching
public void Add(string key, object? value, TimeSpan expiration, bool invalidate) public void Add(string key, object? value, TimeSpan expiration, bool invalidate)
{ {
if (!options.Enable)
{
return;
}
memoryCache.Set(key, value, expiration); memoryCache.Set(key, value, expiration);
if (invalidate) if (invalidate)
@ -54,6 +68,11 @@ namespace Squidex.Infrastructure.Caching
public void Remove(string key) public void Remove(string key)
{ {
if (!options.Enable)
{
return;
}
memoryCache.Remove(key); memoryCache.Remove(key);
Invalidate(key); Invalidate(key);
@ -61,11 +80,23 @@ namespace Squidex.Infrastructure.Caching
public bool TryGetValue(string key, out object? value) public bool TryGetValue(string key, out object? value)
{ {
if (!options.Enable)
{
value = null;
return false;
}
return memoryCache.TryGetValue(key, out value); return memoryCache.TryGetValue(key, out value);
} }
private void Invalidate(string key) private void Invalidate(string key)
{ {
if (!options.Enable)
{
return;
}
pubSub.Publish(new InvalidateMessage { Key = key, Source = instanceId }); pubSub.Publish(new InvalidateMessage { Key = key, Source = instanceId });
} }
} }

14
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; }
}
}

6
backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs

@ -10,11 +10,11 @@ using System.Collections.Generic;
namespace Squidex.Infrastructure.Caching namespace Squidex.Infrastructure.Caching
{ {
public sealed class SimplePubSub : IPubSub public class SimplePubSub : IPubSub
{ {
private readonly List<Action<object>> handlers = new List<Action<object>>(); private readonly List<Action<object>> handlers = new List<Action<object>>();
public void Publish(object message) public virtual void Publish(object message)
{ {
foreach (var handler in handlers) foreach (var handler in handlers)
{ {
@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.Caching
} }
} }
public void Subscribe(Action<object> handler) public virtual void Subscribe(Action<object> handler)
{ {
Guard.NotNull(handler, nameof(handler)); Guard.NotNull(handler, nameof(handler));

2
backend/src/Squidex/Config/Domain/InfrastructureServices.cs

@ -46,6 +46,8 @@ namespace Squidex.Config.Domain
config.GetSection("urls")); config.GetSection("urls"));
services.Configure<ExposedConfiguration>( services.Configure<ExposedConfiguration>(
config.GetSection("exposedConfiguration")); config.GetSection("exposedConfiguration"));
services.Configure<ReplicatedCacheOptions>(
config.GetSection("caching:replicated"));
services.AddSingletonAs(_ => SystemClock.Instance) services.AddSingletonAs(_ => SystemClock.Instance)
.As<IClock>(); .As<IClock>();

13
backend/src/Squidex/appsettings.json

@ -39,15 +39,22 @@
/* /*
* Restrict the surrogate keys to 17KB. * 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": { "languages": {
/* /*
* Use custom langauges where the key is the language code and the value is the english name. * Use custom langauges where the key is the language code and the value is the english name.
*/ */
"custom": "" "custom": ""
}, },
"rules": { "rules": {
/* /*

4
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<IAppsByUserIndexGrain>(userId, null)) A.CallTo(() => grainFactory.GetGrain<IAppsByUserIndexGrain>(userId, null))
.Returns(indexByUser); .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); sut = new AppsIndex(grainFactory, cache);
} }

4
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<ISchemasByAppIndexGrain>(appId.Id, null)) A.CallTo(() => grainFactory.GetGrain<ISchemasByAppIndexGrain>(appId.Id, null))
.Returns(index); .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); sut = new SchemasIndex(grainFactory, cache);
} }

59
backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs

@ -7,6 +7,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Xunit; using Xunit;
@ -15,12 +16,13 @@ namespace Squidex.Infrastructure.Caching
{ {
public class ReplicatedCacheTests public class ReplicatedCacheTests
{ {
private readonly IPubSub pubSub = new SimplePubSub(); private readonly IPubSub pubSub = A.Fake<SimplePubSub>(options => options.CallsBaseMethods());
private readonly ReplicatedCacheOptions options = new ReplicatedCacheOptions { Enable = true };
private readonly ReplicatedCache sut; private readonly ReplicatedCache sut;
public ReplicatedCacheTests() public ReplicatedCacheTests()
{ {
sut = new ReplicatedCache(CreateMemoryCache(), pubSub); sut = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
} }
[Fact] [Fact]
@ -36,7 +38,17 @@ namespace Squidex.Infrastructure.Caching
} }
[Fact] [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); sut.Add("Key", 1, TimeSpan.FromMilliseconds(1), true);
@ -48,8 +60,8 @@ namespace Squidex.Infrastructure.Caching
[Fact] [Fact]
public void Should_not_invalidate_other_instances_when_item_added_and_flag_is_false() public void Should_not_invalidate_other_instances_when_item_added_and_flag_is_false()
{ {
var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub); var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub); var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
cache1.Add("Key", 1, TimeSpan.FromMinutes(1), false); cache1.Add("Key", 1, TimeSpan.FromMinutes(1), false);
cache2.Add("Key", 2, TimeSpan.FromMinutes(1), false); cache2.Add("Key", 2, TimeSpan.FromMinutes(1), false);
@ -61,8 +73,8 @@ namespace Squidex.Infrastructure.Caching
[Fact] [Fact]
public void Should_invalidate_other_instances_when_item_added_and_flag_is_true() public void Should_invalidate_other_instances_when_item_added_and_flag_is_true()
{ {
var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub); var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub); var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true); cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true);
cache2.Add("Key", 2, TimeSpan.FromMinutes(1), true); cache2.Add("Key", 2, TimeSpan.FromMinutes(1), true);
@ -74,8 +86,8 @@ namespace Squidex.Infrastructure.Caching
[Fact] [Fact]
public void Should_invalidate_other_instances_when_item_removed() public void Should_invalidate_other_instances_when_item_removed()
{ {
var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub); var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub); var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options));
cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true); cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true);
cache2.Remove("Key"); cache2.Remove("Key");
@ -84,6 +96,35 @@ namespace Squidex.Infrastructure.Caching
AssertCache(cache2, "Key", null, false); 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<object>._))
.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<object>._))
.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<object>._))
.MustNotHaveHappened();
}
private static void AssertCache(IReplicatedCache cache, string key, object? expectedValue, bool expectedFound) private static void AssertCache(IReplicatedCache cache, string key, object? expectedValue, bool expectedFound)
{ {
var found = cache.TryGetValue(key, out var value); var found = cache.TryGetValue(key, out var value);

Loading…
Cancel
Save