Browse Source

Options for replicated cache.

pull/546/head
Sebastian 6 years ago
parent
commit
788ec0fcca
  1. 33
      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. 9
      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

33
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,17 +25,24 @@ namespace Squidex.Infrastructure.Caching
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(pubSub, nameof(pubSub));
Guard.NotNull(options, nameof(options));
this.memoryCache = memoryCache;
this.pubSub = pubSub;
if (options.Value.Enable)
{
this.pubSub.Subscribe(OnMessage);
}
this.options = options.Value;
}
private void OnMessage(object message)
{
if (message is InvalidateMessage invalidate && invalidate.Source != instanceId)
@ -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 });
}
}

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
{
public sealed class SimplePubSub : IPubSub
public class SimplePubSub : IPubSub
{
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)
{
@ -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));

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

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

9
backend/src/Squidex/appsettings.json

@ -39,7 +39,14 @@
/*
* 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": {

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))
.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);
}

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))
.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);
}

59
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<SimplePubSub>(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<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)
{
var found = cache.TryGetValue(key, out var value);

Loading…
Cancel
Save