Skip to content

Reduce the number of calls to UpdateTimestampsCache #1467

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

Merged
merged 17 commits into from
Dec 8, 2017

Conversation

gliljas
Copy link
Member

@gliljas gliljas commented Nov 30, 2017

This needs a bit of review, since it maybe just goes from wrong to less wrong. The problem is that 2nd level UpdateTimestampCache invalidation is performed twice per action. Insert 200 items, the cache is invalidated 400 times. Unnecessary for "cheap" caches, a significant performance hit for caches with latency.

This PR reduces the number from 400 to 2. It may, due to its design, perform it even on things that fail to execute, but that only has unnecessary invalidation as a side effect.

I see now that a bit of whitespace has been added (probably by R#). The wrong whitespaces?

Copy link
Member

@hazzik hazzik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just comments on the code style. I did not review the actual changes in PR.

public UpdateTimestampsCache UpdateTimestampsCache
{
get => _updateTimestampsCache ?? ActualFactory.UpdateTimestampsCache;
set { _updateTimestampsCache = value; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use consistent code style. Either both need to be in brackets or to be lambdas.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok!

@@ -24,15 +24,6 @@ namespace NHibernate.Engine
using System.Threading;
public partial class ActionQueue
{

public Task AddActionAsync(BulkOperationCleanupAction cleanupAction, CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please manually add back this method (into non-async part) as an async-to-sync wrapper to AddAction. And make it obsolete.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It happened automatically, but I'll try what you said.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, making it obsolete didn't stop the async generator from generating calls to it, and since that is treated as an error, it prevents compilation. I fixed it manually, for the sake of the review, but is there a way to prevent this?

@hazzik hazzik added this to the 5.1 milestone Nov 30, 2017
@fredericDelaporte
Copy link
Member

fredericDelaporte commented Dec 1, 2017

The whitespace changes I have seen match editorconfig, they are "good" ones. But for easing reviews, global reformatting of files should be avoided. These changes normally do not occurs without an explicit call to a global reformatting action.

(That annoys me to not be able to fire a global reformat when I want to reformat the code I have just added, but well, global reformat annoys reviewers instead.)

@@ -169,6 +187,10 @@ public void ExecuteInserts()
/// </summary>
public void ExecuteActions()
{
if (session.Factory.Settings.IsQueryCacheEnabled)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. ExecuteInserts (a few lines above) should call this as well.
  2. It seems that it shall be called after execute

Copy link
Member

@hazzik hazzik Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also Execute(action) can be called directly. Which then will not "pre-invalidate" the cache.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bummer. I had made a mental note of it, but forgot.

@@ -56,6 +57,8 @@ public ActionQueue(ISessionImplementor session)

afterTransactionProcesses = new AfterTransactionCompletionProcessQueue(session);
beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue(session);

querySpaces = new HashSet<string>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better to utilize afterTransactionProcesses.querySpacesToInvalidate instead of creating an additional hashset.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or opposite.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or opposite. As you have 2 collections which hold exactly the same data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you've forgot to clear the field.

@gliljas gliljas force-pushed the unnecessarycachecalls branch from 1b5fce7 to 391d231 Compare December 2, 2017 00:20
@@ -42,6 +43,7 @@ public partial class ActionQueue

private readonly AfterTransactionCompletionProcessQueue afterTransactionProcesses;
private readonly BeforeTransactionCompletionProcessQueue beforeTransactionProcesses;
private readonly HashSet<string> executedSpaces;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gliljas I'm still not sure why you need an additional collection for executedSpaces if you can use AfterTransactionCompletionProcessQueue.querySpacesToInvalidate (just make it internal) which holds exactly the same data?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hazzik I think that it's a bit wrong to to piggyback on a class that is not involved in the PreInvalidation. However, I have now moved them together, so that the spaces added to
AfterTransactionCompletionProcessQueue comes from the same collection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don’t understand why you can not use the same collection

if (session.Factory.Settings.IsQueryCacheEnabled)
{
var spaces = executedSpaces.ToArray();
afterTransactionProcesses.AddSpacesToInvalidate(spaces);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make the parameter of AddSpacesToInvalidate IReadOnlyCollection<string> to avoid calling .ToArray().

Or, actually, remove that method and use executedSpaces directly instead of querySpacesToInvalidate collection.

@hazzik
Copy link
Member

hazzik commented Dec 3, 2017

@gliljas I'll make a PR into your branch. It is apparently easier to code than explain:)

@gliljas
Copy link
Member Author

gliljas commented Dec 4, 2017

@hazzik Console.WriteLine("Please do")

@gliljas gliljas closed this Dec 4, 2017
@gliljas gliljas reopened this Dec 4, 2017
@@ -119,6 +119,8 @@
- name: GetFieldValue
- name: IsDBNull
- name: WriteLine
- name: AddAction
containingTypeName: ActionQueue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maca88 we need a way to ignore a method if it's async counterpart is marked as obsolete. This would happen a lot in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add an option to make this possible in the following days, most likely tomorrow.

Copy link
Member

@hazzik hazzik Dec 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @maca88. Don't need to rush.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new option was added and released with the version 0.7.0. To ignore all obsolete async counterparts add the following lines inside the analyzation property:

ignoreAsyncCounterparts:
- rule: Obsolete

where Obsolete is a method rule defined as:

- filters:
  - hasAttributeName: ObsoleteAttribute
  name: Obsolete

@hazzik
Copy link
Member

hazzik commented Dec 5, 2017

@gliljas here is the PR: gliljas#1

I have removed the cache invalidation from the AfterTransactionCompletionProcessQueue (which is not really a queue, btw). I think this way it's much clear as it's not really AfterTransactionCompletionProcessQueue's job to invalidate caches.

Move invalidation of caches to ActionQueue
@hazzik
Copy link
Member

hazzik commented Dec 6, 2017

There is a bug in NSubstitute, which they seem do not want to acknowledge: nsubstitute/NSubstitute#70. I'll make a necessary workaround.

Fixed assertions in unit test.
@hazzik
Copy link
Member

hazzik commented Dec 6, 2017

@gliljas haha, I've made almost the same adjustments. Need to regenerate the tests thou.

@gliljas
Copy link
Member Author

gliljas commented Dec 6, 2017

@hazzik Ah, crap. Will do.


}

private bool IsRight(HashSet<string> x)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...added when debugging

hazzik
hazzik previously approved these changes Dec 7, 2017
Copy link
Member

@hazzik hazzik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@@ -4,8 +4,6 @@
applyChanges: true
analyzation:
methodConversion:
- conversion: Ignore
hasAttributeName: ObsoleteAttribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for now we want to generate async counterparts for obsoleted methods (as they were here before the methods were marked obsolete)

@@ -96,6 +94,9 @@
- conversion: Ignore
name: Exists
containingTypeName: AbstractCollectionPersister
- conversion: Ignore
name: QuoteTableAndColumns
containingTypeName: SchemaMetadataUpdater
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was obsoleted in 5.0, and did not have an async counterpart, so ignore it explicitly.

@@ -119,6 +120,8 @@
- name: GetFieldValue
- name: IsDBNull
- name: WriteLine
ignoreAsyncCounterparts:
- rule: Obsolete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New from 0.7.0. From now on we want to generate async counterparts for obsolete members, but do not want to call them. Also, we would want to add obsolete members by hands where they ceased to be generated.

@fredericDelaporte
Copy link
Member

I have relaunched the failed builds, they have got a network connectivity issue.

hazzik
hazzik previously approved these changes Dec 7, 2017
@hazzik
Copy link
Member

hazzik commented Dec 7, 2017

Thanks @fredericDelaporte

I think the title of the PR needs to be changed to something more descriptive

else
{
//camel cased field
var name = method.Member.Name.Substring(0, 1).ToLowerInvariant() + method.Member.Name.Substring(1);
Copy link
Member

@fredericDelaporte fredericDelaporte Dec 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced by the need of SetPropertyUsingReflection in its current form. I would rather have just a SetField(object instance, string name) helper for ceasing re-coding the reflection. (Generic if you prefer, this may allow handling static fields too by passing a null object.)
Assuming camel case for the backing field, while we have more and more readonly auto-properties, camel case underscore names (which should be the default naming but due to Java porting we often have camel case instead), is not good in my opinion. We may try to be smarter and handle many conventions (as do the property accessor strategy in mappings), but I am not convinced it is worth it. And readonly auto-properties will remain hard to handle.

At the very least if we keep this, it should throw when not finding the field.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a method I just copied straight from my own at-work code, where we usually use it to set properties on NH mapped domain classes, where the setters are hidden. It uses the current form to provide chaining, so that settings many properties doesn't become a burden. The lambda is just refactoring friendly (we don't use the field version, it was added here)

I don't mind having a simpler implementation.

At the very least if we keep this, it should throw when not finding the field.

Absolutely. That was an omission.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe adjust it by changing

if (field != null)
{
	field.SetValue(instance, value);
}

To:

if (field == null)
    var field = typeof(T).GetField("_" + name, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
    throw new InvalidOperationException($"Unable to find a setter or a backing field for property {method.Member.Name}");

field.SetValue(instance, value);

}

[MethodImpl(MethodImplOptions.Synchronized)]
public virtual void PreInvalidate(IReadOnlyCollection<string> spaces)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this class is meant to be extended. (It should likely be sealed, but it could be done only for 6.0.) Is virtual necessary for NSubstitute? If yes I quite dislike to have to do that for testing purpose.
Otherwise why should it be virtual?
(Same for the two next virtual methods.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more in the "virtual should be the default" camp, but yes, it's for NSubstitute. It's very hard to create real unit tests in NHibernate, since everything is so coupled. I figured making these methods virtual was a small price to pay for coming a bit closer to a unit test. An alternative (which I explored) is to create mocks of the underlying ICache, but that's actually quite hard, since the provider is instantiated using reflection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not as big as making something more visible just for test. So ok.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not as big as making something more visible just for test. So ok.

@fredericDelaporte I was unsuccessfully trying to comprehend your last comment. Could you please explain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching something to virtual just for tests is not, in most cases, a big concern. (At least, far from being as big as some other "just for tests" changes we have already seen in NHibernate, and which were raising the visibility of some members.)
So let it be for this case here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Thanks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the fixes, @hazzik

@hazzik
Copy link
Member

hazzik commented Dec 7, 2017

Removed TestExtensions and made UpdateTimestampsCache.Clear/ClearAsync virtual.

@gliljas gliljas changed the title Altered handling of cache invalidation Reduce the number of calls to UpdateTimestampsCache Dec 7, 2017
@hazzik hazzik merged commit 73797df into nhibernate:master Dec 8, 2017
@hazzik hazzik added the r: Fixed label Dec 8, 2017
@gliljas gliljas deleted the unnecessarycachecalls branch December 8, 2017 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants