Skip to content

Commit 5873ddb

Browse files
maca88fredericDelaporte
authored andcommitted
Optimize batchable cache calls for cached queries (nhibernate#1955)
1 parent 8d13f24 commit 5873ddb

19 files changed

+1560
-180
lines changed

src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs

+363-8
Large diffs are not rendered by default.

src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs

+363-8
Large diffs are not rendered by default.

src/NHibernate/Async/Cache/StandardQueryCache.cs

+155-58
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Collections.Generic;
1414
using System.Linq;
1515
using NHibernate.Cfg;
16+
using NHibernate.Collection;
1617
using NHibernate.Engine;
1718
using NHibernate.Persister.Collection;
1819
using NHibernate.Type;
@@ -208,44 +209,86 @@ public async Task<IList[]> GetManyAsync(
208209
var persistenceContext = session.PersistenceContext;
209210
var defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;
210211
var results = new IList[keys.Length];
211-
for (var i = 0; i < keys.Length; i++)
212+
var finalReturnTypes = new ICacheAssembler[keys.Length][];
213+
try
212214
{
213-
var cacheable = cacheables[i];
214-
if (cacheable == null)
215-
continue;
215+
session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue();
216216

217-
var key = keys[i];
218-
if (checkedSpacesIndexes.Contains(i) && !upToDates[upToDatesIndex++])
217+
for (var i = 0; i < keys.Length; i++)
219218
{
220-
Log.Debug("cached query results were not up to date for: {0}", key);
221-
continue;
219+
var cacheable = cacheables[i];
220+
if (cacheable == null)
221+
continue;
222+
223+
var key = keys[i];
224+
if (checkedSpacesIndexes.Contains(i) && !upToDates[upToDatesIndex++])
225+
{
226+
Log.Debug("cached query results were not up to date for: {0}", key);
227+
continue;
228+
}
229+
230+
var queryParams = queryParameters[i];
231+
if (queryParams.IsReadOnlyInitialized)
232+
persistenceContext.DefaultReadOnly = queryParams.ReadOnly;
233+
else
234+
queryParams.ReadOnly = persistenceContext.DefaultReadOnly;
235+
236+
Log.Debug("returning cached query results for: {0}", key);
237+
238+
finalReturnTypes[i] = GetReturnTypes(key, returnTypes[i], cacheable);
239+
await (PerformBeforeAssembleAsync(finalReturnTypes[i], session, cacheable, cancellationToken)).ConfigureAwait(false);
222240
}
223241

224-
var queryParams = queryParameters[i];
225-
if (queryParams.IsReadOnlyInitialized)
226-
persistenceContext.DefaultReadOnly = queryParams.ReadOnly;
227-
else
228-
queryParams.ReadOnly = persistenceContext.DefaultReadOnly;
242+
for (var i = 0; i < keys.Length; i++)
243+
{
244+
if (finalReturnTypes[i] == null)
245+
{
246+
continue;
247+
}
229248

230-
// Adjust the session cache mode, as GetResultFromCacheable assemble types which may cause
231-
// entity loads, which may interact with the cache.
232-
using (session.SwitchCacheMode(queryParams.CacheMode))
249+
var queryParams = queryParameters[i];
250+
// Adjust the session cache mode, as PerformAssemble assemble types which may cause
251+
// entity loads, which may interact with the cache.
252+
using (session.SwitchCacheMode(queryParams.CacheMode))
253+
{
254+
try
255+
{
256+
results[i] = await (PerformAssembleAsync(keys[i], finalReturnTypes[i], queryParams.NaturalKeyLookup, session, cacheables[i], cancellationToken)).ConfigureAwait(false);
257+
}
258+
finally
259+
{
260+
persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
261+
}
262+
}
263+
}
264+
265+
for (var i = 0; i < keys.Length; i++)
233266
{
234-
try
267+
if (finalReturnTypes[i] == null)
235268
{
236-
results[i] = await (GetResultFromCacheableAsync(
237-
key,
238-
returnTypes[i],
239-
queryParams.NaturalKeyLookup,
240-
session,
241-
cacheable, cancellationToken)).ConfigureAwait(false);
269+
continue;
242270
}
243-
finally
271+
272+
var queryParams = queryParameters[i];
273+
// Adjust the session cache mode, as InitializeCollections will initialize collections,
274+
// which may interact with the cache.
275+
using (session.SwitchCacheMode(queryParams.CacheMode))
244276
{
245-
persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
277+
try
278+
{
279+
await (InitializeCollectionsAsync(finalReturnTypes[i], session, results[i], cacheables[i], cancellationToken)).ConfigureAwait(false);
280+
}
281+
finally
282+
{
283+
persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
284+
}
246285
}
247286
}
248287
}
288+
finally
289+
{
290+
session.PersistenceContext.BatchFetchQueue.TerminateQueryCacheQueue();
291+
}
249292

250293
return results;
251294
}
@@ -275,20 +318,40 @@ private static async Task<List<object>> GetCacheableResultAsync(
275318
return cacheable;
276319
}
277320

278-
private async Task<IList> GetResultFromCacheableAsync(
279-
QueryKey key,
321+
private static async Task PerformBeforeAssembleAsync(
280322
ICacheAssembler[] returnTypes,
281-
bool isNaturalKeyLookup,
282323
ISessionImplementor session,
283324
IList cacheable, CancellationToken cancellationToken)
284325
{
285326
cancellationToken.ThrowIfCancellationRequested();
286-
Log.Debug("returning cached query results for: {0}", key);
287-
if (key.ResultTransformer?.AutoDiscoverTypes == true && cacheable.Count > 0)
327+
if (returnTypes.Length == 1)
288328
{
289-
returnTypes = GuessTypes(cacheable);
329+
var returnType = returnTypes[0];
330+
331+
// Skip first element, it is the timestamp
332+
for (var i = 1; i < cacheable.Count; i++)
333+
{
334+
await (returnType.BeforeAssembleAsync(cacheable[i], session, cancellationToken)).ConfigureAwait(false);
335+
}
290336
}
337+
else
338+
{
339+
// Skip first element, it is the timestamp
340+
for (var i = 1; i < cacheable.Count; i++)
341+
{
342+
await (TypeHelper.BeforeAssembleAsync((object[]) cacheable[i], returnTypes, session, cancellationToken)).ConfigureAwait(false);
343+
}
344+
}
345+
}
291346

347+
private async Task<IList> PerformAssembleAsync(
348+
QueryKey key,
349+
ICacheAssembler[] returnTypes,
350+
bool isNaturalKeyLookup,
351+
ISessionImplementor session,
352+
IList cacheable, CancellationToken cancellationToken)
353+
{
354+
cancellationToken.ThrowIfCancellationRequested();
292355
try
293356
{
294357
var result = new List<object>(cacheable.Count - 1);
@@ -297,53 +360,27 @@ private async Task<IList> GetResultFromCacheableAsync(
297360
var returnType = returnTypes[0];
298361

299362
// Skip first element, it is the timestamp
300-
for (var i = 1; i < cacheable.Count; i++)
301-
{
302-
await (returnType.BeforeAssembleAsync(cacheable[i], session, cancellationToken)).ConfigureAwait(false);
303-
}
304-
305363
for (var i = 1; i < cacheable.Count; i++)
306364
{
307365
result.Add(await (returnType.AssembleAsync(cacheable[i], session, null, cancellationToken)).ConfigureAwait(false));
308366
}
309367
}
310368
else
311369
{
312-
var collectionIndexes = new Dictionary<int, ICollectionPersister>();
313370
var nonCollectionTypeIndexes = new List<int>();
314371
for (var i = 0; i < returnTypes.Length; i++)
315372
{
316-
if (returnTypes[i] is CollectionType collectionType)
317-
{
318-
collectionIndexes.Add(i, session.Factory.GetCollectionPersister(collectionType.Role));
319-
}
320-
else
373+
if (!(returnTypes[i] is CollectionType))
321374
{
322375
nonCollectionTypeIndexes.Add(i);
323376
}
324377
}
325378

326379
// Skip first element, it is the timestamp
327-
for (var i = 1; i < cacheable.Count; i++)
328-
{
329-
await (TypeHelper.BeforeAssembleAsync((object[]) cacheable[i], returnTypes, session, cancellationToken)).ConfigureAwait(false);
330-
}
331-
332380
for (var i = 1; i < cacheable.Count; i++)
333381
{
334382
result.Add(await (TypeHelper.AssembleAsync((object[]) cacheable[i], returnTypes, nonCollectionTypeIndexes, session, cancellationToken)).ConfigureAwait(false));
335383
}
336-
337-
// Initialization of the fetched collection must be done at the end in order to be able to batch fetch them
338-
// from the cache or database. The collections were already created in the previous for statement so we only
339-
// have to initialize them.
340-
if (collectionIndexes.Count > 0)
341-
{
342-
for (var i = 1; i < cacheable.Count; i++)
343-
{
344-
await (TypeHelper.InitializeCollectionsAsync((object[]) cacheable[i], (object[]) result[i - 1], collectionIndexes, session, cancellationToken)).ConfigureAwait(false);
345-
}
346-
}
347384
}
348385

349386
return result;
@@ -367,6 +404,66 @@ private async Task<IList> GetResultFromCacheableAsync(
367404
}
368405
}
369406

407+
private static async Task InitializeCollectionsAsync(
408+
ICacheAssembler[] returnTypes,
409+
ISessionImplementor session,
410+
IList assembleResult,
411+
IList cacheResult, CancellationToken cancellationToken)
412+
{
413+
cancellationToken.ThrowIfCancellationRequested();
414+
var collectionIndexes = new Dictionary<int, ICollectionPersister>();
415+
for (var i = 0; i < returnTypes.Length; i++)
416+
{
417+
if (returnTypes[i] is CollectionType collectionType)
418+
{
419+
collectionIndexes.Add(i, session.Factory.GetCollectionPersister(collectionType.Role));
420+
}
421+
}
422+
423+
if (collectionIndexes.Count == 0)
424+
{
425+
return;
426+
}
427+
428+
// Skip first element, it is the timestamp
429+
for (var i = 1; i < cacheResult.Count; i++)
430+
{
431+
// Initialization of the fetched collection must be done at the end in order to be able to batch fetch them
432+
// from the cache or database. The collections were already created when their owners were assembled so we only
433+
// have to initialize them.
434+
await (TypeHelper.InitializeCollectionsAsync(
435+
(object[]) cacheResult[i],
436+
(object[]) assembleResult[i - 1],
437+
collectionIndexes,
438+
session, cancellationToken)).ConfigureAwait(false);
439+
}
440+
}
441+
442+
private async Task<IList> GetResultFromCacheableAsync(
443+
QueryKey key,
444+
ICacheAssembler[] returnTypes,
445+
bool isNaturalKeyLookup,
446+
ISessionImplementor session,
447+
IList cacheable, CancellationToken cancellationToken)
448+
{
449+
cancellationToken.ThrowIfCancellationRequested();
450+
Log.Debug("returning cached query results for: {0}", key);
451+
returnTypes = GetReturnTypes(key, returnTypes, cacheable);
452+
try
453+
{
454+
session.PersistenceContext.BatchFetchQueue.InitializeQueryCacheQueue();
455+
456+
await (PerformBeforeAssembleAsync(returnTypes, session, cacheable, cancellationToken)).ConfigureAwait(false);
457+
var result = await (PerformAssembleAsync(key, returnTypes, isNaturalKeyLookup, session, cacheable, cancellationToken)).ConfigureAwait(false);
458+
await (InitializeCollectionsAsync(returnTypes, session, result, cacheable, cancellationToken)).ConfigureAwait(false);
459+
return result;
460+
}
461+
finally
462+
{
463+
session.PersistenceContext.BatchFetchQueue.TerminateQueryCacheQueue();
464+
}
465+
}
466+
370467
protected virtual Task<bool> IsUpToDateAsync(ISet<string> spaces, long timestamp, CancellationToken cancellationToken)
371468
{
372469
if (cancellationToken.IsCancellationRequested)

src/NHibernate/Async/Engine/BatchFetchQueue.cs

+15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using NHibernate.Persister.Entity;
1616
using NHibernate.Util;
1717
using System.Collections.Generic;
18+
using System.Linq;
1819
using Iesi.Collections.Generic;
1920

2021
namespace NHibernate.Engine
@@ -395,6 +396,13 @@ private async Task<bool[]> AreCachedAsync(List<KeyValuePair<EntityKey, int>> ent
395396
{
396397
return result;
397398
}
399+
400+
// Do not check the cache when disassembling entities from the cached query that were already checked
401+
if (QueryCacheQueue != null && entityKeys.All(o => QueryCacheQueue.WasEntityKeyChecked(persister, o.Key)))
402+
{
403+
return result;
404+
}
405+
398406
var cacheKeys = new object[keyIndexes.Length];
399407
var i = 0;
400408
foreach (var index in keyIndexes)
@@ -434,6 +442,13 @@ private async Task<bool[]> AreCachedAsync(List<KeyValuePair<KeyValuePair<Collect
434442
{
435443
return result;
436444
}
445+
446+
// Do not check the cache when disassembling collections from the cached query that were already checked
447+
if (QueryCacheQueue != null && collectionKeys.All(o => QueryCacheQueue.WasCollectionEntryChecked(persister, o.Key.Key)))
448+
{
449+
return result;
450+
}
451+
437452
var cacheKeys = new object[keyIndexes.Length];
438453
var i = 0;
439454
foreach (var index in keyIndexes)

0 commit comments

Comments
 (0)