feat(horizon): add fixed-size per-queue worker pools on Coolify Cloud

Cloud production now runs one supervisor per queue (deployments, crons,
high, default, maintenance), so a busy queue cannot starve the others.
Add maintenance_queue() for slow Docker cleanups and a shared
horizon.worker_timeout config value. Self-hosted keeps the s6 supervisor.
This commit is contained in:
Andras Bacsai committed 2026-10-03 21:24:54 +02:00
1 parent c6b8dfe367
commit e144b990c3
8 files changed
+425 -43

No files matched your search

+72 -35
View File
@@ -3,6 +3,67 @@
use App\Models\ScheduledVolumeBackup;
use Illuminate\Support\Str;
/*
| Cloud mode is read from SELF_HOSTED directly instead of isCloud(): isCloud()
| reads the config repository, which is still being built while this file loads.
*/
$isCloud = ! env('SELF_HOSTED', true);
$workerOptions = [
'connection' => 'redis',
'maxTime' => env('HORIZON_MAX_TIME', 0),
'maxJobs' => 400,
'memory' => 128,
'tries' => 1,
'nice' => 0,
'sleep' => 3,
'timeout' => min(
max((int) env('HORIZON_TIMEOUT', 39600), ScheduledVolumeBackup::DEFAULT_TIMEOUT + 600),
85800,
),
];
$selfHostedSupervisor = $workerOptions + [
'balance' => env('HORIZON_BALANCE', 'false'),
'queue' => env('HORIZON_QUEUES', 'high,default'),
'autoScalingStrategy' => 'size',
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
];
/*
| Coolify Cloud: one fixed-size pool per queue, so a busy queue cannot starve
| the others. Process counts apply to each node. Invalid values (not a
| positive integer) fall back to the default.
|
| The `maintenance` pool (see maintenance_queue()) runs slow remote Docker
| cleanups. It is small on purpose: it bounds how many cleanups run at once
| per node, and it comes on top of the other pools, so cleanups never take
| deployment, cron or high workers.
*/
$cloudSupervisors = [];
foreach ([
'deployments' => ['HORIZON_DEPLOYMENTS_PROCESSES', 60],
'crons' => ['HORIZON_CRONS_PROCESSES', 60],
'high' => ['HORIZON_HIGH_PROCESSES', 60],
'default' => ['HORIZON_DEFAULT_PROCESSES', 40],
'maintenance' => ['HORIZON_MAINTENANCE_PROCESSES', 10],
] as $queue => [$processesEnv, $defaultProcesses]) {
$processes = filter_var(env($processesEnv), FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'default' => $defaultProcesses],
]);
$cloudSupervisors[$queue] = $workerOptions + [
'queue' => $queue,
'balance' => false,
'minProcesses' => $processes,
'maxProcesses' => $processes,
];
}
return [
/*
@@ -190,45 +251,21 @@ return [
| in all environments. These supervisors and settings handle all your
| queued jobs and will be provisioned by Horizon during deployment.
|
| "defaults" stays empty: Horizon merges every default supervisor into
| every environment, so each environment defines complete options.
|
*/
'defaults' => [
's6' => [
'connection' => 'redis',
'balance' => env('HORIZON_BALANCE', 'false'),
'queue' => env('HORIZON_QUEUES', 'high,default'),
'maxTime' => env('HORIZON_MAX_TIME', 0),
'maxJobs' => 400,
'memory' => 128,
'tries' => 1,
'nice' => 0,
'sleep' => 3,
'timeout' => min(
max((int) env('HORIZON_TIMEOUT', 39600), ScheduledVolumeBackup::DEFAULT_TIMEOUT + 600),
85800,
),
],
'defaults' => [],
],
/*
| Worker timeout shared by every supervisor. Jobs that must finish before
| the worker kills them (for example database imports) read this value.
*/
'worker_timeout' => $workerOptions['timeout'],
'environments' => [
'production' => [
's6' => [
'autoScalingStrategy' => 'size',
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
],
'local' => [
's6' => [
'autoScalingStrategy' => 'size',
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 4),
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
],
],
'production' => $isCloud ? $cloudSupervisors : ['s6' => $selfHostedSupervisor],
'local' => ['s6' => $selfHostedSupervisor],
],
];