Skip to content

Commit 18936a8

Browse files
authored
Merge pull request #296 from DataObjects-NET/6.0-node-stale-querycache
On Node removing remove related keys from QueryCache if required
2 parents bf7811d + c3632e7 commit 18936a8

File tree

4 files changed

+264
-2
lines changed

4 files changed

+264
-2
lines changed

ChangeLog/6.0.11_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
[main] Added an option to remove cached queries of removing storage node along with the node, default behavior remains the same
12
[postgresql] Dedicated exception when an extracting schema doesn't exist or it belongs to another user
23
[weaver] Fixed ctor processing with try...catch where there was broken catch section leaving
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Copyright (C) 2022 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using NUnit.Framework;
10+
using Xtensive.Orm.Configuration;
11+
using Xtensive.Orm.Tests.Storage.Multinode.QueryCachingTestModel;
12+
13+
namespace Xtensive.Orm.Tests.Storage.Multinode
14+
{
15+
public sealed class StaleQueryCacheForReAddedNodes : MultinodeTest
16+
{
17+
private const string DefaultSchema = WellKnownSchemas.Schema1;
18+
private const string Schema1 = WellKnownSchemas.Schema2;
19+
private const string Schema2 = WellKnownSchemas.Schema3;
20+
21+
private readonly object SimpleQueryKey = new object();
22+
23+
protected override void CheckRequirements() =>
24+
Require.AllFeaturesSupported(Orm.Providers.ProviderFeatures.Multischema);
25+
26+
protected override DomainConfiguration BuildConfiguration()
27+
{
28+
CustomUpgradeHandler.TypeIdPerNode.Add(TestNodeId2, 100);
29+
CustomUpgradeHandler.TypeIdPerNode.Add(TestNodeId3, 100);
30+
31+
var configuration = base.BuildConfiguration();
32+
configuration.Types.Register(typeof(BaseTestEntity).Assembly, typeof(BaseTestEntity).Namespace);
33+
configuration.UpgradeMode = DomainUpgradeMode.Recreate;
34+
configuration.DefaultSchema = DefaultSchema;
35+
return configuration;
36+
}
37+
38+
protected override void PopulateNodes()
39+
{
40+
CustomUpgradeHandler.CurrentNodeId = TestNodeId2;
41+
var nodeConfiguration = new NodeConfiguration(TestNodeId2);
42+
nodeConfiguration.SchemaMapping.Add(DefaultSchema, Schema1);
43+
nodeConfiguration.UpgradeMode = DomainUpgradeMode.Recreate;
44+
_ = Domain.StorageNodeManager.AddNode(nodeConfiguration);
45+
46+
CustomUpgradeHandler.CurrentNodeId = TestNodeId3;
47+
nodeConfiguration = new NodeConfiguration(TestNodeId3);
48+
nodeConfiguration.SchemaMapping.Add(DefaultSchema, Schema2);
49+
nodeConfiguration.UpgradeMode = DomainUpgradeMode.Recreate;
50+
_ = Domain.StorageNodeManager.AddNode(nodeConfiguration);
51+
}
52+
53+
protected override void PopulateData()
54+
{
55+
var nodes = new[] { WellKnown.DefaultNodeId, TestNodeId2, TestNodeId3 };
56+
57+
foreach (var nodeId in nodes) {
58+
var selectedNode = Domain.StorageNodeManager.GetNode(nodeId);
59+
using (var session = selectedNode.OpenSession())
60+
using (var tx = session.OpenTransaction()) {
61+
62+
var nodeIdName = string.IsNullOrEmpty(nodeId) ? "<default>" : nodeId;
63+
64+
_ = new BaseTestEntity(session) { BaseName = "A", BaseOwnerNodeId = nodeIdName };
65+
_ = new MiddleTestEntity(session) {
66+
BaseName = "AA",
67+
MiddleName = "AAM",
68+
BaseOwnerNodeId = nodeIdName,
69+
MiddleOwnerNodeId = nodeIdName
70+
};
71+
_ = new LeafTestEntity(session) {
72+
BaseName = "AAA",
73+
MiddleName = "AAAM",
74+
LeafName = "AAAL",
75+
BaseOwnerNodeId = nodeIdName,
76+
MiddleOwnerNodeId = nodeIdName,
77+
LeafOwnerNodeId = nodeIdName
78+
};
79+
80+
_ = new BaseTestEntity(session) { BaseName = "B", BaseOwnerNodeId = nodeIdName };
81+
_ = new MiddleTestEntity(session) {
82+
BaseName = "BB",
83+
MiddleName = "BBM",
84+
BaseOwnerNodeId = nodeIdName,
85+
MiddleOwnerNodeId = nodeIdName
86+
};
87+
_ = new LeafTestEntity(session) {
88+
BaseName = "BBB",
89+
MiddleName = "BBBM",
90+
LeafName = "BBBL",
91+
BaseOwnerNodeId = nodeIdName,
92+
MiddleOwnerNodeId = nodeIdName,
93+
LeafOwnerNodeId = nodeIdName
94+
};
95+
96+
_ = new BaseTestEntity(session) { BaseName = "C", BaseOwnerNodeId = nodeIdName };
97+
_ = new MiddleTestEntity(session) {
98+
BaseName = "CC",
99+
MiddleName = "CCM",
100+
BaseOwnerNodeId = nodeIdName,
101+
MiddleOwnerNodeId = nodeIdName
102+
};
103+
_ = new LeafTestEntity(session) {
104+
BaseName = "CCC",
105+
MiddleName = "CCCM",
106+
LeafName = "CCCL",
107+
BaseOwnerNodeId = nodeIdName,
108+
MiddleOwnerNodeId = nodeIdName,
109+
LeafOwnerNodeId = nodeIdName
110+
};
111+
112+
_ = new BaseTestEntity(session) { BaseName = "D", BaseOwnerNodeId = nodeIdName };
113+
_ = new MiddleTestEntity(session) {
114+
BaseName = "DD",
115+
MiddleName = "DDM",
116+
BaseOwnerNodeId = nodeIdName,
117+
MiddleOwnerNodeId = nodeIdName
118+
};
119+
_ = new LeafTestEntity(session) {
120+
BaseName = "DDD",
121+
MiddleName = "DDDM",
122+
LeafName = "DDDL",
123+
BaseOwnerNodeId = nodeIdName,
124+
MiddleOwnerNodeId = nodeIdName,
125+
LeafOwnerNodeId = nodeIdName
126+
};
127+
128+
// puts one query per each node to the query cache
129+
_ = ExecuteSimpleQueryCaching(session);
130+
131+
tx.Complete();
132+
}
133+
}
134+
}
135+
136+
[Test]
137+
public void ReAddNodeTest()
138+
{
139+
var node = Domain.StorageNodeManager.GetNode(TestNodeId2);
140+
var queryCacheSize = Domain.QueryCache.Count;
141+
142+
using (var session = node.OpenSession())
143+
using (var tx = session.OpenTransaction()) {
144+
_ = session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
145+
}
146+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
147+
148+
_ = Domain.StorageNodeManager.RemoveNode(TestNodeId2);
149+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
150+
151+
CustomUpgradeHandler.CurrentNodeId = TestNodeId2;
152+
var nodeConfiguration = new NodeConfiguration(TestNodeId2);
153+
nodeConfiguration.SchemaMapping.Add(DefaultSchema, Schema1);
154+
nodeConfiguration.UpgradeMode = DomainUpgradeMode.Validate;
155+
_ = Domain.StorageNodeManager.AddNode(nodeConfiguration);
156+
157+
node = Domain.StorageNodeManager.GetNode(TestNodeId2);
158+
159+
using (var session = node.OpenSession())
160+
using (var tx = session.OpenTransaction()) {
161+
_ = session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
162+
}
163+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
164+
165+
CustomUpgradeHandler.CurrentNodeId = TestNodeId3;
166+
nodeConfiguration = new NodeConfiguration(TestNodeId3);
167+
nodeConfiguration.SchemaMapping.Add(DefaultSchema, Schema2);
168+
nodeConfiguration.UpgradeMode = DomainUpgradeMode.Recreate;
169+
_ = Domain.StorageNodeManager.AddNode(nodeConfiguration);
170+
}
171+
172+
[Test]
173+
public void ReAddNodeWithAnotherSchemaMappingNoCacheCleanTest()
174+
{
175+
var node = Domain.StorageNodeManager.GetNode(TestNodeId2);
176+
var queryCacheSize = Domain.QueryCache.Count;
177+
178+
using (var session = node.OpenSession())
179+
using (var tx = session.OpenTransaction()) {
180+
_ = session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
181+
}
182+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
183+
184+
_ = Domain.StorageNodeManager.RemoveNode(TestNodeId2);
185+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
186+
187+
CustomUpgradeHandler.CurrentNodeId = TestNodeId2;
188+
var nodeConfiguration = new NodeConfiguration(TestNodeId2);
189+
nodeConfiguration.SchemaMapping.Add(DefaultSchema, Schema2);// uses schema of TestNodeId3
190+
nodeConfiguration.UpgradeMode = DomainUpgradeMode.Validate;
191+
_ = Domain.StorageNodeManager.AddNode(nodeConfiguration);
192+
193+
node = Domain.StorageNodeManager.GetNode(TestNodeId2);
194+
195+
using (var session = node.OpenSession())
196+
using (var tx = session.OpenTransaction()) {
197+
var results = session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
198+
foreach(var item in results) {
199+
Assert.That(item.BaseOwnerNodeId, Is.EqualTo(node.Id)); // gets result from old schema
200+
}
201+
}
202+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
203+
}
204+
205+
[Test]
206+
public void ReAddNodeWithAnotherSchemaMappingWithCacheCleanTest()
207+
{
208+
var node = Domain.StorageNodeManager.GetNode(TestNodeId2);
209+
var queryCacheSize = Domain.QueryCache.Count;
210+
211+
using (var session = node.OpenSession())
212+
using (var tx = session.OpenTransaction()) {
213+
_ = session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
214+
}
215+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
216+
217+
_ = Domain.StorageNodeManager.RemoveNode(TestNodeId2, true);
218+
Assert.That(Domain.QueryCache.Count, Is.LessThan(queryCacheSize));
219+
220+
CustomUpgradeHandler.CurrentNodeId = TestNodeId2;
221+
var nodeConfiguration = new NodeConfiguration(TestNodeId2);
222+
nodeConfiguration.SchemaMapping.Add(DefaultSchema, Schema2);// uses schema of TestNodeId3
223+
nodeConfiguration.UpgradeMode = DomainUpgradeMode.Validate;
224+
_ = Domain.StorageNodeManager.AddNode(nodeConfiguration);
225+
226+
node = Domain.StorageNodeManager.GetNode(TestNodeId2);
227+
228+
using (var session = node.OpenSession())
229+
using (var tx = session.OpenTransaction()) {
230+
var results = session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
231+
foreach (var item in results) {
232+
Assert.That(item.BaseOwnerNodeId, Is.Not.EqualTo(node.Id)); // gets result from correct schema but data was added by TestNodeId3
233+
}
234+
}
235+
Assert.That(Domain.QueryCache.Count, Is.EqualTo(queryCacheSize));
236+
}
237+
238+
private List<BaseTestEntity> ExecuteSimpleQueryCaching(Session session) =>
239+
session.Query.Execute(SimpleQueryKey, q => q.All<BaseTestEntity>().Where(e => e.BaseName.Contains("B"))).ToList();
240+
}
241+
}

