-
Notifications
You must be signed in to change notification settings - Fork 9
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
Optimize JobWrapper queueing ActiveJobs #35
Conversation
From one angle it's an optimization, from another angle it's a bugfix (: We don't need to call ActiveJob::Base.deserialize during queueing the job, because we already have an instance of the class we can send methods to for finding out the #max_attempts or #queue_name. Teach JobWrapper how to take a job instance in and serialize it into job_data in that case, whilst still supporting being loaded from the database as a hash and calling deserialize lazily (existing flow). The potential bugfix is someone implementing a second-order effect in #deserialize like using it as a hook to setup ActiveSupport::CurrentAttributes for the job from persisted data. If we invoke #deserialize during enqueuing this can change CurrentAttributes values in the web request that's queueing the job.
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.
Thanks for the contribution @caius! I made a couple of suggestions, and if you have a chance, feel free to bump the gem version & update the changelog! (If not I'll do that in a follow-up.)
Thanks! 🙌
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.
@smudge thanks for the feedback, much nicer patch now. Think I've addressed everything if you could take another look please!
## [0.5.2] - 2023-10-19 | ||
### Fixed | ||
|
||
- Fixed regression for Rails 7.1.1 not executing jobs, due to lazy loading change in Rails. | ||
The `JobWrapper` needs to be loaded before `ActiveJob::Base` is loaded, but this wasn't | ||
happening in the worker processes. Fix by extracting `JobWrapper` into it's own file and | ||
loading when `Delayed` is loaded earlier in the process. | ||
|
||
## [0.5.1] - 2023-10-11 | ||
### Changed | ||
|
||
- Explicitly test Rails 7.1 and Ruby 3.1, Ruby 3.2 to verify compatability. Previous | ||
gems were compatible, but this release is now verified to be compatible. |
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.
@smudge I noticed the changelog wasn't up to date, so backfilled these entries based on the diffs/PR descriptions.
@smudge gentle ping? :) |
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.
domain LGTM && platform LGTM -- will merge and release shortly, thank you! 🙌
This PR introduces a change to avoid calling
ActiveJob::Base#deserialize
during queueing the job whilst maintaining existing functionality.The change is in
JobWrapper
, which previously only took in a hash ofjob_data
and knew how to deserialize it into a job object so we can interrogate it forqueue_name
ormax_attempts
, etc. To avoid the roundtrip through serialization, we changeJobWrapper#initialize
to take in either the job object directly, or a hash as it did previously. When it's a hash (needed when running the job in the worker), it still deserializes it into a job object. When it's a job object just assign it to@job
directly and continue.We discovered this through having a second-order effect in
#deserialize
using it to trigger some logic as the job comes off the queue, but we were then seeing the logic run multiple times for one job execution. Tracked it back to theJobClass.perform_later
calling#deserialize
and being majorly confused why it was deserialising in the web process.This will make enqueueing jobs faster too, because we don't need a roundtrip through
#serialize
,#deserialize
as well as allocating less objects. In practice this is likely a small effect, but we get it for free avoiding the behaviour above.Looking at all the built-in adapters in ActiveJob, as well as good_job and solid_queue, they all avoid calling
job.serialize
during the enqueuing, even those adapters that support invoking methods on the Job class instance like delayed does.From one angle it's an optimization, from another angle it's a bugfix (: