Skip to content

Comments

Ensure SQLite transaction default to IMMEDIATE mode#50371

Merged
byroot merged 1 commit intorails:mainfrom
fractaledmind:ar-sqlite-immediate-transactions
Jul 26, 2024
Merged

Ensure SQLite transaction default to IMMEDIATE mode#50371
byroot merged 1 commit intorails:mainfrom
fractaledmind:ar-sqlite-immediate-transactions

Conversation

@fractaledmind
Copy link
Contributor

@fractaledmind fractaledmind commented Dec 16, 2023

Motivation / Background

As SQLite's popularity grows as a production database engine for Rails applications, so does the need for robust and resilient default configuration. One of the most common issues faced when using SQLite in a Rails application are the occasional ActiveRecord::StatementInvalid (SQLite3::BusyException: database is locked) exceptions. These occur when a DEFERRED transaction attempts to acquire the SQLite database lock in the middle of a transaction once hitting a write query while another connection holds the database lock. Since this occurs in the middle of a transaction, SQLite does not attempt to retry to transaction by calling the set busy_handler/busy_timeout callback, but instead immediately errors with a busy exception.

Detail

This PR has considered two different approaches over the course of its existence:

  1. globally change the default transaction mode for the SQLite adapter from DEFERRED to IMMEDIATE
  2. only change the transaction mode from DEFERRED to IMMEDIATE for transactions that Rails uses to wrap ActiveRecord write operations

Various test failures where tests are manually creating transactions, along with a stated preference to not expose a generic mode option to the #transaction method, which would have no meaning or purpose for other adapters, led me to go with option 2.

With option 2, ActiveRecord::Base#with_transaction_returning_status now calls Adapter#transaction_returning_status instead of Adapter#transaction directly. By default, that method is simply as alias, but the SQLite3 adapter implements the transaction_returning_status method to ensure that the immediate transaction mode is used. Transaction mode setting is done via the use_*_transaction_mode! methods added to the SQLite3 adapter, which the test_fixtures.rb module uses as well to ensure that fixture transactions always use deferred transactions.

Additional information

Alongside #50370, this PR stabilizes SQLite's ability to handle concurrency without throwing intermittent but frequent busy exceptions.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Changes that are unrelated should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@byroot
Copy link
Member

byroot commented Dec 16, 2023

CI failures look legit:

Error:
DefaultScopingTest#test_default_scope_with_conditions_hash:
RuntimeError: Wrapped undumpable exception for: ActiveRecord::StatementInvalid: SQLite3::BusyException: database is locked
    /usr/local/bundle/gems/sqlite3-1.6.9-x86_64-linux/lib/sqlite3/resultset.rb:162:in `step'
    /usr/local/bundle/gems/sqlite3-1.6.9-x86_64-linux/lib/sqlite3/resultset.rb:162:in `next_hash'
    /usr/local/bundle/gems/sqlite3-1.6.9-x86_64-linux/lib/sqlite3/resultset.rb:97:in `next'
    /usr/local/bundle/gems/sqlite3-1.6.9-x86_64-linux/lib/sqlite3/resultset.rb:125:in `each'

@fractaledmind
Copy link
Contributor Author

@byroot: Interesting. I cannot reproduce locally.

$ bundle exec rake test:sqlite3
Using sqlite3
Run options: --seed 41270

Finished in 106.020541s, 81.4182 runs/s, 276.5973 assertions/s.
8632 runs, 29325 assertions, 0 failures, 0 errors, 33 skips

I'm guessing Buildkite runs tests in parallel? How could I best mimic Buildkite's test setup on my local machine? I need to reproduce to start hunting down the best way to get tests passing on Buildkite.

@byroot
Copy link
Member

byroot commented Dec 18, 2023

I'm guessing Buildkite runs tests in parallel?

Not as far as I know. However it runs test on Linux, perhaps it's not linking to the same version of sqlite3?

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch from dba5e67 to 4dc199a Compare December 23, 2023 04:21
@fractaledmind
Copy link
Contributor Author

My first experiment failed, but I have honed in on where the issue arises, but without getting to the point of understanding precisely what the issue is.

The issue has to do with setting up fixtures. My bet is that the setup_shared_connection_pool method is where things get setup in a way that doesn’t like immediate transactions. At the theoretical level, it would seem to the case that Rails is opening transactions for fixtures that never make a write. So, when transactions were deferred, those transactions never needed the db lock and thus ran fine. In immediate mode, they ask for the lock and doesn’t receive it in the timeout window. Locally, it must be the case that my laptop is faster than Buildkite’s machines and so my connections can complete their linear tasks in less than the timeout.

I can see that we open transactions in 4 connections, 3 of which are for the same database file (fixtures.sqlite3). And the lock exception is thrown on the last connection. I'm not immediately certain why we need to have multiple connections to the same database file. But, regardless, I haven't yet found why trying to open an immediate transaction on the final connection throws a "database locked" exception. Will keep debugging.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch from 1106c0a to 96ca4e6 Compare December 27, 2023 12:26
@fractaledmind
Copy link
Contributor Author

Making progress. I'm down to a few sporadic failures where the tests simply take longer than the busy_timeout and thus the busy exception is thrown.

@dhh
Copy link
Member

dhh commented Dec 31, 2023

What is the default busy timeout? Should it be higher?

@fractaledmind
Copy link
Contributor Author

fractaledmind commented Dec 31, 2023

What is the default busy timeout? Should it be higher?

For tests, Rails sets up connections for the fixtures with a 5 second timeout (see here). This matches the 5 second default that Rails generates for a SQLite database.yml file.

I'll try bumping the test timeout and seeing if that resolves the tests, tho it will be flaky of course. I can also keep working on trying to shore up the underlying logic for handling fixtures in tests. If we don't keep so many connections to the same database in our tests, this problem goes away more resiliently.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch from 96ca4e6 to 00417ee Compare December 31, 2023 22:08
@fractaledmind
Copy link
Contributor Author

Still failing. It is a pretty consistent, and small, collection of test cases that are failing. My current hypothesis is that these tests have connections that aren't covered by the config.yml setup that sets the transaction mode for fixtures to deferred. Since the fixture setup starts a transaction for every connection present at the start of the test, if any connection is open that hasn't been configured to use the deferred transaction mode, the fixture setup will open an immediate transaction. Trying to open a second transaction to the same database after an immediate transaction has been opened will immediately throw a busy exception.

I think that we may need to pair this PR with a refactor of the fixture_setup to always open deferred transactions for dealing with fixtures.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch 3 times, most recently from 32786a0 to 989c876 Compare January 1, 2024 13:09
@fractaledmind
Copy link
Contributor Author

fractaledmind commented Jan 1, 2024

@dhh: Unrelated ujs and app:update test failures aside, this PR is green. Needed to ensure that fixture transactions were deferred. Instead of introducing a new option to the transaction method felt improper, as transaction modes are not meaningful to other DBMS. So, I isolated the change to the SQLite3 adapter and fixtures. Then, I needed one other fix for a transaction test.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch 2 times, most recently from ef7a7fa to 5d531a1 Compare January 1, 2024 13:38
@dhh
Copy link
Member

dhh commented Jan 1, 2024

Looks like there are still busy failures on the CI?

IMG_3896

@fractaledmind
Copy link
Contributor Author

Ah, I pushed what I thought was an irrelevant change (removing the default_transaction_mode: deferred from the test config), and it was clearly very relevant. There are a number of tests it seems that open multiple transactions to the same database. With the default busy_timeout blocking GVL, these tests take 5 seconds and then fail. It seems that this PR needs the non-blocking busy_handler_timeout that I am working to get into the SQLite3 repo (sparklemotion/sqlite3-ruby#443) after another PR discussion (#50370) ended with the decision to move this logic from Rails down to the SQLite3 repo. I will inject that fix locally and try to isolate those tests that require running deferred transactions and fix those.

However, it seems that this PR is blocked for now.

@fractaledmind
Copy link
Contributor Author

An alternative thought I just had:

We could go another direction altogether and only have the automatically created transactions used to wrap ActiveRecord's write operations run as immediate, while keeping the default behavior of deferred transactions for manually created transactions. I will see if I can make that happen.

@fractaledmind
Copy link
Contributor Author

fractaledmind commented Jan 1, 2024

@dhh: I found the entry point for ActiveRecord write operation transactions. I have refactored the entire PR to only make these operations INMEDIATE transactions. So, the default of deferred transactions remains the same. This was a long road, as I naively presumed that both automatic and manual transactions would basically always have a write operation. After slogging thru tests, I realize that a more isolated change is probably a more solid and resilient change for Rails. There are the flaky app:update and ujs test failures, but the ActiveRecord tests are all green (sorry for the false promise last time; double checked this time 😉)

ensure_finalize = !connection.transaction_open?

connection.transaction do
connection.transaction(mode: :immediate) do
Copy link
Member

Choose a reason for hiding this comment

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

Don't want to have a SQLite3 specific mode concern leak all the way into the other adapters and the root transaction setup. There's gotta be a better way with less collateral damage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I had focused on just the fixtures, I introduced use_deferred_transaction_mode!, use_immediate_transaction_mode!, and use_default_transaction_mode! methods to the SQLite3 adapter and called them before calling #transaction. This original implementation was to avoid precisely this leak, tho it traded that with "spooky action as a distance". When I pivoted to this approach, I thought that some users are likely to want control over the transaction mode when starting a transaction manually. From this use-case point of view, I thought that the "spooky action at a distance" approach of:

connection.use_immediate_transaction_mode!
connection.transaction do
end

felt off.

I am open to going in that direction if you think that the conceptual leak of the reality that SQLite has transaction modes and other DBMSes don't is a larger issue than the interface for end-user control of SQLite's transaction modes.

I did make sure to ensure that none of the other adapters change their interface, only their method signature. A couple other possibilities:

  1. implement a generic options = {} interface for the transaction method where currently only the SQLite adapter looks into the hash to see if a :mode was set
  2. keep the mode = nil interface for the transaction method, but explicitly raise if that option is passed into the methods of the other adapters

Or, again, we could revert to the connection.use_immediate_transaction_mode! approach I was using earlier. Of course, I'm also open to any other suggestions.

@dhh
Copy link
Member

dhh commented Jan 1, 2024

Other option is allowing the adapters to be able to take over that transaction method.

@fractaledmind
Copy link
Contributor Author

Other option is allowing the adapters to be able to take over that transaction method.

True. I can try that out and push it up and you can consider that as a wholistic diff. Will do that shortly.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch 2 times, most recently from 8dece5c to 93fc4a5 Compare January 1, 2024 19:56
@fractaledmind
Copy link
Contributor Author

@dhh: ActiveRecord::Base#with_transaction_returning_status now calls Adapter#transaction_returning_status instead of Adapter#transaction directly. By default, that method is simply as alias, but the SQLite3 adapter implements the transaction_returning_status method to ensure that the immediate transaction mode is used. Transaction mode setting is done via the use_*_transaction_mode! methods added to the SQLite3 adapter, which the test_fixtures.rb module uses as well to ensure that fixture transactions always use deferred transactions.

This implementation doesn't leak the concept of "transaction modes" to any other adapters. WDYT?

(as always, rake test:ujs tests are the only thing making this build red)

@dhh dhh requested a review from byroot January 1, 2024 21:41
@fractaledmind
Copy link
Contributor Author

@matthewd @byroot: I resolved conflicts on the CHANGELOG, and I will squash commits once we agree on a direction. I'd greatly appreciate your thoughts here. I updated the initial post to match the current state of affairs, and I'm happy to answer any questions you might have. This really is a big pain for production SQLite applications, and I'd love to find a path forward here that works for everyone.

@fractaledmind
Copy link
Contributor Author

@byroot @matthewd: Another CHANGELOG resolution. I would love to get some eyes on this.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch from 5482aa1 to 9fa0de7 Compare May 30, 2024 22:47
@rails-bot rails-bot bot added the railties label May 30, 2024
@djmb
Copy link
Contributor

djmb commented Jun 3, 2024

@fractaledmind - we've been running ONCE Campfire (https://once.com/campfire) with this setting since it was released and it's worked well so, +1 for this. With deferred transactions, busy exceptions were a constant problem. With immediate transactions plus a GVL releasing timeout they were virtually non-existent.

I guess if you had an app that heavily relied on read only transactions, deferred mode might be preferable for those. In that case I think you'd still want this setting as the default so that transactions created internally for writes by ActiveRecord are not deferred.

So in that case it would be nice to be able to set the mode per-transaction though that would leak sqlite specific settings into the transaction method. Maybe something to revisit later if it is a real issue.

@byroot byroot force-pushed the ar-sqlite-immediate-transactions branch from 9fa0de7 to 1cc8528 Compare June 13, 2024 08:05
@byroot
Copy link
Member

byroot commented Jun 13, 2024

I modified your PR so it's enabled in the adapter by default rather than in the default generated config, as to have coverage on CI.

But it's causing issues with transactional fixtures, so I'll need to dig more into this when I got time.

@fractaledmind
Copy link
Contributor Author

I modified your PR so it's enabled in the adapter by default rather than in the default generated config, as to have coverage on CI.

But it's causing issues with transactional fixtures, so I'll need to dig more into this when I got time.

@byroot: Sorry for the delayed response, been on vacation. I had originally enabled this in the adapter as well and ran into the same issues. Basically, Rails' transaction fixtures require DEFERRED transactions. I began fixing this by introducing a keyword option to the transaction method that allowed one to specify which kind of transaction needed, but DHH didn't love the idea of a SQLite-specific concept leaking into the global ActiveRecord transaction method signature. I then implemented #use_deferred_transactions! and #use_immediate_transactions! methods in the SQLite3 adapter, and called them from within the transaction fixtures, but changes to the fixtures logic in the interim broke that logic.

Eventually, I landed on trying to simply push this out of Rails and into the application. I need to add back a mechanism for specifying a specific transaction mode (either an additional kwarg in transaction or the #user_*_transactions! methods), but I pushed up that first change to ensure that CI would pass (which it did). By pushing this change out of Rails, I ensure that transactional fixtures in Rails' CI always work. But, I wasn't able to test, while on vacation, if transaction fixtures in the application's CI would then start failing with such a change.

If you'd like, I can write up a more detailed explanation of the difficulty of patching transactional fixtures to use DEFERRED transactions, while having the rest of AR default to IMMEDIATE transactions, given the current state of that code (TL;DR the abstraction doesn't provide a clean place to make the change for only transactional fixtures, as the transaction itself has been moved up into AR now).

Finding some solution for this is very important for Rails 8 tho, so whatever it takes, I'm here to do whatever I can.

@byroot
Copy link
Member

byroot commented Jul 5, 2024

The fixtures transaction uses joinable: false, you can probably just use that to decide on defered VS immediate.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch from 1cc8528 to 71ef352 Compare July 5, 2024 12:42
@fractaledmind
Copy link
Contributor Author

@byroot: Interesting thought! This helped me finalize a comprehensive approach. I just pushed a new commit which handles 3 details:

  1. All SQLite3 connection default to using IMMEDIATE transactions
  2. Any joinable transaction issues a DEFERRED transaction
  3. Users can manually specify that a transaction be either IMMEDIATE or DEFERRED, but only for the SQLite3 adapter

This provides the full suite of tools to ensure applications can be built precisely as needed, but with productive defaults.

A couple of notes:

  • I decided against allowing the default transaction mode to be overridden in the database.yml, as I can't see a case where a Rails app would need the opposite default. Most transactions will be write operations, which should be immediate transactions. However, I'm open to your opinion here
  • Strictly speaking, I could remove the mode: option from the SQLite3Adapter#transaction method signature, and simply rely on the !joinable? == :deferred logic added to give users control over transaction mode. I added the new kwarg for increased clarity and to avoid overloading the meaning of joinable. Again, though, I am open to your opinion.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch 2 times, most recently from 7a792ff to 80270fc Compare July 5, 2024 13:08

# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
def begin_db_transaction(mode = nil) end
Copy link
Member

Choose a reason for hiding this comment

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

begin_db_transaction is public API I'd rather not change it.

I'd prefer to add a begin_deferred_db_transaction that is # :nodoc: and only for internal use. On adapters other than Sqlite3 it would just be an alias to begin_db_transaction.

It's also more consistent with begin_isolated_db_transaction

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need a way to compose deferred and isolated transactions. You can see this need in the changed test in activerecord/test/cases/adapters/sqlite3/transaction_test.rb. Simply having begin_deferred and begin_isolated doesn't quite provide enough for such composition. I see the point of not wanting to change the public API of begin_db_transaction. Maybe an alternative is to move away (first with backwards compat) from begin_*_db_transaction methods and add attr_accessors for different transaction modifiers that begin_db_transaction uses to craft the desired transaction?

Copy link
Member

Choose a reason for hiding this comment

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

We need a way to compose deferred and isolated transactions.

If app developers have to concern themselves about whether a transaction should be deffered or immediate, then I'm not sure deferred is a suitable default.

That's not a concern I'm personally willing to expose to users, even more so because it has no equivalent on other adapters. To me that's a deal breaker. If someone else feel strongly about it I'm not gonna oppose, but I won't merge that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

App developers will rarely need to concern themselves with whether a transaction should be deferred or immediate, in much the same way they rarely need to concern themselves with whether a transaction should be joinable or not. The main situation in which an app developer would need to concern himself is in fact shared between these concerns: nested transactions. And the test in activerecord/test/cases/adapters/sqlite3/transaction_test.rb demonstrates an example:

test "opens a `read_uncommitted` transaction" do
  with_connection(flags: shared_cache_flags) do |conn1|
    conn1.create_table(:zines) { |t| t.column(:title, :string) } if in_memory_db?
    conn1.transaction do
      conn1.transaction_manager.materialize_transactions
      conn1.execute("INSERT INTO zines (title) VALUES ('foo')")

      with_connection(flags: shared_cache_flags) do |conn2|
        conn2.transaction(joinable: false, isolation: :read_uncommitted) do
        conn2.transaction(joinable: false, isolation: :read_uncommitted) do
          assert_not_empty(conn2.execute("SELECT * FROM zines WHERE title = 'foo'"))
        end
      end
      raise ActiveRecord::Rollback
    end
  end
end

Now, it should be rare indeed that a developer needs a real (joinable: false), isolated, nested transaction. But if they ever do, that transaction must be deferred when the adapter is SQLite. Otherwise, the database will immediately throw an error and the code will never execute.

I agree to not exposing mode: :deferred | :immediate directly through the #transaction method. I see your point. My point is only that we cannot simply add a begin_deferred_db_transaction method that acts as a sibling of the begin_isolated_db_transaction method naively, because sometimes you will need to begin both a deferred and an isolated transaction (like in the test case above).

So, the naive code that I originally wrote in response to your review isn't sufficient:

class RealTransaction < Transaction
  def materialize!
    if isolation_level
      connection.begin_isolated_db_transaction(isolation_level)
    elsif !joinable?
      connection.begin_deferred_db_transaction
    else
      connection.begin_db_transaction
    end

    super
  end
  
  # ...
end

This code doesn't allow that test case to pass. We aren't exposing anything about SQLite's transaction modes to the app developer; the test case looks precisely as it does now on main, but it does fail and will always fail unless we can set up a transaction that is both deferred and isolated.

I wanted to share that realization with you as I made it, and share the first thought that come to my mind for how to slightly evolve the codebase to accommodate composing transaction behaviors, instead of simply branching between them. I will finish my update to this branch today and push my suggestion for how this could look. You can then review that code. Sorry for adding confusion by talking about a problem without pushing some code that provides at least one possibility of a solution.


Small addendum: Postgres (I haven't studied MySQL to know either way) has a similar concept as deferred transactions, it just isn't something that is a concern of ActiveRecord:

The DEFERRABLE transaction property has no effect unless the transaction is also SERIALIZABLE and READ ONLY. When all three of these properties are selected for a transaction, the transaction may block when first acquiring its snapshot, after which it is able to run without the normal overhead of a SERIALIZABLE transaction and without any risk of contributing to or being canceled by a serialization failure. This mode is well suited for long-running reports or backups.
https://www.postgresql.org/docs/16/sql-set-transaction.html

Not suggesting that we should in fact used mode: to the #transaction method, but only sharing that this concept isn't completely unique to SQLite. It is only unique to SQLite how essential the difference between the two modes is.

Copy link
Member

Choose a reason for hiding this comment

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

in much the same way they rarely need to concern themselves with whether a transaction should be joinable or not.

Again. joinable isn't a public property, and public API doesn't allow you to request a non-joinable transaction, it's a purely internal concept. So the comparison doesn't hold.

I'm fine with the adapter having some internal logic for when to use immediate or deferred based on wehther it's nested or other criteria, but the user shouldn't have to think about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I think I finally understand what you mean by "public". I was originally interpreting that as "not publicly accessible", but I am seeing that you mean instead "not publicly documented". Gotcha. Again, sorry for my confusion. I think we are on the same page now as to the public interface for #transaction, and I will get my code finished today or this weekend so you can review my attempt to support that interface and the internal mechanics it requires.

Thanks again for the helpful reviews. I appreciate it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@byroot: I have introduced a nodoc method that no-ops for MySQL and PG called mark_transaction_deferred that I call at the start of RealTransaction#materialize!. This allows to compose isolation and deferral in a single transaction, when needed (as in the one test), but doesn't expose any of this externally. All transactions default to immediate and only fixture transactions use deferred via joinable

Copy link
Member

Choose a reason for hiding this comment

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

Alright, I refactored that further but I think it's good to go now.

@fractaledmind fractaledmind force-pushed the ar-sqlite-immediate-transactions branch 3 times, most recently from ed27fcf to 818e17e Compare July 23, 2024 11:17
@byroot byroot force-pushed the ar-sqlite-immediate-transactions branch from 818e17e to de70b95 Compare July 26, 2024 07:01
Transactions run against the SQLite3 adapter default to IMMEDIATE mode to
improve concurrency support and avoid busy exceptions.

Fixture transactions use DEFERRED mode transactions as all `joinable`
transactions become DEFERRED transactions.
@byroot byroot force-pushed the ar-sqlite-immediate-transactions branch from de70b95 to 1e2c904 Compare July 26, 2024 07:03
@byroot byroot merged commit def0397 into rails:main Jul 26, 2024
@fractaledmind fractaledmind deleted the ar-sqlite-immediate-transactions branch August 2, 2024 14:05
rosa added a commit to rails/solid_queue that referenced this pull request Jan 6, 2026
rosa added a commit to rails/solid_queue that referenced this pull request Jan 6, 2026
sincerydev123 added a commit to sincerydev123/queue that referenced this pull request Jan 26, 2026
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.

6 participants