From 460bb67d632939dad3d833f6329c4cf99e209146 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:29:04 +0200 Subject: [PATCH] fix(server): avoid a burst of patch and Sentinel checks after the upgrade The per-server schedules reused the cache keys of the old schedule, so every server ran these checks at once on the first run after the upgrade. New keys start fresh, so each server runs at its own slot. --- app/Console/Commands/ScheduledJobDiagnostics.php | 4 ++-- app/Jobs/ServerManagerJob.php | 8 +++++--- tests/Feature/ScheduledJobDiagnosticsTest.php | 4 ++-- tests/Feature/Server/ServerManagerJobStaggerTest.php | 11 +++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/Console/Commands/ScheduledJobDiagnostics.php b/app/Console/Commands/ScheduledJobDiagnostics.php index a99184cc96..d8ebcc7806 100644 --- a/app/Console/Commands/ScheduledJobDiagnostics.php +++ b/app/Console/Commands/ScheduledJobDiagnostics.php @@ -211,8 +211,8 @@ class ScheduledJobDiagnostics extends Command } $dedupKeys = [ - "server-patch-check:{$server->id}" => ServerManagerJob::patchCheckCron($server), - "sentinel-version-check:{$server->id}" => ServerManagerJob::sentinelVersionCheckCron($server), + "server-patch-check-v2:{$server->id}" => ServerManagerJob::patchCheckCron($server), + "sentinel-version-check-v2:{$server->id}" => ServerManagerJob::sentinelVersionCheckCron($server), "server-check:{$server->id}" => isCloud() ? '*/5 * * * *' : '* * * * *', "server-storage-check:{$server->id}" => data_get($server->settings, 'server_disk_usage_check_frequency', '0 23 * * *'), ]; diff --git a/app/Jobs/ServerManagerJob.php b/app/Jobs/ServerManagerJob.php index 95cb20fb1c..a8f2a50a31 100644 --- a/app/Jobs/ServerManagerJob.php +++ b/app/Jobs/ServerManagerJob.php @@ -184,7 +184,7 @@ class ServerManagerJob implements ShouldBeEncrypted, ShouldQueue // Unreachable servers are skipped: the SSH check would only fail. The connection check recovers them. if ($server->isSentinelEnabled() && ! Cache::has(Server::sentinelReportedVersionCacheKey($server->id)) - && shouldRunCronNow(self::sentinelVersionCheckCron($server), $serverTimezone, "sentinel-version-check:{$server->id}", $this->executionTime) + && shouldRunCronNow(self::sentinelVersionCheckCron($server), $serverTimezone, "sentinel-version-check-v2:{$server->id}", $this->executionTime) && $server->isFunctional() ) { CheckAndStartSentinelJob::dispatch($server); @@ -204,8 +204,10 @@ class ServerManagerJob implements ShouldBeEncrypted, ShouldQueue } } - // Dispatch ServerPatchCheckJob if due (weekly, staggered per server on Sunday) - $shouldRunPatchCheck = shouldRunCronNow(self::patchCheckCron($server), $serverTimezone, "server-patch-check:{$server->id}", $this->executionTime); + // Dispatch ServerPatchCheckJob if due (weekly, staggered per server on Sunday). + // The "-v2" keys start fresh: v4.3.23 stored its old run times under the unversioned keys, which + // would make every server run these checks at once on the first run after the upgrade. + $shouldRunPatchCheck = shouldRunCronNow(self::patchCheckCron($server), $serverTimezone, "server-patch-check-v2:{$server->id}", $this->executionTime); if ($shouldRunPatchCheck) { ServerPatchCheckJob::dispatch($server); diff --git a/tests/Feature/ScheduledJobDiagnosticsTest.php b/tests/Feature/ScheduledJobDiagnosticsTest.php index 69293c7208..34f49eeb68 100644 --- a/tests/Feature/ScheduledJobDiagnosticsTest.php +++ b/tests/Feature/ScheduledJobDiagnosticsTest.php @@ -27,8 +27,8 @@ it('does not consume the dedup keys of the server manager checks', function () { $this->artisan('scheduled:diagnostics', ['--type' => 'server-jobs'])->assertSuccessful(); expect(Cache::get("server-check:{$server->id}"))->toBeNull() - ->and(Cache::get("server-patch-check:{$server->id}"))->toBeNull() - ->and(shouldRunCronNow('0 0 * * 0', 'UTC', "server-patch-check:{$server->id}"))->toBeTrue(); + ->and(Cache::get("server-patch-check-v2:{$server->id}"))->toBeNull() + ->and(shouldRunCronNow('0 0 * * 0', 'UTC', "server-patch-check-v2:{$server->id}"))->toBeTrue(); }); it('shows the occurrence state that the scheduled job manager records', function () { diff --git a/tests/Feature/Server/ServerManagerJobStaggerTest.php b/tests/Feature/Server/ServerManagerJobStaggerTest.php index 9911c2e8fc..135caf0e84 100644 --- a/tests/Feature/Server/ServerManagerJobStaggerTest.php +++ b/tests/Feature/Server/ServerManagerJobStaggerTest.php @@ -9,6 +9,7 @@ use App\Models\Server; use App\Models\Team; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Queue; uses(RefreshDatabase::class); @@ -113,3 +114,13 @@ it('dispatches the patch check once per week at the server slot on Sunday', func runStaggerTestManagerAt($slot->copy()->addWeek()); Queue::assertPushed(ServerPatchCheckJob::class, 2); }); + +it('does not run every patch check at once after an upgrade from the old weekly schedule', function () { + $server = createStaggerTestServer($this->team); + // v4.3.23 ran the patch check for all servers on Sunday at 00:00 under this key. + Cache::put("server-patch-check:{$server->id}", Carbon::parse('2025-01-12 00:00:00', 'UTC')->toIso8601String(), 2592000); + + runStaggerTestManagerAt(Carbon::parse('2025-01-15 12:00:00', 'UTC')); + + Queue::assertNotPushed(ServerPatchCheckJob::class); +});