-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Instrument Active Record transactions #49192
Conversation
64a1da7
to
4a1b6fd
Compare
Tracking Active Record-managed transactions seems to be a common need, but there's currently not a great way to do it. Here's a few examples I've seen: * GitHub has custom transaction tracking that monkey patches the Active Record `TransactionManager` and `RealTransaction`. We use the tracking to prevent opening a transaction to one database cluster inside a transaction to a different database cluster, and to report slow transactions (we get slow transaction data directly from MySQL as well, but it's still helpful to report from the application with backtraces to help track them down). * https://github.com/palkan/isolator tracks transactions to prevent non-atomic interactions like external network calls inside a transaction. The gem works by subscribing to `sql.active_record`, then piecing together the transactions by looking for `BEGIN`, `COMMIT`, `SAVEPOINT`, etc., but this is unreliable: - palkan/isolator#65 - palkan/isolator#64 * It looks like GitLab patches `TransactionManager` and `RealTransaction` to track nested savepoints. See palkan/isolator#46 This commit adds a new `transaction.active_record` event that should provide a more reliable solution for these various use cases. It includes the connection in the payload (useful, for example, in differentiating transactions to different databases), but if this change gets merged we're also planning to add details about what type of transaction it is (savepoint or real) and what the outcome is (commit, rollback, restarted, errored). This instrumentation needs to start and finish at fairly specific times: - start on materialize - finish after committing or rolling back, but before the after_commit or after_rollback callbacks - finish and start again when the transaction restarts (at least for real transactions—we've done it for savepoints as well but I'm not certain we should) - ensure it finishes if commit and rollback fail (e.g. if the connection goes away) To make all that work, this commit uses the lower-level `#build-handle` API instead of `#instrument`. Co-authored-by: Ian Candy <ipc103@github.com>
4a1b6fd
to
ce49fa9
Compare
Following up on rails#49192, this commit adds the transaction `outcome` to the payload, helpful for collecting stats on how many transactions commit, rollback, restart, or (perhaps most interestingly) are incomplete because of an error. The one quirk here is that we have to modify the payload on finish. It's not the only place this sort of thing happens (instrument mutates the payload with exceptions, for example), but it does mean we need to dup the payload we initialize with to avoid mutating it for other tracking. Co-authored-by: Ian Candy <ipc103@github.com>
For practical uses, a pity the transaction or some identifier or something is not available in the payload. I am pondering to add a (lazy) UUID to transactions to be able to group queries in logs. |
I also felt weird the whole connection object was made available, but I don't have a clear reason as to why. |
Right, in 7.2 it is going to be. There is now I have the transaction UUID in place, and will look at this event now. |
PR passing the transaction object here #51955. |
GitHub mainly uses it to differentiate transactions by connection (e.g. using the FWIW |
Motivation / Background
This Pull Request adds events to track when Active Record-managed transactions occur. Tracking Active Record-managed transactions seems to be a common need, but there's currently not a great way to do it. Here's a few examples we've seen:
TransactionManager
andRealTransaction
. We use the tracking to prevent opening a transaction to one database cluster inside a transaction to a different database cluster, and to report slow transactions (we get slow transaction data directly from MySQL as well, but it's still helpful to report from the application with backtraces to help track them down).sql.active_record
, then piecing together the transactions by looking forBEGIN
,COMMIT
,SAVEPOINT
, etc., but this is unreliable: - Corrupt transaction counts with Rails 7.1 restarting savepoint transactions palkan/isolator#65 - Corrupt transaction counts when connection closes unexpectedly palkan/isolator#64TransactionManager
andRealTransaction
to track nested savepoints. See Subtransactions tracking/preventions palkan/isolator#46Detail
This PR adds a new
transaction.active_record
event that should provide a more reliable solution for these various use cases. It includes the connection in the payload (useful, for example, in differentiating transactions to different databases),Additional information
#build-handle
API instead of#instrument
.Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]