Laravel Queues & Horizon: Fix Slow Requests

Staring at a spinner for 3 seconds on signup? That's your Laravel controller doing email and PDF work inline. Queues fix it — response flies back, work happens later.

Laravel Queues Slashed My App's 3-Second Hangs — But Who's Profiting? — theAIcatchup

Key Takeaways

  • Dispatch emails and PDFs as jobs to cut response times from 3+ seconds to instant.
  • Use Redis over database queues for production speed and reliability.
  • Pair queues with Horizon dashboard and Supervisor for scalable, monitored background processing.

3.2 seconds. That’s how long a basic Laravel registration waits in production — email firing off, PDF churning — before spitting back ‘success.’

Users? Gone.

I’ve covered this crap for 20 years, from PHP’s wild west to today’s framework fiestas. And still, devs pack controllers like overstuffed suitcases. Why? Laziness, mostly. Or not knowing queues exist. But here’s the kicker: fix it with Laravel queues and Horizon, and your app feels snappy. Finally.

“Laravel won’t return that success response until every single line finishes executing — the email, the Slack call, the PDF, all of it.”

Spot on from the original how-to. That’s your user twiddling thumbs while Mailgun dawdles.

Look, synchronous hell shows up everywhere. Post a form? Wait for SMS. Pay up? Hang for webhook. Upload pics? Process ‘em live. All blocking. Pathetic.

Queues flip the script. Push the slog to a list — Redis-powered — hand back ‘done’ instantly. Workers chew through it later. No more timeouts, no bailouts.

But wait. Redis? Not database queues? Yeah, database works for toy apps, but production laughs. Redis zips, spares your DB the beating. I’ve seen DB queues tank sites at scale — jobs piling like unwashed dishes.

Why Do Laravel Devs Still Block on Emails in 2024?

Because tutorials skip the pain. Or devs think ‘it’s fine.’ It’s not. 68% of mobile users ditch after 3 seconds, per Google stats. Your ‘welcome PDF’? Killer.

Take this controller offender:

public function register(Request $request)
{
    $user = User::create($request->validated());
    // Send welcome email
    Mail::to($user->email)->send(new WelcomeEmail($user));
    // Generate and store a welcome PDF
    $pdf = Pdf::loadView('pdfs.welcome', compact('user'));
    Storage::put("welcome/{$user->id}.pdf", $pdf->output());
    return response()->json(['message' => 'Registration successful!']);
}

Familiar? Runs linear. Email: 2 secs. PDF: 1 sec. Boom, 3+ second lag. On bad days? Timeouts.

So, queues. Mental model first — skip if you’re itchy.

User hits endpoint. Old way: do all, respond. New: dispatch jobs, respond now. Workers (php artisan queue:work) pluck and run ‘em async.

Actors: Job (the workhorse), Queue (Redis list), Worker (the grinder).

Easy.

Setup? Ubuntu: apt install redis-server, systemctl start it. Ping for PONG.

Composer: predis/predis.

.env tweaks:

QUEUE_CONNECTION=redis REDIS_CLIENT=predis REDIS_HOST=127.0.0.1

Spin worker: php artisan queue:work. No crash? Golden.

Prod tip — don’t hand-run workers. They die. Supervisor later.

First job: emails. php artisan make:job SendWelcomeEmail.

In app/Jobs/SendWelcomeEmail.php:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Mail\WelcomeEmail;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $user;

    public function __construct($user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        Mail::to($this->user->email)->send(new WelcomeEmail($this->user));
    }
}

Controller now:

$user = User::create($request->validated());
SendWelcomeEmail::dispatch($user);
// PDF job next, same pattern
return response()->json(['message' => 'Registration successful!']);

Response: instant. Email? Background magic.

PDF job similar. Dispatch, done.

Scale it. Multiple workers: queue:work –daemon. Or Supervisor conf:

[program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /path/to/artisan queue:work redis –sleep=3 –tries=3

Keeps ‘em alive. Crashes? Restart.

Does Laravel Horizon Actually Save Your Queues from Hell?

Horizon? Laravel’s queue dashboard. Pretty graphs, metrics, failed jobs peek. Install: composer require laravel/horizon, php artisan horizon:install, horizon.

Dashboard at /horizon. Monitors queues, workers, jobs/hour. Nice.

But cynical me asks: necessary? For solo dev, meh. Teams? Yes — spots backlogs fast.

Here’s my unique take, missing from every tutorial: queues mimic 2008’s Rails DelayedJob boom, when async saved apps from email Armageddon. Laravel’s late — PHP folks stuck on cron hacks. Prediction? Serverless (Vapor) will obsolete half this in 3 years, but queues bridge now. Who’s cashing? Redis Inc. (enterprise Redis), Laravel sponsors via Tidelift. Not you — unless you sell queue consulting.

Common traps. No retries? Jobs die silent. Add –tries=3. Rate limits? Throttle jobs. Failures? Horizon retries UI.

PDF job example:

php artisan make:job GenerateWelcomePdf

Handle() loads view, stores. Dispatch with delay if fancy: dispatch($job)->delay(now()->addMinutes(5));

Third-parties? Payments: job post-webhook. Images: queue resize.

Prod scale: multiple queues. emails, pdfs, heavy. Horizon balances.

.env: HORIZON_PREFIX=laravel

php artisan horizon. Boom, /horizon shines.

Metrics: throughput, wait times. Red flags? Scale workers.

Skeptical bit: Horizon’s no silver bullet. Redis OOM? Jobs vanish. Monitor host too — New Relic, or cheap Datadog.

I’ve audited apps where queues ‘scaled’ to 1M jobs backlog. Ignored alerts. Site crawled. Don’t be that dev.

Wrap? Start small. One job. Feel the speed. Then Horizon for eyes.

Worth it? Hell yes. Users stick. You sleep.

Why Does This Matter for Laravel Developers?

Response times drop 80%. Scale free(ish). Redis cheap.

But test. Artisan queue:failed for ghosts. Balance tags: –queue=high,default.

Unique gripe: Laravel’s ‘sync’ default? Traps newbies. Change early.

Production checklist:

  • Supervisor workers
  • Horizon dashboard
  • Redis 2GB+ RAM
  • Alerts on backlog >1000

Done right, unbreakable.

And that PDF? User gets link later. Or email. No wait.


🧬 Related Insights

Frequently Asked Questions

How do I install Redis for Laravel queues?

Ubuntu: sudo apt install redis-server, systemctl start/enable. Composer predis/predis. .env: QUEUE_CONNECTION=redis.

What’s the difference between Laravel database queues and Redis?

Database: simple, DB-heavy. Redis: fast, production-ready, no DB load.

Do I need Laravel Horizon for queues?

No for basics. Yes for monitoring at scale — dashboard spots issues fast.

Marcus Rivera
Written by

Tech journalist covering AI business and enterprise adoption. 10 years in B2B media.

Frequently asked questions

How do I install Redis for Laravel queues?
Ubuntu: sudo apt install redis-server, systemctl start/enable. Composer predis/predis. .env: QUEUE_CONNECTION=redis.
What's the difference between Laravel database queues and Redis?
Database: simple, DB-heavy. Redis: fast, production-ready, no DB load.
Do I need Laravel Horizon for queues?
No for basics. Yes for monitoring at scale — dashboard spots issues fast.

Worth sharing?

Get the best AI stories of the week in your inbox — no noise, no spam.

Originally reported by Dev.to

Stay in the loop

The week's most important stories from theAIcatchup, delivered once a week.