Orm/Xtensive.Orm/Caching/LruCache{TKey, TItem}.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,12 @@ protected virtual void Cleared() { }
239239

240240
#endregion
241241

242+
internal IEnumerable<TKey> GetKeysInternal()
243+
{
244+
foreach (KeyValuePair<TKey, TItem> cachedItem in deque)
245+
yield return cachedItem.Key;
246+
}
247+
242248

243249
// Constructors
244250

Orm/Xtensive.Orm/Orm/StorageNodeManager.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// Created: 2014.03.13
66

77
using System;
8+
using System.Linq;
89
using JetBrains.Annotations;
10+
using Xtensive.Core;
911
using Xtensive.Orm.Configuration;
1012
using Xtensive.Orm.Providers;
1113
using Xtensive.Orm.Upgrade;
@@ -34,12 +36,24 @@ public bool AddNode([NotNull] NodeConfiguration configuration)
3436
/// Removes node with specified <paramref name="nodeId"/>.
3537
/// </summary>
3638
/// <param name="nodeId">Node identifier.</param>
39+
/// <param name="clearQueryCache">
40+
/// if <see langword="true"/> then cached queries dedicated to the removing node will be removed from cache as well. By default <see langword="false"/>.
41+
/// </param>
3742
/// <returns>True if node was removed, otherwise false.</returns>
38-
public bool RemoveNode([NotNull] string nodeId)
43+
public bool RemoveNode([NotNull] string nodeId, bool clearQueryCache = false)
3944
{
40-
return handlers.StorageNodeRegistry.Remove(nodeId);
45+
var removeResult = handlers.StorageNodeRegistry.Remove(nodeId);
46+
47+
if (removeResult && clearQueryCache) {
48+
var queryCache = (Caching.LruCache<object, Pair<object, Linq.TranslatedQuery>>) handlers.Domain.QueryCache;
49+
foreach (var key in queryCache.GetKeysInternal().Where(k => k is Pair<object, string> p && p.Second == nodeId).ToChainedBuffer()) {
50+
queryCache.RemoveKey(key, true);
51+
}
52+
}
53+
return removeResult;
4154
}
4255

56+
4357
/// <summary>
4458
/// Gets node with the specified <paramref name="nodeId"/>
4559
/// </summary>

0 commit comments

Comments
 (0)