From 389373b08efcc350ba2711309d7a5533e7045dc1 Mon Sep 17 00:00:00 2001 From: Aqeel Nazeer Date: Wed, 12 Aug 2026 15:07:27 +0530 Subject: [PATCH] Exclude archived stacks from the scheduler loops Stack.schedule_continuous_delivery and Stack.refresh_deployed_revisions run every minute from the host application's scheduler, but neither scope excluded archived stacks. Archiving locks a stack, so the jobs were guaranteed no-ops: ContinuousDeliveryJob bails on the lock and FetchDeployedRevisionJob refreshes state nobody can see. At Shopify's scale that was ~1300 archived stacks enqueuing ~2000 no-op ContinuousDeliveryJobs per minute, plus full git clones from FetchDeployedRevisionJob for archived stacks with fetch steps, all on the same worker pool as deploys. Unarchiving already triggers a GithubSyncJob via sync_github_if_necessary, so a revived stack re-enters both loops naturally. --- app/models/shipit/stack.rb | 10 ++++++++-- test/models/shipit/stack_test.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/models/shipit/stack.rb b/app/models/shipit/stack.rb index b1113faab..9b694cd42 100644 --- a/app/models/shipit/stack.rb +++ b/app/models/shipit/stack.rb @@ -117,11 +117,17 @@ def sync_github_if_necessary ) def self.refresh_deployed_revisions - find_each.select(&:supports_fetch_deployed_revision?).each(&:async_refresh_deployed_revision) + # where.not avoids deserializing every stack's cached_deploy_spec each + # minute: a stack without a cached spec cannot have fetch steps. + not_archived + .where.not(cached_deploy_spec: nil) + .find_each + .select(&:supports_fetch_deployed_revision?) + .each(&:async_refresh_deployed_revision) end def self.schedule_continuous_delivery - where(continuous_deployment: true).find_each do |stack| + not_archived.where(continuous_deployment: true).find_each do |stack| ContinuousDeliveryJob.perform_later(stack) end end diff --git a/test/models/shipit/stack_test.rb b/test/models/shipit/stack_test.rb index 74038d35f..9358f145d 100644 --- a/test/models/shipit/stack_test.rb +++ b/test/models/shipit/stack_test.rb @@ -11,6 +11,38 @@ def setup GithubHook.any_instance.stubs(:teardown!) end + test ".schedule_continuous_delivery skips archived stacks" do + archived = shipit_stacks(:archived_6hours_ago) + archived.update!(continuous_deployment: true) + @stack.update!(continuous_deployment: true) + + Stack.schedule_continuous_delivery + + enqueued_args = enqueued_jobs + .select { |job| job[:job] == ContinuousDeliveryJob } + .map { |job| job[:args].to_s } + assert enqueued_args.any? { |args| args.include?("Stack/#{@stack.id}") }, + "expected a ContinuousDeliveryJob for the active stack" + refute enqueued_args.any? { |args| args.include?("Stack/#{archived.id}") }, + "archived stacks must not trigger continuous delivery" + end + + test ".refresh_deployed_revisions skips archived stacks" do + archived = shipit_stacks(:archived_6hours_ago) + archived.update!(cached_deploy_spec: DeploySpec.new('fetch' => ['echo 1'])) + @stack.update!(cached_deploy_spec: DeploySpec.new('fetch' => ['echo 1'])) + + Stack.refresh_deployed_revisions + + enqueued_args = enqueued_jobs + .select { |job| job[:job] == FetchDeployedRevisionJob } + .map { |job| job[:args].to_s } + assert enqueued_args.any? { |args| args.include?("Stack/#{@stack.id}") }, + "expected a FetchDeployedRevisionJob for the active stack with fetch steps" + refute enqueued_args.any? { |args| args.include?("Stack/#{archived.id}") }, + "archived stacks must not refresh deployed revisions" + end + test "branch defaults to default branch name" do @stack.branch = "" Shipit.github.api.expects(:repo).with("shopify/shipit-engine").returns(