Skip to content

Commit 009f0a2

Browse files
committed
Optimize batchable cache calls for cached queries
1 parent 5804190 commit 009f0a2

20 files changed

+1336
-174
lines changed

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

Lines changed: 227 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ protected override void Configure(Configuration configuration)
4141
{
4242
configuration.SetProperty(Environment.UseSecondLevelCache, "true");
4343
configuration.SetProperty(Environment.UseQueryCache, "true");
44+
configuration.SetProperty(Environment.GenerateStatistics, "true");
4445
configuration.SetProperty(Environment.CacheProvider, typeof(BatchableCacheProvider).AssemblyQualifiedName);
4546
}
4647

@@ -706,14 +707,7 @@ public async Task QueryCacheTestAsync()
706707
if (!Sfi.ConnectionProvider.Driver.SupportsMultipleQueries)
707708
Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries");
708709

709-
var queryCache = Sfi.GetQueryCache(null);
710-
var field = typeof(StandardQueryCache).GetField(
711-
"_cache",
712-
BindingFlags.NonPublic | BindingFlags.Instance);
713-
Assert.That(field, Is.Not.Null, "Unable to find _cache field");
714-
var cache = (BatchableCache) field.GetValue(queryCache);
715-
Assert.That(cache, Is.Not.Null, "_cache is null");
716-
710+
var cache = GetDefaultQueryCache();
717711
var timestamp = Sfi.UpdateTimestampsCache;
718712
var tsField = typeof(UpdateTimestampsCache).GetField(
719713
"_updateTimestamps",
@@ -870,6 +864,218 @@ public async Task QueryCacheTestAsync()
870864
}
871865
}
872866

