-
Notifications
You must be signed in to change notification settings - Fork 190
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
Support bulk enqueuing of jobs which differ only in args/kwargs #340
Conversation
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 is great work.
How about sharing your manual testing runtime results here?
How about making the method take an option for whether to disable the notify while enqueueing the jobs? |
Interesting idea. How would we do that without also impacting jobs enqueued using the normal |
I was thinking something like this - disable and re-enable the trigger in the call to |
Note that disabling/enabling triggers is a schema change, meaning it will require a I’ve found adding a trigger condition which checks a custom setting works well to temporarily stop row triggers on bulk operations. Something like this: CREATE TRIGGER que_job_notify
AFTER INSERT ON public.que_jobs FOR EACH ROW
WHEN (NOT coalesce(current_setting('que.skip_notify', true)::boolean, false))
EXECUTE FUNCTION public.que_job_notify(); Then use
|
@jasoncodes Good to know! Thanks for the tip <3 Edit: The docs for disabling a trigger confirm this |
Rebased (via cherry-picking just the latest commit) on master now that the Ruby 3 PR has been merged |
@jasoncodes: @AndrewColbeck and I have had to adjust the query you provided, as we found it unfortunately errors after a transaction which sets the variable. It seems that
Here's a working alternative that avoids the need to cast:
|
Good catch. I can confirm this empty string behaviour on at least PG 14.x. Here’s an alternate solution to consider: |
Thanks for that idea. We tried it out, but it doesn't work - I think because casting null to boolean doesn't:
Sorry, I thought casting null to boolean was working before 😅 We've found these two options to be working: WHEN (nullif(current_setting('que.skip_notify', true), '') is null)
WHEN (NOT coalesce(current_setting('que.skip_notify', true), '') = 'true') We think we prefer the second form, given it's checking the value is set to something truthy rather than just set to something. |
…mance Implementation is based on [a tip from @jasoncodes](que-rb#340 (comment))! Co-Authored-By: Andrew Colbeck <andrewcolbeck@gmail.com>
…ormance Implementation is based on [a tip from @jasoncodes](que-rb#340 (comment))! Co-Authored-By: Andrew Colbeck <andrewcolbeck@gmail.com>
I've just realised there's a second trigger,
I think I'd still like to add an option in bulk enqueueing for whether to disable the notify (disabling it by default, I reckon). The MyJob.bulk_enqueue(
[
{ args: ['job1_arg1'], kwargs: { job1_kwarg1: 'x' } },
{ args: ['job2_arg1'], kwargs: { job2_kwarg1: 'x' } },
],
) It could be good to improve that here or subsequently. I was wondering about something like: Que::Job.with_bulk_enqueue do
MyJob.enqueue('job1_arg1', job1_kwarg1: 'x')
MyJob.enqueue('job2_arg1', job2_kwarg1: 'x')
end |
Ah, true. The trigger condition must return true and I missed the
Agreed entirely. Comparing to WHEN (nullif(current_setting('que.skip_notify', true), '')::boolean IS DISTINCT FROM true) I read this one as “run the trigger when when skip_notify is not true”. |
I think disabled by default makes sense.
Agreed. Not being able to use the normal Job
Yes, please! This would be is a much nicer interface and would allow users to adopt bulk enqueuing without a lot of changes to application code. One could divert the The main API difference I can think of is |
I've reworked the interface to use basically what I proposed, but still called To keep track of everything that's left, I've just added a todo list to the first comment. |
Yeah, that's what I'd been thinking too 😉
Ah, I hadn't thought of that! Cheers; done. I'm not really familiar with the ActiveJob part of Que - do you reckon we should add a test for that here? |
Yeah, but the settings are strings, so we can't really avoid string stuff afaik, having to use either string parsing or string comparison, of which I presume the performance is similar.
Interesting alternative. I think what we went with is slightly more readable, personally, so I'll stick with that. Thanks though |
To avoid a ShareRowExclusiveLock, which would prevent row modifications while the transaction is open (que-rb#340 (comment))
…ormance Implementation is based on [a tip from @jasoncodes](que-rb#340 (comment))! Co-Authored-By: Andrew Colbeck <andrewcolbeck@gmail.com>
To avoid a ShareRowExclusiveLock, which would prevent row modifications while the transaction is open (que-rb#340 (comment))
Rebased to avoid changelog conflict |
Co-Authored-By: Andrew Colbeck <andrewcolbeck@gmail.com>
…s used It was consistently failing when run with: ```bash DATABASE_URL=postgres://que:que@localhost:5697/que-test TESTOPTS="--seed=50554 -v" USE_RAILS=true be rake spec ``` Co-Authored-By: Andrew Colbeck <andrewcolbeck@gmail.com>
…ormance Implementation is based on [a tip from @jasoncodes](que-rb#340 (comment))! Co-Authored-By: Andrew Colbeck <andrewcolbeck@gmail.com>
… 'PROCEDURE' for compatibility with older PostgreSQL
Allows fast-failing a test run using: ```bash TESTOPTS=--fail-fast bundle exec rake spec ```
…calls to .enqueue
It disables notify by default for performance
To avoid a ShareRowExclusiveLock, which would prevent row modifications while the transaction is open (que-rb#340 (comment))
This does not seem to be used
…queue rather than .bulk_enqueue I added a matcher, `assert_raises_with_message` to make the expectation cleaner. I reckon this should have been part of minitest, like it is in RSpec
… args, and do so only at the end of the .bulk_enqueue block
…ecessary serialisation, making it ~19% faster
Co-Authored-By: Jerome Paul <jerome.paul@greensync.com.au>
Co-Authored-By: Jerome Paul <jerome.paul@greensync.com.au>
Co-authored-by: Brendan Weibrecht <brendan@weibrecht.net.au>
[ActiveJob always adds job options to the Que.enqueue call](https://github.com/rails/rails/blob/main/activejob/lib/active_job/queue_adapters/que_adapter.rb#L25) but that is not supported. Furthermore ActiveJob does not have a dedicated bulk enqueue interface. Co-authored-by: Brendan Weibrecht <brendan@weibrecht.net.au>
Using Que to manually enqueue an ActiveJob job is not just as simple being able to specify your job class in the `job_class` job option in `Que.enqueue` - there's a lot more to it, e.g.: ``` [2] pry(#<running jobs via ActiveJob>)> TestJobClass.perform_later(1, 2) => #<TestJobClass:0x00007fb081f4bcb0 @arguments=[1, 2], @exception_executions={}, @Executions=0, @job_id="227ffa28-57b2-4517-99ef-a53a4136c01a", @priority=nil, @provider_job_id=136557356, @queue_name="default", @Timezone=nil> [3] pry(#<running jobs via ActiveJob>)> jobs_dataset.all => [{:priority=>100, :run_at=>2022-08-25 06:25:15.406266 +0000, :id=>136557356, :job_class=>"ActiveJob::QueueAdapters::QueAdapter::JobWrapper", :error_count=>0, :last_error_message=>nil, :queue=>"default", :last_error_backtrace=>nil, :finished_at=>nil, :expired_at=>nil, :args=> [{:job_id=>"227ffa28-57b2-4517-99ef-a53a4136c01a", :locale=>"en", :priority=>nil, :timezone=>nil, :arguments=>[1, 2], :job_class=>"TestJobClass", :executions=>0, :queue_name=>"default", :enqueued_at=>"2022-08-25T06:25:15Z", :provider_job_id=>nil, :exception_executions=>{}}], :data=>{}, :job_schema_version=>2, :kwargs=>{}}] ``` Co-Authored-By: Jerome Paul <jerome.paul@greensync.com.au>
Co-Authored-By: Jerome Paul <jerome.paul@greensync.com.au>
🚀 |
Resolves: #333
Todo:
.bulk_enqueue
.enqueue
que_job_notify
trigger in.bulk_enqueue
for performanceque_state_notify
trigger in.bulk_enqueue
for performance /completely remove it(removal deferred)que_state_notify
trigger in the changelog.bulk_enqueue
.bulk_enqueue
in synchronous modeque.skip_notify
Add a test for enqueueing a job with ActiveJob within a.bulk_enqueue
blockConfirm that the alternate instructions in the ActiveJob error message actually work for ActiveJob jobs- it didn't: removedTo PR and release subsequently:
.bulk_enqueue
block.bulk_enqueue
block