-
Notifications
You must be signed in to change notification settings - Fork 31
Add Redis provider with batching support #45
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
Conversation
Nice! I was doing the same thing, but as a fork of https://github.com/TheCloudlessSky/NHibernate.Caches.Redis. I think this feels a bit fresher, and there will be no conflict with that author. I will review it and test it in a real project, which currently is stuck in NH4. |
I think we should not shorten the name of the library it uses. It should be (Or maybe even Note that About the JSON serializer, the main issue is testing it. Implementation should be as easy as referencing Newtonsoft Json.Net and using it directly without any additional options. But note that "basic types" also include |
As After better looking at the current |
I was hoping for NHibernate.Caches.StackExchange.Redis, but NHibernate.Caches.StackExchangeRedis will do just fine. |
I have overlooked that the state was encapsulated in these There also seems to have an issue for many numeric types by default: Json.Net is said by your link as not preserving their exact type (which is not surprising with an object array), causing failures on assembling. So indeed a custom Json serialization would need to be provided. Maybe you will have better to just provide a way to inject a custom serializer. Update: full list of custom classes sent as cache values:
|
StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs
Outdated
Show resolved
Hide resolved
StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs
Outdated
Show resolved
Hide resolved
StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs
Outdated
Show resolved
Hide resolved
protected virtual void Start(string configurationString, IDictionary<string, string> properties, TextWriter textWriter) | ||
{ | ||
var configuration = ConfigurationOptions.Parse(configurationString); | ||
_connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's great that the ConnectionMultiplexer can be configured and instantiated like this. However, ConnectionMultiplexers should be "as singleton as possible". It would be nice to have a way to provide it programmatically, if there is already an instance that you want to use. This could be done like in the other provider (NHibernate.Caches.Redis), which has a static SetConnectionMultiplexer method, but even better would be a callback. Another callback could be used intercept GetDatabase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion the right way to go is to have an additional constructor that takes the ConnectionMultiplexer
as a parameter. I know that NHibernate currently does not have a simple way to set the IObjectsFactory which would allow us to use our favourite IoC container. My plan would be to provide a PR to decuple the IObjectsFactory
from BytecodeProvider
and then add the extra constructor with the ConnectionMultiplexer
if you agree? This is an example how the final solution would look like:
container.RegisterSingleton(myConnectionMultiplexer);
Cfg.Environment.ObjectsFactory = new MyObjectsFactory(container);
... // build a session factory
// The constructor with the ConnectionMultiplexer parameter would be called by the IoC container
For GetDatabase
I would rather provide a IRedisDatabaseProvider
interface that would look like:
public interface IRedisDatabaseProvider {
IDatabase GetDatabase(ConnectionMultiplexer connectionMultiplexer, int database)
}
which could be configured in the configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if the IoC experience gets better (there's so much static stuff in the initialization...), that would be a good way. Probably using IConnectionMultiplexer, instead of ConnectionMultiplexer, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wrong about saying that add an additional constructor overload is the way to as it is considered an anti-pattern. You mentioned about configuring it with a callback, but how would that callback be set, statically?
A possible solution to avoid having static configuration methods and multiple constructors would be to have an interface similar to what I proposed for the GetDatabase
:
public interface IConnectionMultiplexerProvider {
IConnectionMultiplexer GetConnectionMultiplexer(IDictionary<string, string> properties);
}
that could be configured by a NHibernate configuration property or by an IoC container, example with an IoC container:
// Create a custom connection multiplexer and register it as a singleton
IConnectionMultiplexer connectionMultiplexer = CreateConnection();
container.RegisterSingleton(connectionMultiplexer);
// MyConnectionMultiplexerProvider would have the IConnectionMultiplexer as parameter in the
// constructor that would be then used as a return value in GetConnectionMultiplexer method
container.Register<IConnectionMultiplexerProvider, MyConnectionMultiplexerProvider>();
// Setup the ObjectsFactory with a custom one that uses our favorite IoC container
Cfg.Environment.ObjectsFactory = new MyObjectsFactory(container);
nhConfig.BuildSessionFactory(); // build a session factory
There is already a configuration to set the serializer (see |
Yes, agreed. And such a NHibernate compatible serializer could eventually be provided as maybe something like a Update: there are more classes to handle than the |
I've added tests for serialization of NHibernate types and as expected they throws for types that @fredericDelaporte mentioned. With nhibernate/nhibernate-core#1765 the broken tests should be fixed. |
Unfortunately, I was wrong about stating that I could make |
As with NHibernate 5.2 the serialization of cache types will be more user friendly I made a json serializer to see how would impact the overall performance. The serializer reduces the json size by mapping the registered types to shorten names as they have to be included in the json in order to deserialize them correctly. For the following test the Redis cache was located on a different local machine in order to simulate a real environment. Results with the json serializer:
Results with the binary serializer:
As we can see for single operations the json serializer is up to 7% faster which is not that much, but for batch operations is up to 90% faster. If you agree I can prepare a different PR for having the mentioned serializer in a separate package |
Yes, go for it. I also wonder if we should add some |
In the current state the cache provider can be configured in several ways, let's suppose that we want to configure the 1. Register it in the NH configuration
2. Extending the Extended class:
NH configuration:
3. Register it with a custom
4. Configure the Configure
In order to work correctly we have to alter the NH configuration by setting the interface as the provider class:
For the last example, I think it would be nice if NHibernate would try to get the type by the interface when a custom Update: Added a fifth option 5. Configure
|
This comment has been minimized.
This comment has been minimized.
Another way in order to avoid having try/catches and a user mapping api for registering implementations/interfaces would be to use the IServiceProvider interface, which is We could obsolete the |
@fredericDelaporte , @gliljas , @maca88 Is this pr about second level cache get multiple records with single request? (about 50 - 150 record) In this scenario is Redis right choice?
|
Currently, caches do not support batching of operations, and do them all one by one. Depending on the application usage pattern, a distributed second level cache can have worst performances than not using the second level cache. This happens when the cache usage causes a lot of cache operations for just a few SQL queries avoided: when distributed, the IO cost of the many cache operations may overrun the savings bound to not running even complex SQL queries. In such case, it is better to not use a distributed cache, and this is not specific to Redis. Better use an in-memory cache then, or not use the second level cache at all. nhibernate/nhibernate-core#1633 add support for batching some operations together, in NHibernate 5.2. For NHibernate-Caches updated to support batching, this will reduces the cases where using a distributed cache is worst than not using one. This #45 PR does that for a new Redis provider. The already existing one, CoreDistributedCache.Redis, cannot support batching, because it uses an abstraction, |
Yes it is.
Yes, with NHibernate 5.2 and having the provider from this PR, the records for the cached query will be retrieved in batches.
|
…e of the key to the cache key
…lexer instance by code or NH configuration.
And some cleanup
bf58bdc
to
832b216
Compare
Rebased for solving conflicts with the latest merge. |
All TODOs are done, now it is ready to be reviewed. |
@@ -22,7 +22,7 @@ | |||
<ItemGroup> | |||
<PackageReference Include="Iesi.Collections" Version="4.0.4" /> | |||
<PackageReference Include="NHibernate" Version="5.2.0" /> | |||
<PackageReference Include="StackExchange.Redis.StrongName" Version="1.2.6" /> | |||
<PackageReference Include="StackExchange.Redis" Version="2.0.495" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With version 2.0 the .StrongName
package does not exist anymore as StackExchange.Redis
is now strong-named.
@@ -13,7 +13,6 @@ public IConnectionMultiplexer Get(string configuration) | |||
{ | |||
TextWriter textWriter = Log.IsDebugEnabled() ? new NHibernateTextWriter(Log) : null; | |||
var connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); | |||
connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is obsolete with 2.0, and is false by default (see here for more info).
And some additional minor clean-up
@maca88, do you agree with the removal of |
I agree, we will add it when NHibernate will support it. |
This PR contains a
Redis
provider forNHibernate
which usesStackExchange.Redis
as the client. Key features:5.2
Clear
operationClear
operationThe async code was generated with a non yet released version that supports searching async counterparts in inherited types, used to generate async counterparts of
IDatabase
, located inIDatabaseAsync
.In comparison to the currently most popular provider, this provider does not suffer of producing a memory leak over time as it uses a different strategy where only one key per region is added without expiration that contains the current valid version (see
DefaultRegionStrategy
).Open questions:
Should we provide a JSON serializer as it is stated that is faster than the currently default binary serializer and if yes, how?
What should be the final package name?
TODO:
NHibernate 5.2
release and update the code in order to use theIBatchableCache
ICache.Lock
andICache.Unlock
thread safe