From 21dc34ae615a203d1b15b6f4c0547a70ca7352ce Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:38:39 +0200 Subject: [PATCH] fix(scheduler): spread CDN update checks over a random delay All instances share the same default update cron, so they request the CDN at the same second and Cloudflare rejects the burst (error 1200). Queue the CDN fetching jobs with a random 0-600s delay to spread the load. Co-Authored-By: Claude Opus 5.5 --- app/Console/Kernel.php | 21 ++++++++++++++++----- tests/Feature/ScheduleOnOneServerTest.php | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a88ba64a81..7f3e5fb8bf 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -25,6 +25,8 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { + private const CDN_JITTER_SECONDS = 600; + private Schedule $scheduleInstance; private InstanceSettings $settings; @@ -91,8 +93,8 @@ class Kernel extends ConsoleKernel $this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes(); $this->scheduleInstance->command('cleanup:unreachable-servers')->daily()->onOneServer(); - $this->scheduleInstance->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); - $this->scheduleInstance->job(new PullChangelog)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); + $this->scheduleInstance->job($this->withCdnJitter(new PullTemplatesFromCDN))->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); + $this->scheduleInstance->job($this->withCdnJitter(new PullChangelog))->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); $this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $this->scheduleUpdates(); @@ -117,7 +119,7 @@ class Kernel extends ConsoleKernel private function pullImages(): void { - $this->scheduleInstance->job(new CheckHelperImageJob) + $this->scheduleInstance->job($this->withCdnJitter(new CheckHelperImageJob)) ->cron($this->updateCheckFrequency) ->timezone($this->instanceTimezone) ->onOneServer(); @@ -162,20 +164,29 @@ class Kernel extends ConsoleKernel private function scheduleUpdates(): void { - $this->scheduleInstance->job(new CheckForUpdatesJob) + $this->scheduleInstance->job($this->withCdnJitter(new CheckForUpdatesJob)) ->cron($this->updateCheckFrequency) ->timezone($this->instanceTimezone) ->onOneServer(); if ($this->settings->is_auto_update_enabled) { $autoUpdateFrequency = $this->settings->auto_update_frequency; - $this->scheduleInstance->job(new UpdateCoolifyJob) + $this->scheduleInstance->job($this->withCdnJitter(new UpdateCoolifyJob)) ->cron($autoUpdateFrequency) ->timezone($this->instanceTimezone) ->onOneServer(); } } + /** + * All instances share the same default cron, so without a random delay they all request the CDN + * at the same second and Cloudflare rejects the burst (error 1200). + */ + private function withCdnJitter(object $job): object + { + return $job->delay(random_int(0, self::CDN_JITTER_SECONDS)); + } + protected function commands(): void { $this->load(__DIR__.'/Commands'); diff --git a/tests/Feature/ScheduleOnOneServerTest.php b/tests/Feature/ScheduleOnOneServerTest.php index 2e4cdcf34e..19edd2af41 100644 --- a/tests/Feature/ScheduleOnOneServerTest.php +++ b/tests/Feature/ScheduleOnOneServerTest.php @@ -1,10 +1,16 @@ whereKey(0)->update(['is_auto_update_enabled' => true]); + Queue::fake(); + + $jobs = [CheckHelperImageJob::class, CheckForUpdatesJob::class, PullTemplatesFromCDN::class, PullChangelog::class, UpdateCoolifyJob::class]; + $schedule = app(Schedule::class); + + foreach ($jobs as $job) { + $event = collect($schedule->events())->first(fn ($e) => $e->description === $job); + expect($event)->not->toBeNull("[{$job}] is not scheduled"); + $event->run(app()); + + Queue::assertPushed($job, fn ($pushed) => is_int($pushed->delay) && $pushed->delay >= 0 && $pushed->delay <= 600); + } +}); + it('does not schedule Stripe subscription reconciliation automatically', function () { $schedule = app(Schedule::class);