-
Notifications
You must be signed in to change notification settings - Fork 21.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
Omit BEGIN/COMMIT statements for empty transactions #32647
Conversation
r? @pixeltrix (@rails-bot has picked a reviewer for you, use r? to override) |
4ee21ad
to
710c810
Compare
@@ -112,7 +112,7 @@ def discard! # :nodoc: | |||
private | |||
|
|||
def connect | |||
@connection = Mysql2::Client.new(@config) | |||
@connection = LazyTransactionProxy.new(Mysql2::Client.new(@config)) |
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 we can push this lazy proxy creation up to the abstract class and call that method to avoiding having to remember to wrap it every time a driver connection is create.
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've added a private connection=
method to the abstract adapter that wraps the connection in a proxy, and changed the concrete adapters to use it when they create a connection.
e674c1e
to
2d8da6d
Compare
@eugeneius Let me take a look at if it "breaks" Oracle enhanced adapter. If it does I am happy to update Oracle enhanced adapter. |
Thanks @yahonda! 🙌 I've tried to make the implementation backwards compatible, so please let me know if it does break - it might mean that I made an incorrect assumption about the adapter interface. |
This test failure was legitimate: https://travis-ci.org/rails/rails/jobs/368924286#L1171-L1175 Executing a prepared statement doesn't touch the connection object (at least on the SQLite adapter), but does require the connection's transaction state to be up to date. I've fixed the problem by materializing transactions in 😕 |
b08216a
to
2839b04
Compare
Thanks @eugeneius This turned out very nicely /cc @Fryguy |
2839b04
to
47fbef6
Compare
I've moved the |
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.
This feels like a lot of complexity for very little gain, but the implementation seems fine.
module ActiveRecord | ||
module ConnectionAdapters | ||
class LazyTransactionProxy < SimpleDelegator # :nodoc: | ||
class StatementProxy < SimpleDelegator |
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.
Does it make sense to use DelegateClass
instead for both of these?
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 so, because the type of object they wrap depends on the adapter in use; there's no single class we can provide with the full set of methods they need to support.
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.
This comment did make me realise that I forgot to :nodoc:
the inner class, though.
47fbef6
to
4d08d52
Compare
Oracle enhanced adapter needs some update to support this pull request. Here are CI results for this pull request https://travis-ci.org/yahonda/oracle-enhanced/builds/370051142 . I am still having some jet lags due after a long flight, I'll take a look at this result in detail tomorrow. |
@connection = connection | ||
end | ||
|
||
def method_missing(*) |
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.
This needs to implement respond_to_missing?
as well
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.
Delegator
already provides an implementation of respond_to_missing?
:
https://github.com/ruby/ruby/blob/v2_4_4/lib/delegate.rb#L95-L104
Since we call super
in method_missing
, we should do the same for respond_to_missing?
:
def respond_to_missing?(*)
super
end
I've added this to both classes, if only to document that using Delegator
's behaviour unmodified is intentional.
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 a bit confused as to why we need to call materialize_transactions
in method_missing
, but not respond_to_missing?
. These two methods should always be in sync.
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.
The contract between the two methods is that respond_to_missing?
will return true iff called with a method that method_missing
would dispatch. Usually this means making equivalent changes to both methods to keep them in sync, as you've noted. In this case however, calling materialize_transactions
in method_missing
doesn't affect which methods are dispatched; it's purely a side effect, and so doesn't require an equivalent change in respond_to_missing?
.
Another way of looking at it: we need to call materialize_transactions
in method_missing
because we don't know whether the call to super
will touch the socket. We don't need to call it in respond_to_missing?
because we know that calling super
won't touch the socket.
__getobj__ | ||
end | ||
|
||
def method_missing(*) |
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.
This needs to implement respond_to_missing?
as well
fb88426
to
16925ad
Compare
@yahonda can you try triggering another build? I renamed |
A few thoughts from an initial read-through: Yay! ❤️ As I mentioned in Pittsburgh, it's awesome to have an implementation to look at -- this one's been sitting on the "nice to have" list for a while. My gut feeling is that this "should" be closer to the transaction manager rather than the connection -- particularly the deferral stack, though the connection of course needs to be responsible for triggering the materialize. Failing that, I also wonder about putting the functionality on the abstract connection itself rather than a proxy. I don't mind the code complexity needed for more thorough bookkeeping, but the proxy feels architecturally heavyweight. Side-thought that would probably just distract us to pursue right now, but I'll mention anyway: this has some interesting parallels (deferring work on the connection until we need to run a real statement, and then potentially giving that some special handling) with #28200 and friends. Finally, the big one, because it's about behaviour rather than implementation: I believe it's important that savepoints don't materialize the transaction -- that they too get deferred. To me, the ideal version of this change allows us to create transactions more freely, which means we can default |
CI is green now with Oracle enhanced adapter. As a nature of Oracle database transaction management |
🚀🌈🚀🌈🚀😊
…On Thu, Aug 23, 2018 at 03:10 Matthew Draper ***@***.***> wrote:
Sorry I left this sitting for ages, and thanks again!
❤️ 💚 💙 💛 💜
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#32647 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAAAx-Yub-sVym022pHU6tES3UPfhBMaks5uTn-hgaJpZM4Tcdd->
.
|
Thanks so much Matthew and Eugene and all others who took a stab at this and helped it go forward |
Wow. I was just wondering about omiting empty transactions and found out that it was merged several hours ago. Thank you, rails community. You are awesome <3 |
It broke due new to [Lazy transactions](rails/rails#32647) feature. Workaround is taken from the Isolator gem: palkan/isolator#20
I would love if this would be backported to 5.2. Is that something not out of the realm of possibility? |
I don't think so; 5.2 is only supported for security fixes at this point. |
@eugeneius I'm pretty sure that 5.2 is still receiving some bug fixes but they'll be limited in scope unless warranted by a new Ruby version or some other similar event (e.g. we backported a whole bunch of stuff to Rails 4.2 to make it work with Ruby 2.4's changes to However, even given the above I agree that it's unlikely to be backported unless something else makes it necessary. |
Omit BEGIN/COMMIT statements for empty transactions rails/rails#32647
for implementing supports for lazy transactions rails/rails#32647 (comment)
for implementing supports for lazy transactions rails/rails#32647 (comment)
* Support lazy transactions * aaded materialize_transactions guard to connection uses. for implementing supports for lazy transactions rails/rails#32647 (comment) * Moved materialize_transactions calls * Fix typo * Materialize transaction in do_execute Co-authored-by: Yehuda Goldberg <[email protected]>
Rails 6 adds support for lazy transactions: rails/rails#32647 Specifically, it has all adapters default to claiming to be able to support lazy transactions, and adds logic in them to actually support it. Unfortunately, that means that the SeamlessDatabasePool adapter is now also claiming to be able to support lazy transactions, despite not having any logic added to it to do so. We could probably go in and teach it how to correctly support lazy transactions, but since we don't currently require that functionality and because we plan to migrate off of SeamlessDatabasePool, we instead simply update the adapter to explicitly NOT support lazy transactions.
It broke due new to [Lazy transactions](rails/rails#32647) feature. Workaround is taken from the Isolator gem: palkan/isolator#20
Fixes #17937.
If a transaction is opened and closed without any queries being run, we can safely omit the
BEGIN
andCOMMIT
statements, as they only exist to modify the connection's behaviour inside the transaction. This removes the overhead of those statements when saving a record with no changes, which makes workarounds likesave if changed?
unnecessary.This implementation wraps the raw database connection in a proxy which buffersbegin_transaction
calls, and materializes them the next time the connection is used, or discards them ifend_transaction
is called first. This approach makes new connection usage inside the adapters behave correctly by default. Third party adapters can use thebegin_transaction
/end_transaction
APIs to opt in to the new behaviour, but will still work even if they don't.This implementation buffers transactions inside the transaction manager and materializes them the next time the connection is used. For this to work, the adapter needs to guard all connection use with a call to
materialize_transactions
. Because of this, adapters must opt in to get this new behaviour by implementingsupports_lazy_transactions?
.If
raw_connection
is used to get a reference to the underlying database connection, the behaviour is disabled and transactions are opened eagerly, as we can't know how the connection will be used. However when the connection is checked back into the pool, we can assume that the application won't use the reference again and reenable lazy transactions. This prevents a singleraw_connection
call from disabling lazy transactions for the lifetime of the connection.