Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Distributed Caching bypass capability #10297

Closed
wants to merge 2 commits into from
Closed

Support Distributed Caching bypass capability #10297

wants to merge 2 commits into from

Conversation

ShaneCourtrille
Copy link
Contributor

@ShaneCourtrille ShaneCourtrille commented Sep 17, 2021

As per the request in #10197 I've created an initial PR so we can discuss the changes necessary to improve system stability when Redis is unavailable.

I'm still unsure on the delay when an exception occurs. If it should move into config or if it should use an increasing time as per the RedisLock.

Encapsulates distributed caching and adds cache bypass when Redis has an issue
@dnfadmin
Copy link

CLA assistant check
Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.

❌ ShaneCourtrille sign now
You have signed the CLA already but the status is still pending? Let us recheck it.

@Skrypt
Copy link
Contributor

Skrypt commented Sep 18, 2021

I like the idea of making it redundant to make sure it will work properly. Though, I think the implementation you did forces us to use a Redis Cache while the IDistributedCache should be abstract.


namespace OrchardCore.Caching.Distributed
{
public interface IDistributedCache
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the problem is that if you use the same interface and AddStackExchangeRedis() you either have to extract/wrap the dependency it creates OR stop using that call and recreate it.

I actually thought of a solution last night.. in the constructor for DocumentManager it could ask for a MS IDistributedCache and then wrap it as long as I changed the RedisDistributedCache into a more generic version.

using Microsoft.Extensions.Caching.Distributed;
using OrchardCore.Modules;

namespace OrchardCore.Redis.Caching
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move it to the Services folder where we already have RedisBus and RedisLock.

public RedisDistributedCache(Microsoft.Extensions.Caching.Distributed.IDistributedCache cache, IClock clock)
{
_underlyingCache = cache;
_clock = clock;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated by @Skrypt we don't need to define a new interface

Here we could inject IOptions<RedisCacheOptions> optionsAccessor so that we can create an inner _redisCache without having to resolve it, just by doing _redisCache = new RedisCache(optionsAccessor); https://github.com/dotnet/aspnetcore/blob/db4dc17779dbfbe96b94ad33bc02a368b4a94463/src/Caching/StackExchangeRedis/src/RedisCache.cs#L49

Then, in the redis module startup, not call AddStackExchangeRedisCache() that registers the RedisCache
https://github.com/dotnet/aspnetcore/blob/db4dc17779dbfbe96b94ad33bc02a368b4a94463/src/Caching/StackExchangeRedis/src/StackExchangeRedisCacheServiceCollectionExtensions.cs#L23-L40

And do what AddStackExchangeRedisCache() does unless that we register our wrapper

        services.AddOptions(); // <= not necessary here
        services.Configure(setupAction); // <= idem, so I think only the following line
        services.Add(ServiceDescriptor.Singleton<IDistributedCache, RedisCacheService>());

Then we would have to implement IDisposable so that we can dispose our inner _redisCache.

public class RedisCacheService : IDistributedCache, IDisposable

Not sure yet of the best name ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the code doesn't actually depend on anything Redis specific what if I made it more generic?

The one problem I see with that is I don't have a 'clean room' code implementation of wrapping an existing dependency in services collection (I have an implementation I grabbed from somewhere else so the rights get tricky I think).

A semi dirty way around that would be to just wrap it in the DocumentManagers constructor in the if (distributedCache == typeof(MemoryCacheDistributedCache) {} block. I'm not the biggest fan of that though if someone can provide a clean services.AddDecorator(..) extension method then that would be preferred?

CheckDistributedErrorRetry();

if(_redisUnavailable)
return Array.Empty<byte>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an .editorconfig file at the root so that e.g. under Visual studio you get some warnings about code style rules. Here, always use braces { and } for if blocks and on new lines

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .editorconfig file isn't working locally for me which is odd. It has the root=true and I've verified that my 'Follow project coding conventions' option is enabled.

if(_redisUnavailable)
return Array.Empty<byte>();

try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open brace on new line

catch (Exception)
{
RedisCacheFailed();
return Array.Empty<byte>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe return null

CheckDistributedErrorRetry();

if(_redisUnavailable)
return Task.FromResult(Array.Empty<byte>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use braces and on newlines

if(_redisUnavailable)
return Task.FromResult(Array.Empty<byte>());

try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open brace on new line


try {
return _underlyingCache.GetAsync(key, token);
} catch (Exception) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem for all other ones

@jtkech
Copy link
Member

jtkech commented Sep 19, 2021

@ShaneCourtrille

Just did a first review, will continue when I will have time

return;

// TODO: move to configuration or should this be an increasing number like RedisLock?
_retryAt = _clock.UtcNow.AddSeconds(15);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I will think about it

@ShaneCourtrille
Copy link
Contributor Author

I like the idea of making it redundant to make sure it will work properly. Though, I think the implementation you did forces us to use a Redis Cache while the IDistributedCache should be abstract.

I don't think it forces Redis BUT there isn't actually anything Redis specific in the code now so the naming/location might not make sense anymore. Originally I was thinking I'd want to look for RedisConnectionExceptions and RedisConnectionTImeouts but since that never ended up happening I could just rename/move things so that its a more generic DistributedCacheErrorWrapper or whatever better name someone can come up with?

@ShaneCourtrille
Copy link
Contributor Author

ShaneCourtrille commented Sep 20, 2021

@jtkech @Skrypt I've created an alternative implementation in PR #10313 which is much cleaner except for one problem with dependency injection that I get around via a bit of ugly manual object creation.

Can you let me know which one makes more sense to focus on?

@Piedone
Copy link
Member

Piedone commented Jan 18, 2024

Closing as per #10313 (comment).

@Piedone Piedone closed this Jan 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants