Skip to content

Prepare for Rails 7.2 support (and disable enqueue_after_transaction_commit) - #40

Merged
smudge merged 5 commits into
Betterment:mainfrom
smudge:rails-7-2
Apr 29, 2024
Merged

Prepare for Rails 7.2 support (and disable enqueue_after_transaction_commit)#40
smudge merged 5 commits into
Betterment:mainfrom
smudge:rails-7-2

Conversation

@smudge

@smudge smudge commented Apr 5, 2024

Copy link
Copy Markdown
Member

/domain @effron @samandmoore
/platform @effron @samandmoore

This anticipates the release of a new feature in Rails 7.2 introduced here: rails/rails#51426

In Rails 7.2, ActiveJob will default enqueue_after_transaction_commit to true, and we would like to explicitly set this to false (retaining transactional enqueues, since delayed is DB-backed).

This PR goes a step further and also raises a UnsafeEnqueueError if, during enqueue, it encounters any job type that has overridden enqueue_after_transaction_commit to true. Worth noting that this exception may still be raised after transaction commit (based on the way the lifecycle hooks are handled), so it is intended first and foremost as a guard during development & testing.

As part of this change I also:

  • Added a new Appraisal for the latest Rails main build on GitHub.
  • Re-generated the rubocop todo file. There are changes we can pull out & autocorrect, but we can do them in separate PRs.

@ghiculescu

Copy link
Copy Markdown

Other than the fact it would open a second transaction, is there any downside to setting it to true that you're trying to protect against?

@effron effron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

domainLGTM

@smudge

smudge commented Apr 8, 2024

Copy link
Copy Markdown
Member Author

@ghiculescu It's mainly that one downside, which to be fair is a big one 😄. This gem is a foundational building block for ensuring that persistence side effects always run when they should (and don't run when they shouldn't) and, more broadly, for maintaining eventual consistency across systems. It is also consumed by other libraries (like journaled) that rely on these guarantees.

So by default we want to maintain all existing message delivery guarantees, and avoid the possibility that some jobs may backslide on Rails 7.2 and lose the DB's ACID compliance.

@smudge
smudge requested a review from samandmoore April 8, 2024 21:11
@jmileham

jmileham commented Apr 8, 2024

Copy link
Copy Markdown
Member

Also, if you've got 40 minutes I'd hype @smudge's talk, (also linked from the README), if you want to know more about the core guarantee that delayed offers.

@ghiculescu

Copy link
Copy Markdown

Thanks @smudge. That makes sense.

I assume if you use a separate database for your jobs, then most of the guarantees you describe go out the window. The readme says this is possible but not explicitly supported, at least that's my interpretation.

(We currently use delayed job but are considering switching to delayed.)

@smudge

smudge commented Apr 9, 2024

Copy link
Copy Markdown
Member Author

The readme says this is possible but not explicitly supported.

Since Delayed::Job is just an ActiveRecord class, it can be configured in an initializer to connect to a different database.

I assume if you use a separate database for your jobs, then most of the guarantees you describe go out the window.

Correct. There are situations where it may make sense (perhaps your application already connects to two databases), but I would go as far as to say that this is the wrong queuing library if the intention is always to directly enqueue into a secondary database.

That said, when pointed at the app's primary database, delayed (and, fwiw, delayed_job_active_record) can be used to support a co-transactional "outbox" to ensure that jobs make it into an external job queue, and I'd consider that a good use of this library's core guarantees.

@ghiculescu

Copy link
Copy Markdown

The readme says this is possible but not explicitly supported.

Since Delayed::Job is just an ActiveRecord class, it can be configured in an initializer to connect to a different database.

FYI, with delayed_job it wasn't that simple, because you can only configure abstract classes to connect to different databases. We ended up with this. I imagine it'll be similar with this gem.

@smudge
smudge requested review from effron, pat-whitrock and rzane April 24, 2024 23:31

@rzane rzane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

platform lgtm

@smudge
smudge merged commit 9a328c6 into Betterment:main Apr 29, 2024
@smudge
smudge deleted the rails-7-2 branch April 29, 2024 17:15
smudge pushed a commit that referenced this pull request Aug 13, 2024
…or Rails 7.2 support (#43)

Hi Betterment friends!

As addressed in @smudge's PR
#40, Rails 7.2 introduces the
concept of `enqueue_after_transaction_commit`, which should not be used
in conjunction with Delayed. The implementation, and the associated
tests, make the assumption that the values used by ActiveJob are `true`
and `false`. The implementation in
ActiveJob::EnqueueAfterTransactionCommit
(https://github.com/rails/rails/blob/ac1d7681d05906b2b050eced76c6a2165f420821/activejob/lib/active_job/enqueue_after_transaction_commit.rb#L7-L14)
uses the values of `:always` and `:never`. The issues arises when
hitting the conditional check
(https://github.com/Betterment/delayed/compare/main...warmerzega:delayed:main?expand=1#diff-325ce2458ac2c3e05143813a4899c9f996ae51f54cdb8fa5f79f50efab872f9aR20)
where `!!:never == true` and causes the condition to be true (and
raising the exception) when `job.class.enqueue_after_transaction_commit
== :never`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants