-
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
feat: read only sessions #10077
feat: read only sessions #10077
Conversation
/cc @Sanne @stuartwdouglas @famod a less complex way of providing readonly transactions by using the transaction synchronization registry. |
I'd perfer this PR, nice work! I'd agree that resetting readOnly and FlushMode seems unnecessary. |
assertTrue(session.isDefaultReadOnly()); | ||
assertEquals(FlushMode.MANUAL, session.getHibernateFlushMode()); | ||
|
||
assertThrows(Exception.class, () -> forceRO()); |
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.
Exception
is a bit "broad". Any chance to narrow it down to a more specific exception?
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.
Yes, tests need to be reworked
Address adr = new Address(); | ||
adr.setStreet("rue du paradis"); | ||
entityManager.persist(adr); | ||
entityManager.flush(); |
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.
When called from testRO()
, does this flush()
make a difference?
Today a colleague of mine mentioned that in Spring Boot with read-only transactions you don't realize when you accidentally try to persist/update something.
Now I am wondering whether this would also happen in Quarkus when flush()
is not called (calling flush()
is not necessarily the default case).
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 will add more tests ...
@@ -119,6 +125,8 @@ protected Object invokeInOurTx(InvocationContext ic, TransactionManager tm, Runn | |||
tm.setTransactionTimeout(currentTmTimeout); | |||
} | |||
} | |||
// put the transaction configuration inside the synchronization registry to access it from Hibernate | |||
transactionSynchronizationRegistry.putResource(CONFIG_KEY, configAnnotation); |
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 think that we also need to make sure that the transaction is rolled back rather than committed if it is read only.
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 about this one. Read only transactions are only a hint sent to the underlying data-remated technolohy (here, the Hibernate Session), they are not realy a property of the transaction. If you use transaction with JDBC (or MongoDB), they will have no effect (this needs to be documented as a hint not honored by all data access layer).
Of course, we can set the transaction to be rollback only at the transaction manager level. But read only transaction are not a JTA standard so I will be reluctant that they act at JTA level.
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.
Two things to keep in mind:
- some databases can actually start a "read only transaction", and this will get you performance benefits; but to take advantage of this, you need to be able to start the transaction in RO mode from the very beginning. Might be out of scope? e.g. I don't think Agroal allows this today.
- some (other) databases actually perform better on commit than on rollback. So I'm not sure about rolling back being a good idea as an enforced mode - I'm aware it's popular among some people, but I'd not be sure about enforcing this.
Why would you roll it back? Just to be really sure that no changes are written? To achieve such an effect, I think it would be more useful to actually wrap our data-access APIs with a read-only decoration, so to throw a clear exception on any attempt.
If we wanted to do that, I suppose we'd need the TX to be marked as transaction since its creation (so to affect any resources we provide within its scope); which would have the added benefit of allowing optimisation #1 above, should we want to implement that in the future.
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 just don't like the idea that a read only TX can still commit data, and if this does only affect hibernate it just feels wrong that you could still use JDBC to do writes in a supposed read only TX.
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 agree with that. Would be nice to be able to wrap such other APIs to prevent it.
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 we have two options I believe we all could agree on:
- convert this into a
@SessionConfiguration
so that it's clearer that this is a tuning hint for Hibernate only. - Implement a real read-only transaction support.
While 2. is probably more valuable, it's clearly a long way to get there: at very least it needs patches in multiple components.
So let's go with approach 1 and make it very clear that:
- this is Hibernate specific
- it's only a hint
Hopefully such annotation would be used for several more useful tuning options? It begs the question of how a single annotation becomes extensible, maybe it will need multiple annotations?
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.
OK for converting to @SessionConfiguration
.
There is two ways of implementing this:
-
Use a
@Transactional
CDI interceptor that then try to locate this@SessionConfiguration
annotation: the session configuration will have the same scope as the transaction, and we can still use the Transaction Synchronization Registry to communicate between the interceptor and the entity manager factory. -
Use a
@SessionConfiguration
CDI interceptor: in this case we cannot be sure that we are in the context of a transaction so we cannot use the Transaction Synchronization Registry as a communication layer between the interceptor and the entity manager factory. We can still use Arc inside the interceptor to access the entity manager. If we implement this, a user could have a read only scope smaller than the transaction one (and possibly switch to read only / read write multiple times inside the same transaction).
WDYT ?
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.
@Sanne can you give your advice on how to implement read only sessions with @SessionConfiguration
as we discuss in this thread ?
I proposed two way to do this and need your input on which one is better ;)
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.
@Sanne can you give your advise on the best way to update this old PR to use @SessionConfiguration
as we discussed ? I proposed two ways here #10077 (comment) can you give me some feedback ?
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 would go with a take on approach 1) (some of this may be slightly overkill)
- Add an @TransactionConfig meta annotation
- @SessionConfiguration is annotated with @TransactionConfig
- The transaction interceptors are modified to discover @TransactionConfig annotations on the method or class, and put them into the TSR
- Have hibernate pull this info out of the TSR and use it to decide on how to proceed
There will be caching needed here, we don't want to be reading annotations every transaction. I am not sure if @TransactionConfig is really needed, but the alternatives are hard coding a hibernate annotation into the TX subsystem, or sticking every annotation into the TSR (which may end up having perf impact).
@Sanne does this sound reasonable?
So it seems like all this is really just to run 3 lins of code:
Why don't we just put these in a utility method? I don't really think the annotation based approach buys you much here. |
Developers used to use annotation for such contextual configuration. Moreover, in Panache, they don't manipulate the entityManager by themself. I also think it should be done as early as possible, ideally at session creation time (like it is done today). |
Are we yay or nay on this one? @stuartwdouglas @Sanne ? |
I think it should be done as a SessionConfiguration rather than a TransactionConfiguration, to make it clear what is going on. There are also other uses for SessionConfiguration, e.g. you could use it to configure flush mode or ask for a stateless EM. |
@stuartwdouglas OK for |
e5717aa
to
b09933f
Compare
b09933f
to
0afb1a9
Compare
0afb1a9
to
a92bfa5
Compare
@stuartwdouglas @Sanne I update the impl as suggested. |
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.
Mostly looks really good.
Session session = newEntityManager.unwrap(Session.class); | ||
session.setDefaultReadOnly(true); | ||
session.setHibernateFlushMode(FlushMode.MANUAL); | ||
//TODO maybe set back the flush mode / default read only somewhere ? |
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 don't think we need this todo, current impl is correct IMHO
@@ -96,6 +105,35 @@ private TransactionConfiguration getTransactionConfiguration(InvocationContext i | |||
return configuration; | |||
} | |||
|
|||
private Map<Class<?>, Annotation> getAdditionalTransactionalConfiguration(InvocationContext 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.
I think this should be cached, as it is slow. Ideally we would pre-compute this at build time.
Also does this work in native mode? I think it will but an integration test would be helpful.
I think it's a mistake to add a new annotation for this, especially when the annotation has just one boolean member. I could live with it if:
But in the current form it looks like a wart. It looks very much under-designed, like it's trying to solve one very very specific problem without stepping back and contextualizing the problem in terms of a class of similar problems. |
And I even have an additional objection: I don't think we should be defining APIs and new programming models in |
@gavinking thanks for your feedback, the current design has been discussed with @Sanne and @stuartwdouglas in this PR (and another one) comments. First, I propose to add a This new annotation has been called As you detected, it has been put on the wrong package, it should reside inside |
a92bfa5
to
657aedc
Compare
Right, but the thing is that I don't see how other kinds of session configuration fit into this model. Specifically I don't see how filters fit in. Annotations simply can't do some things that calling functions can do. That's what I mean when I say it looks underdesigned. I don't think it addresses (even in principle) all the things one wants to do when "configuring" a session. |
FTR I agreed with Stuart's original comment months ago that this would be better as an API and I didn't speak up at the time because I had nothing additional to add to that. Apparently there have been additional discussions since then which were not captured here on the issue. (They should have been, IMO.) |
@gavinking some discussions has been made on the #7455 that has been closed on the favor of this one. An API to configure the session already available as one can inject the EntityManager (or get it from a Panache entity/repository) and extract the Session from it. Something like this (not tested) @Inject EntityManager entityManager;
Session session = entityManager.unwrap(Session.class);
session.setDefaultReadOnly(true);
session.setHibernateFlushMode(FlushMode.MANUAL); We can of course provides something inside Panache to facilitate. The choice of an annotation is that it's a very common task to set the session as readonly for performance reason, and some user ask for it. Spring provides the same facility. Now, if you think that it's a badly design API, I'm open for suggestion. Maybe @stuartwdouglas or @Sanne have some throught on it ? |
Look, the over-arching problem we have here is that everyone is trying to add their own little bits and pieces to Quarkus to try and make Hibernate easier to use. But it doesn't. It makes it harder to use because you're smearing the API of Hibernate out across many different modules and the user has no idea where to go to get the thing they're looking for. Hibernate-related APIs belong in hibernate-xxx.jar, not in Quarkus. The Quarkus extensions exist to provide integration code, not APIs. We really need to get a handle on this before it gets out of control. If Hibernate is missing some API that Quarkus users are going to need, go to https://github.com/hibernate and add it there. Because otherwise we're going to end up with a clusterfuck of everyone adding their own teensy little Hibernate APIs that address some teensy discomfort but without a global view of how to solve problems without all sorts of inconsistency and redundancy. |
@gavinking I understand your point, and yes, the Hibernate ORM extension is for integration purpose not API. @Sanne and @gsmet are the maintainers of the Hibernate ORM Quarkus extension, I'll let them decide if we close this one and the corresponding #6414 issue as won't fix, or provide something else somewhere else (on Panache which have an API, or on Hibernate itself as you suggested). |
Let's close this one for now. We can rediscuss it later. |
This is a new attemp to provides so called read only transactions.
Instead of using a CDI interceptor like with #7455 it put inside the transaction synchronization registry the transaction configuration object, retrieve it inside Hibernate and update the session.
I made some choices that should be discussed:
TransactionConfiguration
annotation inside the registry for future usage if needed. Maybe I need to create a separate object.If we agree it is a better implementation as #7455 I'll provides more test and some documentation