867+
[TestCase(true)]
868+
[TestCase(false)]
869+
public async Task QueryEntityBatchCacheTestAsync(bool clearEntityCacheAfterQuery, CancellationToken cancellationToken = default(CancellationToken))
870+
{
871+
var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName);
872+
var cache = (BatchableCache) persister.Cache.Cache;
873+
var queryCache = GetDefaultQueryCache();
874+
875+
Sfi.Statistics.Clear();
876+
await (Sfi.EvictQueriesAsync(cancellationToken));
877+
cache.ClearStatistics();
878+
queryCache.ClearStatistics();
879+
880+
List<ReadOnlyItem> items;
881+
882+
using (var s = OpenSession())
883+
using (var tx = s.BeginTransaction())
884+
{
885+
items = await (s.Query<ReadOnlyItem>()
886+
.WithOptions(o => o.SetCacheable(true))
887+
.ToListAsync(cancellationToken));
888+
889+
await (tx.CommitAsync(cancellationToken));
890+
}
891+
892+
Assert.That(queryCache.GetCalls, Has.Count.EqualTo(1), "Unexpected query cache GetCalls");
893+
Assert.That(queryCache.PutCalls, Has.Count.EqualTo(1), "Unexpected query cache PutCalls");
894+
Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1), "Unexpected entity cache PutMultipleCalls");
895+
Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(0), "Unexpected entity cache GetMultipleCalls");
896+
Assert.That(items, Has.Count.EqualTo(36), "Unexpected items count");
897+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count");
898+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(1), "Unexpected cache put count");
899+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(1), "Unexpected cache miss count");
900+
901+
cache.ClearStatistics();
902+
queryCache.ClearStatistics();
903+
904+
if (clearEntityCacheAfterQuery)
905+
{
906+
await (cache.ClearAsync(cancellationToken));
907+
}
908+
909+
Sfi.Statistics.Clear();
910+
911+
using (var s = OpenSession())
912+
using (var tx = s.BeginTransaction())
913+
{
914+
items = await (s.Query<ReadOnlyItem>()
915+
.WithOptions(o => o.SetCacheable(true))
916+
.ToListAsync(cancellationToken));
917+
918+
await (tx.CommitAsync(cancellationToken));
919+
}
920+
921+
Assert.That(queryCache.GetCalls, Has.Count.EqualTo(1), "Unexpected query cache GetCalls");
922+
Assert.That(queryCache.PutCalls, Has.Count.EqualTo(0), "Unexpected query cache PutCalls");
923+
// Ideally the PutMultipleCalls count should be 1 when clearing the cache after the first query, in order to achieve this
924+
// the CacheBatcher would need to be on the session and executed once the query is processed
925+
Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(clearEntityCacheAfterQuery ? 9 : 0), "Unexpected entity cache PutMultipleCalls");
926+
Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1), "Unexpected entity cache GetMultipleCalls");
927+
Assert.That(items, Has.Count.EqualTo(36));
928+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count");
929+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count");
930+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
931+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(1), "Unexpected cache hit count");
932+
}
933+
934+
[TestCase(true, false)]
935+
[TestCase(false, false)]
936+
[TestCase(true, true)]
937+
[TestCase(false, true)]
938+
public async Task QueryFetchCollectionBatchCacheTestAsync(bool clearEntityCacheAfterQuery, bool future, CancellationToken cancellationToken = default(CancellationToken))
939+
{
940+
if (future && !Sfi.ConnectionProvider.Driver.SupportsMultipleQueries)
941+
{
942+
Assert.Ignore($"{Sfi.ConnectionProvider.Driver} does not support multiple queries");
943+
}
944+
945+
var persister = Sfi.GetEntityPersister(typeof(ReadOnly).FullName);
946+
var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName);
947+
var collectionPersister = Sfi.GetCollectionPersister($"{typeof(ReadOnly).FullName}.Items");
948+
var cache = (BatchableCache) persister.Cache.Cache;
949+
var itemCache = (BatchableCache) itemPersister.Cache.Cache;
950+
var collectionCache = (BatchableCache) collectionPersister.Cache.Cache;
951+
var queryCache = GetDefaultQueryCache();
952+
953+
int middleId;
954+
955+
using (var s = OpenSession())
956+
{
957+
var ids = await (s.Query<ReadOnly>().Select(o => o.Id).OrderBy(o => o).ToListAsync(cancellationToken));
958+
middleId = ids[2];
959+
}
960+
961+
Sfi.Statistics.Clear();
962+
await (Sfi.EvictQueriesAsync(cancellationToken));
963+
queryCache.ClearStatistics();
964+
cache.ClearStatistics();
965+
await (cache.ClearAsync(cancellationToken));
966+
itemCache.ClearStatistics();
967+
await (itemCache.ClearAsync(cancellationToken));
968+
collectionCache.ClearStatistics();
969+
await (collectionCache.ClearAsync(cancellationToken));
970+
971+
List<ReadOnly> items;
972+
using (var s = OpenSession())
973+
using (var tx = s.BeginTransaction())
974+
{
975+
if (future)
976+
{
977+
s.Query<ReadOnly>()
978+
.WithOptions(o => o.SetCacheable(true))
979+
.FetchMany(o => o.Items)
980+
.Where(o => o.Id > middleId)
981+
.ToFuture();
982+
983+
items = s.Query<ReadOnly>()
984+
.WithOptions(o => o.SetCacheable(true))
985+
.FetchMany(o => o.Items)
986+
.Where(o => o.Id <= middleId)
987+
.ToFuture()
988+
.ToList();
989+
}
990+
else
991+
{
992+
items = await (s.Query<ReadOnly>()
993+
.WithOptions(o => o.SetCacheable(true))
994+
.FetchMany(o => o.Items)
995+
.ToListAsync(cancellationToken));
996+
}
997+
998+
await (tx.CommitAsync(cancellationToken));
999+
}
1000+
1001+
Assert.That(queryCache.GetCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache GetCalls");
1002+
Assert.That(queryCache.GetMultipleCalls, Has.Count.EqualTo(future ? 1 : 0), "Unexpected query cache GetMultipleCalls");
1003+
Assert.That(queryCache.PutCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache PutCalls");
1004+
Assert.That(queryCache.PutMultipleCalls, Has.Count.EqualTo(future ? 1 : 0), "Unexpected query cache PutMultipleCalls");
1005+
Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1), "Unexpected entity cache PutMultipleCalls");
1006+
Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(0), "Unexpected entity cache GetMultipleCalls");
1007+
Assert.That(collectionCache.PutMultipleCalls, Has.Count.EqualTo(1), "Unexpected collection cache PutMultipleCalls");
1008+
Assert.That(collectionCache.GetMultipleCalls, Has.Count.EqualTo(0), "Unexpected collection cache GetMultipleCalls");
1009+
Assert.That(items, Has.Count.EqualTo(future ? 3 : 6), "Unexpected items count");
1010+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(1), "Unexpected execution count");
1011+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache put count");
1012+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache miss count");
1013+
1014+
cache.ClearStatistics();
1015+
itemCache.ClearStatistics();
1016+
collectionCache.ClearStatistics();
1017+
queryCache.ClearStatistics();
1018+
1019+
if (clearEntityCacheAfterQuery)
1020+
{
1021+
await (cache.ClearAsync(cancellationToken));
1022+
await (collectionCache.ClearAsync(cancellationToken));
1023+
await (itemCache.ClearAsync(cancellationToken));
1024+
}
1025+
1026+
Sfi.Statistics.Clear();
1027+
1028+
using (var s = OpenSession())
1029+
using (var tx = s.BeginTransaction())
1030+
{
1031+
if (future)
1032+
{
1033+
s.Query<ReadOnly>()
1034+
.WithOptions(o => o.SetCacheable(true))
1035+
.FetchMany(o => o.Items)
1036+
.Where(o => o.Id > middleId)
1037+
.ToFuture();
1038+
1039+
items = s.Query<ReadOnly>()
1040+
.WithOptions(o => o.SetCacheable(true))
1041+
.FetchMany(o => o.Items)
1042+
.Where(o => o.Id <= middleId)
1043+
.ToFuture()
1044+
.ToList();
1045+
}
1046+
else
1047+
{
1048+
items = await (s.Query<ReadOnly>()
1049+
.WithOptions(o => o.SetCacheable(true))
1050+
.FetchMany(o => o.Items)
1051+
.ToListAsync(cancellationToken));
1052+
}
1053+
1054+
await (tx.CommitAsync(cancellationToken));
1055+
}
1056+
1057+
Assert.That(queryCache.GetCalls, Has.Count.EqualTo(future ? 0 : 1), "Unexpected query cache GetCalls");
1058+
Assert.That(queryCache.GetMultipleCalls, Has.Count.EqualTo(future ? 1 : 0), "Unexpected query cache GetCalls");
1059+
Assert.That(queryCache.PutCalls, Has.Count.EqualTo(0), "Unexpected query cache PutCalls");
1060+
Assert.That(queryCache.PutMultipleCalls, Has.Count.EqualTo(0), "Unexpected query cache PutMultipleCalls");
1061+
Assert.That(collectionCache.GetMultipleCalls, Has.Count.EqualTo(1), "Unexpected collection cache GetMultipleCalls");
1062+
Assert.That(collectionCache.GetMultipleCalls[0], Has.Length.EqualTo(6), "Unexpected collection cache GetMultipleCalls length");
1063+
Assert.That(cache.GetMultipleCalls, Has.Count.EqualTo(1), "Unexpected entity cache GetMultipleCalls");
1064+
Assert.That(cache.GetMultipleCalls[0], Has.Length.EqualTo(6), "Unexpected entity cache GetMultipleCalls length");
1065+
Assert.That(itemCache.GetMultipleCalls, Has.Count.EqualTo(1), "Unexpected entity item cache GetMultipleCalls");
1066+
Assert.That(itemCache.GetMultipleCalls[0], Has.Length.EqualTo(36), "Unexpected entity item cache GetMultipleCalls length");
1067+
// Ideally the PutMultipleCalls count should be 1 when clearing the cache after the first query, in order to achieve this
1068+
// the CacheBatcher would need to be on the session and executed once the batch fetch queries are processed
1069+
Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(clearEntityCacheAfterQuery ? 2 : 0), "Unexpected entity cache PutMultipleCalls");
1070+
Assert.That(collectionCache.PutMultipleCalls, Has.Count.EqualTo(clearEntityCacheAfterQuery ? 2 : 0), "Unexpected collection cache PutMultipleCalls");
1071+
Assert.That(itemCache.PutMultipleCalls, Has.Count.EqualTo(clearEntityCacheAfterQuery ? 9 : 0), "Unexpected entity item cache PutMultipleCalls");
1072+
Assert.That(items, Has.Count.EqualTo(future ? 3 : 6));
1073+
Assert.That(Sfi.Statistics.QueryExecutionCount, Is.EqualTo(0), "Unexpected execution count");
1074+
Assert.That(Sfi.Statistics.QueryCachePutCount, Is.EqualTo(0), "Unexpected cache put count");
1075+
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
1076+
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count");
1077+
}
1078+
8731079
private async Task AssertMultipleCacheCallsAsync<TEntity>(IEnumerable<int> loadIds, IReadOnlyList<int> getIds, int idIndex,
8741080
int[][] fetchedIdIndexes, int[] putIdIndexes, Func<int, bool> cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken))
8751081
where TEntity : CacheEntity
@@ -996,5 +1202,18 @@ private void AssertEquivalent(List<int> ids, int[][] expectedIdIndexes, List<obj
9961202
}
9971203
}
9981204

1205+
private BatchableCache GetDefaultQueryCache()
1206+
{
1207+
var queryCache = Sfi.GetQueryCache(null);
1208+
var field = typeof(StandardQueryCache).GetField(
1209+
"_cache",
1210+
BindingFlags.NonPublic | BindingFlags.Instance);
1211+
Assert.That(field, Is.Not.Null, "Unable to find _cache field");
1212+
var cache = (BatchableCache) field.GetValue(queryCache);
1213+
Assert.That(cache, Is.Not.Null, "_cache is null");
1214+
1215+
return cache;
1216+
}
1217+
9991218
}
10001219
}

0 commit comments

Comments
 (0)