Horizon, Reverb, and Octane in queue-heavy Laravel systems

Separate queue capacity, WebSocket delivery, and persistent HTTP workers before choosing a Laravel performance tool.
Trace one export from request to notification
A user submits an export, a job builds the file, and the browser receives a completion update. This path contains three different delays: accepting the request, waiting and executing in the queue, and delivering the update. Measure each before selecting a tool. Faster HTTP bootstrapping cannot drain an overloaded export queue.
Horizon manages and observes Redis-backed Laravel queues; it is not an SQS dashboard. Reverb handles WebSocket communication. Octane keeps the application in memory to serve HTTP requests through persistent workers. An application may use all three, but each should have an independently demonstrated purpose.
Reserve capacity for short jobs
For Laravel 12 with Horizon installed and a compatible Redis setup, separate interactive notifications from long exports. The following fragment replaces only the local environment entry in the published Horizon configuration; retain the rest of that file. Queue names must match job dispatch configuration. These process counts are small trial values, not production sizing advice.
<?php
return [
'interactive' => [
'connection' => 'redis',
'queue' => ['notifications'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 2,
'tries' => 3,
'timeout' => 30,
'backoff' => 10,
],
'exports' => [
'connection' => 'redis',
'queue' => ['exports'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 2,
'tries' => 3,
'timeout' => 90,
'backoff' => 30,
],
];
Timeouts and retries are a correctness problem
In this example, the export supervisor timeout is 90 seconds and the Redis retry_after is 120. The gap allows a timed-out worker to stop before the job becomes available again. Also configure timeouts on outbound HTTP calls; a process timeout is a poor substitute for bounding a dependency request.
Three attempts do not guarantee completion. A permanently invalid input should become an actionable failure, while a temporary dependency outage may merit retry with backoff. Use a stable operation identifier and enforce duplicate protection where the durable effect occurs. A worker can stop after creating a file but before acknowledging success. Verify that retrying reuses or safely replaces the intended result.
Observe queue wait and execution time independently while filling the export queue with synthetic jobs. Confirm notifications still progress. Raising worker counts may instead saturate database connections or an external API; queue isolation gives you separate controls, not unlimited capacity.
Treat WebSockets as an update channel
Reverb can tell a connected browser that the export finished, but the export status should remain in durable application storage. After reconnecting, the browser should fetch the current authorized status rather than assume it received every event. Test a disconnect during completion.
Restrict allowed origins and authorize private channels for the appropriate account. These checks have different purposes: an allowed origin is not proof that a user owns an export. Keep sensitive download access behind the normal authorization path and handle a missing or expired result explicitly.
Adopt Octane only after profiling HTTP work
Octane changes object lifetimes. A singleton that captures the current request can retain stale information across later requests. Audit mutable static state, user-specific services, and memory growth before rollout. Exercise alternating users on a persistent worker, rather than only isolated requests in a test suite.
Compare the same endpoints and concurrency before and after the runtime change. If database time dominates, focus there first. Rehearse process reloads for Horizon, Reverb, and Octane separately: publishing new files is not sufficient evidence that every long-lived process uses them.

