-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Configure transaction timeouts via properties #17357
Configure transaction timeouts via properties #17357
Conversation
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.
Thanks.
So this needs two things:
- a rebase so that history is clean
- a proper assessment of the performance consequences of this change
/cc @Sanne
@@ -104,18 +111,21 @@ protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm) thro | |||
protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm, RunnableWithException afterEndTransaction) | |||
throws Exception { | |||
|
|||
TransactionConfiguration configAnnotation = getTransactionConfiguration(ic); | |||
Integer timeoutConfiguredForMethod = cache.computeIfAbsent(ic.getMethod(), | |||
(m) -> extractTransactionConfigurationTimeoutFromAnnotation(ic)); |
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.
We try to avoid lambdas in runtime code as they consume memory.
Also given it's a sensitive call site, better do a get()
and return it if not null before doing the computeIfAbsent()
(this would need to be moved to a method).
Now, I'm not exactly sure what cost this will have even when fully optimized.
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.
@gsmet computeIfAbsent
is right here, if you do a get first then compute the missing value you exposes yourself on concurrency issues.
The lambda can be an issue here as it's a capturing lambda and those have memory consideration, but I'm not against them (I know people on the community don't like them) it has a very slight impact with respect with transaction management ;)
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 saying computeIfAbsent
shouldn't be used. I'm saying you should do a get before and return directly if not null. You do the computeIfAbsent
after that. It's a CHM, you won't have concurrency issues doing that. Locking is less efficient when using computeIfAbsent so if you can avoid calling it, it's a win. And here, at some point, all the values will be in the cache so, after the warmup, things will be significantly better because you won't call computeIfAbsent()
at all. @Sanne was the one mentioning this to me first and I did numerous HV benchmarks proving this point (at least with the JDK used at that time).
As for lambdas, we decided as a community to avoid them as much as possible in runtime code (i.e. except if they really bring something to the plate. This is not the case 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.
After giving it some additional thoughts, I think the cache should be only used for methods annotated with timeoutFromConfigProperty
. It won't cost that much to not use the cache in other cases and it will avoid the cost of it entirely. My wild guess is that having the cache will have a higher cost if either we don't have anything set or if we are just using timeout
.
@marcelhanser and you thought it would be easy ;)
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 thought i do a small little pull-request and go to get a beer - buuuut^^
hmm... How do we know if a method is annotated with timeoutFromConfigProperty, without looking into it and do the costly reflection call? That was the reason for me to cache 'all'
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.
@gsmet oh yeah, I remember already having this discussion, sorry not to have remembered it ;)
So yes, making a get() before is good, there is a slight risk to compute two times the cache entry but it should not be an issue here as computing it is not very ressource consuming.
After giving it some additional thoughts, I think the cache should be only used for methods annotated with timeoutFromConfigProperty.
Retrieving annotation is costly I think, it's reflection so it may be cached, but still ... I remember we discuss it with @Sanne and agree that at some point we will cache the TransactionConfiguration
to avoid too much reflection.
So the correct thing to cache is not the timeout but the full TransactionConfiguration
object to avoid the reflective all.
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.
Hum, forget what I just told, caching the annotation will not be usefull here as we want to avoid the call to the config ...
So I'll keep it as is, or create a TransactionConfigurationInfo
object that will have the informations resolved from the annotation, as the annotation currently only deals with timeout it may not be necessary.
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.
So yes, making a get() before is good, there is a slight risk to compute two times the cache entry but it should not be an issue here as computing it is not very ressource consuming.
There isn't a race: first you try get()
, when it fails you still do the computeIfAbsent
so you'll have a lock. Worst thing that can happen is that the initial call does an additional pointless get()
... totally ok.
It's a good idea, but let me ask: what do you expect will happen if an annotated method is invoked while a Transaction is already started? How will multiple such annotations compose? I think this should be documented clearly. |
@@ -104,18 +111,21 @@ protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm) thro | |||
protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm, RunnableWithException afterEndTransaction) | |||
throws Exception { | |||
|
|||
TransactionConfiguration configAnnotation = getTransactionConfiguration(ic); | |||
Integer timeoutConfiguredForMethod = cache.computeIfAbsent(ic.getMethod(), | |||
(m) -> extractTransactionConfigurationTimeoutFromAnnotation(ic)); |
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.
@gsmet computeIfAbsent
is right here, if you do a get first then compute the missing value you exposes yourself on concurrency issues.
The lambda can be an issue here as it's a capturing lambda and those have memory consideration, but I'm not against them (I know people on the community don't like them) it has a very slight impact with respect with transaction management ;)
@@ -34,6 +40,8 @@ | |||
public abstract class TransactionalInterceptorBase implements Serializable { | |||
|
|||
private static final long serialVersionUID = 1L; | |||
private static final Logger log = Logger.getLogger(TransactionalInterceptorBase.class); | |||
private final Map<Method, Integer> cache = new ConcurrentHashMap<>(); |
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.
Maybe make it static
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 sure using Method as cache key is a good idea here, the method object is not very lightweigth and we may retain it for a long time.
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.
My experience with HV is: caching things at a method level costs a lot. Because you either use Method
or generate a full signature and in both cases, it's not fast.
As for making it static, depends. If you make it static, you will need to be able to clean it properly at shutdown.
.../src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java
Outdated
Show resolved
Hide resolved
4c41c6c
to
ffa831e
Compare
@gsmet I rebased and squashed my stuff together. |
@Sanne @loicmathieu @gsmet |
I'll have a look later today. IIRC, there are still things that need fixing. |
Resolves: quarkusio#15752
Resolves: quarkusio#15752
e727d9d
to
bbd8f25
Compare
@marcelhanser I rebased and added a new commit. The main idea is to avoid paying any cost in the normal case. I think I kept all the behaviors you implemented (thanks for the tests!). Could you have a look? |
Awesome - thx @gsmet - looks good to me. |
@Sanne could you have a second look at this one given I pushed an additional commit that changes quite a lot of things? Thanks! |
thanks @gsmet - looks nice, but help me understand why isn't the annotation lookup included in the cache as well? I would probably have cached the whole invocation into |
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.
As mentioned in other comments: I'm not sure why the cache isn't caching all operations from getTransactionTimeoutFromAnnotation
, but it's not a blocker.
@Sanne I did that on purpose. My wild guess is that the annotation lookup will take less time than the cache lookup based on a I'm not entirely sure as the annotation lookup does 2 map lookups based on I don't know if you have an easy way to check if my assumption is correct or not? |
@gsmet ok that sounds reasonable, thanks for explaining. I won't check it: IMO it's totally OK to use your experience in this case. |
To clarify my above comments: this PR is good for me. But it wasn't merged as it's in status "changes requested" by @gsmet . Have they been addressed? |
Merging, thanks everyone! |
Add caching of the Transaction-Timeout config values