From cfad0ed7e98ff3b78d7d422ae807cd15c8422745 Mon Sep 17 00:00:00 2001 From: Mohammad alkhassawneh Date: Thu, 24 Sep 2026 10:58:20 +0300 Subject: [PATCH] Fix superfluous SELECT when scheduling/dispatching a single job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a single job is enqueued, each Execution subclass fires a SELECT on solid_queue_jobs before INSERT. This happens because: 1. assumes_attributes_from_job sets a before_create callback that calls job.send(attribute) for each attribute to copy from the job record. 2. Since create_or_find_by!(job_id: id) only sets the job_id foreign key, the job association isn't loaded — so accessing it triggers a SELECT. The bulk path (enqueue_all) already avoids this by using insert_all, which bypasses callbacks entirely. Fix: pass job: self via the create_or_find_by! block so the association is already loaded in memory when both the before_create callback and the belongs_to validation run. No DB round-trip needed. Applied to all three single-job dispatch paths: schedule (ScheduledExecution), ready (ReadyExecution), and block (BlockedExecution). --- .../solid_queue/job/concurrency_controls.rb | 2 +- app/models/solid_queue/job/executable.rb | 2 +- app/models/solid_queue/job/schedulable.rb | 2 +- test/models/solid_queue/job_test.rb | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/models/solid_queue/job/concurrency_controls.rb b/app/models/solid_queue/job/concurrency_controls.rb index b59c464ad..68d223266 100644 --- a/app/models/solid_queue/job/concurrency_controls.rb +++ b/app/models/solid_queue/job/concurrency_controls.rb @@ -63,7 +63,7 @@ def handle_concurrency_conflict end def block - BlockedExecution.create_or_find_by!(job_id: id) + BlockedExecution.create_or_find_by!(job_id: id) { |e| e.job = self } end def release_next_blocked_job diff --git a/app/models/solid_queue/job/executable.rb b/app/models/solid_queue/job/executable.rb index 75a5d2111..09faf614c 100644 --- a/app/models/solid_queue/job/executable.rb +++ b/app/models/solid_queue/job/executable.rb @@ -106,7 +106,7 @@ def discard private def ready - ReadyExecution.create_or_find_by!(job_id: id) + ReadyExecution.create_or_find_by!(job_id: id) { |e| e.job = self } end def execution diff --git a/app/models/solid_queue/job/schedulable.rb b/app/models/solid_queue/job/schedulable.rb index 04419aa61..75f9b6cba 100644 --- a/app/models/solid_queue/job/schedulable.rb +++ b/app/models/solid_queue/job/schedulable.rb @@ -38,7 +38,7 @@ def scheduled? private def schedule - ScheduledExecution.create_or_find_by!(job_id: id) + ScheduledExecution.create_or_find_by!(job_id: id) { |e| e.job = self } end def execution diff --git a/test/models/solid_queue/job_test.rb b/test/models/solid_queue/job_test.rb index 7e9c1568f..c7eb79231 100644 --- a/test/models/solid_queue/job_test.rb +++ b/test/models/solid_queue/job_test.rb @@ -87,6 +87,21 @@ class DiscardableNonOverlappingGroupedJob2 < NonOverlappingJob assert_equal solid_queue_job.scheduled_at, execution.scheduled_at end + test "scheduling a single future job does not SELECT the job record back" do + active_job = AddToBufferJob.new(1) + job_selects = 0 + + subscriber = ->(*, payload) do + job_selects += 1 if payload[:sql].match?(/\ASELECT.*FROM.*solid_queue_jobs/i) + end + + ActiveSupport::Notifications.subscribed(subscriber, "sql.active_record") do + SolidQueue::Job.enqueue(active_job, scheduled_at: 5.minutes.from_now) + end + + assert_equal 0, job_selects + end + test "enqueue jobs within a connected_to block for the primary DB" do ShardedRecord.connected_to(role: :writing, shard: :shard_two) do ShardedJobResult.create!(value: "in shard two")