-
Notifications
You must be signed in to change notification settings - Fork 926
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
Implement multiple get and put for query cache and query batch #1788
Implement multiple get and put for query cache and query batch #1788
Conversation
a721e4f
to
38c3c53
Compare
/// from an <see cref="IEntityPersister"/> or <see cref="ICollectionPersister"/>. | ||
/// When a different persister or a different operation is added to the batch, the current batch will be executed. | ||
/// </summary> | ||
internal partial class CacheBatcher | ||
public sealed partial class CacheBatcher |
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.
For enabling batching of puts across batched queries, I had to transmit a CacheBatcher
with a public interface method, so I had to switch this type to public.
@@ -34,7 +30,7 @@ public CacheBatcher(ISessionImplementor session) | |||
/// </summary> | |||
/// <param name="persister">The entity persister.</param> | |||
/// <param name="data">The data to put in the cache.</param> | |||
public void AddToBatch(IEntityPersister persister, CachePutData data) | |||
internal void AddToBatch(IEntityPersister persister, CachePutData data) |
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.
CachePutData
being internal, the method has to be internal too since the declaring class is no more internal but public. For consistency and has users should not need to use directly the class, but just have to pass it around, I have switched all the methods to internal.
{ | ||
private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof (StandardQueryCache)); | ||
private readonly ICache _queryCache; | ||
private readonly IBatchableReadOnlyCache _batchableReadOnlyCache; | ||
private readonly IBatchableCache _batchableCache; |
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.
#1777 (CacheBase
) would allow a simpler implementation here.
if (queryParameters.IsReadOnlyInitialized) | ||
persistenceContext.DefaultReadOnly = queryParameters.ReadOnly; | ||
else | ||
queryParameters.ReadOnly = persistenceContext.DefaultReadOnly; |
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.
Here is the reason for the need of QueryParameters
: handling the readonly flag for loading entities. With single gets, it can be done on the caller side.
But with multiple gets, it has to be done on the callee side. Unless gets are sorted out according to their readonly status matching or not the one of the persistence context (which is not trivial to handle) and then called as separated multiple gets. I considered it would complicate the code a bit to much and so choose to move this handling in the query cache.
(For the record, multi-query/criteria were not handling the readonly flag of each query they were containing.)
src/NHibernate/Cache/IQueryCache.cs
Outdated
bool Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool isNaturalKeyLookup, ISessionImplementor session); | ||
// Since 5.2 | ||
[Obsolete("Have the query cache implement IBatchableQueryCache, and use IBatchableQueryCache.Get")] | ||
IList Get(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyLookup, ISet<string> spaces, ISessionImplementor session); |
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 order to properly handle the multi-get cases, I had to transmit the QueryParameters
. So for consistency, I have made the same change to the single Get
and to Put
methods.
src/NHibernate/Multi/QueryBatch.cs
Outdated
@@ -44,18 +47,17 @@ public void Execute() | |||
{ | |||
Init(); | |||
|
|||
if (!Session.Factory.ConnectionProvider.Driver.SupportsMultipleQueries) | |||
using (Session.BeginProcess()) |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
src/NHibernate/Multi/QueryBatch.cs
Outdated
using (Session.BeginProcess()) | ||
{ | ||
DoExecute(); | ||
ExecuteBatched(); |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
src/NHibernate/Multi/QueryBatch.cs
Outdated
{ | ||
// The auto-flush must be handled before obtaining the commands, because an auto-flush | ||
// may have to invalidate cached data which otherwise would cause a command to be skipped. | ||
Session.AutoFlushIfRequired(querySpaces); |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
src/NHibernate/Multi/QueryBatch.cs
Outdated
stopWatch.Start(); | ||
} | ||
var resultSetsCommand = Session.Factory.ConnectionProvider.Driver.GetResultSetsCommand(Session); | ||
CombineQueries(resultSetsCommand, GetCachedResults()); |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
if (index == _queryInfos.Count - 1) | ||
{ | ||
InitializeEntitiesAndCollections(reader, hydratedObjects); | ||
} |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
// the UnresolvableObjectException could occur while resolving | ||
// associations, leaving the PC in an inconsistent state | ||
Log.Debug(ex, "could not reassemble cached result set"); | ||
// TODO: add and handle a RemoveMany? |
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 forgot about RemoveMany
when doing #1633 as it was not needed there, but it is already implemented in nhibernate/NHibernate-Caches#45, so why not add it if it can be used here.
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 have put a question mark because this case is almost an error case, and it does not look likely to me it would benefit from batching. It should not happen frequently, and even less frequently would it benefit from a Many
handling, since the first encountered one short-circuit the result-set processing: it could be batched only in multi-queries case, if many queries in it encounter a "no more resolvable entity reference" case.
So in fact I should change that comment, I do not think it would be worth it to handle a many case here.
IEnumerable<ISqlCommand> GetCommands(); | ||
/// <param name="cachedResults">The cached results.</param> | ||
/// <returns>The commands for obtaining the results not already cached.</returns> | ||
IEnumerable<ISqlCommand> GetCommands(IDictionary<ICachingInformation, IList> cachedResults); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Additional cleanup/doc originating from nhibernate#1788 but which should be here
1be92d8
to
5d4f46f
Compare
Squashed and rebased for resolving conflicts with #1789. |
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.
Some code stlyles
/// </summary> | ||
public partial interface IQueryCache | ||
{ | ||
/// <summary> | ||
/// The underlying <see cref="ICache"/>. | ||
/// </summary> | ||
ICache Cache { get; } |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
src/NHibernate/Cache/IQueryCache.cs
Outdated
void Clear(); | ||
// Since 5.2 |
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.
Need a new line before
src/NHibernate/Cache/IQueryCache.cs
Outdated
bool Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool isNaturalKeyLookup, ISessionImplementor session); | ||
// Since 5.2 |
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.
Need a new line before
src/NHibernate/Cache/IQueryCache.cs
Outdated
IList Get(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyLookup, ISet<string> spaces, ISessionImplementor session); | ||
/// <summary> |
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.
A new line before please
{ | ||
get | ||
{ | ||
ThrowIfNotInitialized(); |
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 one would have been better moved in #1789, but I forgot it.
@@ -243,8 +294,15 @@ private void InitializeEntitiesAndCollections(DbDataReader reader, List<object>[ | |||
if (queryInfo.IsResultFromCache) | |||
continue; | |||
queryInfo.Loader.InitializeEntitiesAndCollections( | |||
hydratedObjects[i], reader, Session, Session.PersistenceContext.DefaultReadOnly); | |||
hydratedObjects[i], reader, Session, queryInfo.Parameters.IsReadOnly(Session), |
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 change would also have been better moved in #1789. The old code was from multi-query/criteria, which was not knowing for which queries they were initializing and so could not handle their readonly flag.
Adjust formatting
src/NHibernate/Multi/QueryBatch.cs
Outdated
|
||
private void CombineQueries(IResultSetsCommand resultSetsCommand) | ||
{ | ||
foreach (var multiSource in _queries) |
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 multiSource
name is leftover of initial interface namings. Maybe it's better to rename it simply to query
(if you agree - please note that this name is used mulitple times in this file)
@@ -208,16 +258,17 @@ public void ExecuteNonBatched() | |||
|
|||
protected List<T> GetTypedResults<T>() | |||
{ | |||
if (_loaderResults == null) | |||
ThrowIfNotInitialized(); | |||
if (_queryInfos.Any(qi => qi.Result == null)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
internal IList GetResultFromQueryCache(ISessionImplementor session, QueryParameters queryParameters, | ||
ISet<string> querySpaces, IQueryCache queryCache, | ||
QueryKey key) | ||
internal bool CanGetFromCache(ISessionImplementor session, QueryParameters queryParameters) |
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 method doesn't handle per query SetCacheMode
calls. Which actually now is used in tests (as no op but might lead to confusion that it actually can be used in Future):
nhibernate-core/src/NHibernate.Test/Futures/QueryBatchFixture.cs
Lines 354 to 355 in 7ce79ed
.SetCacheMode(CacheMode.Normal) | |
.Future<EntitySimpleChild>() |
Maybe this query cache mode needs to be moved to QueryParameters
and handled similarly as QueryParameters.ReadOnly
flag?
So we should move this code to QueryParameters.CanGetFromCache(session)
method. And for parity introduce QueryParameters.CanPutToCache(session)
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.
Indeed, the queries individual cache mode has never been handled for future and multi-query/criteria, but we could. I do not think it should be done here, but better as another PR.
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 I agree - per query cacheMode support should be done as another PR.
But to minimize "change noise" I think it's better to introduce here QueryParameters.CanGetFromCache(session)
and use it instead of this method (and also introduce QueryParameters.CanPutToCache(session)
with simple session check). Again I see no difference why this usage should be different from QueryParameters.IsReadOnly(session)
- so those checks should be implemented and used the same way.
And dedicated "per query cacheMode" PR will simiply fill those 2 methods with proper implementation.
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 will not work, so better stick to YAGNI, not doing this in advance.
CacheMode
needs to be temporarily switched on the session instead, because many cache interactions triggered deeply in the loading implementation have no access to query parameters.
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.
For this PR I'm just saying that it's better to have consistent way for readonly check for query and cache mode check for query. So from this point - what will not work?
We use queryParameters.IsReadOnly(session)
for readonly check. So it looks logical to me to use queryParameters.CanGetFromCache(session)
for cache mode check. So why introduce new logically similair method in Loader
instead? It can be still marked internal here if you think this "cache mode per query" won't work.
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.
These cases are not comparable in my opinion:
IsReadonly
is a bit messy, originating from times where nullable were not avaliable. It is also there to hide the complexity of replacing nullability by an additional boolean.CacheMode
is not a boolean but a flag enumeration value. It conveys more state than a boolean.CanGetFromCache
is just a small part of it, because by example it tells nothing about what should be done with entities assembled from their id cached in query result, whether they should themselves be put into the cache or not if they were missing in it when re-assembled.
So it would be just a partial part of the information we actually need to handle properly cache mode, and we will have to put more somehow somewhere. It then looks likely to me thatCanGetFromCache
will just be redundant and not needed.
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'm not talking about how data needs to be stored. But rather how it should be used in code.
Calling queryParameters.IsReadOnly(session)
for checking query read-only flag looks pretty clean to me. How messy its implementation is irrelevant.
And as we need now method CanGetFromCache
I think place and synatx should be the same. But let's not spend more time on it. If you are not convinced - let's keep it as is for time being.
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.
You were right with the need for CanGetFromCache
and CanPutToCache
, but I still think it was better to do it in a dedicated PR.
/// <summary> | ||
/// The query result. | ||
/// </summary> | ||
IList Result { get; } |
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.
May be it should be exposed as GetResultsToCache
or some other name that suggests that implementor should return values that's going to be put to cache.
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.
If doing so, it should then hold neither results retrieved from cache nor results after cacheable transformation which should not be put (the put must be done on the untransformed result).
61cce23
to
8eabc3c
Compare
Adjust code according to review
8eabc3c
to
455f664
Compare
Adjust some naming
Adjust some logs & messages
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.
No more complaints from my part. And thanks for fixing the mess I made :)
I validated it, so it was also my responsibility. |
This is a follow up to @maca88 #1633 (batching 2nd level cache operations) and @bahusoid #1718 (query batch). It aims at combining both: enabling batching of 2nd level cache operations for the query batch.