tanjilahmed87@gmail.com

Backend6 min read

Scaling Laravel Queue Workers Without Losing Your Mind

Adding more workers is the easy part. Keeping them from stampeding your database is where most Laravel queue setups actually fail.

Tanjil Ahmed

Lead Software Engineer · Notionhive

The first time a Laravel queue backs up, the instinct is to add workers. That fixes throughput and usually creates a second problem: dozens of workers hammering the same database rows, connection pools exhausting, and jobs failing in ways that look like the original bug got worse.

Separate queues by contention, not by feature

Instead of one default queue for everything, I split queues by what they contend for: a queue for jobs touching the same aggregate rows, a queue for outbound HTTP calls, a queue for CPU-heavy exports. Each gets its own worker count tuned to what it's actually bottlenecked on.

  • Rate-limit worker concurrency per queue with Laravel's `WithoutOverlapping` and `RateLimited` middleware.
  • Use `retry_after` values that actually match job runtime, or workers will double-process long jobs.
  • Horizon's metrics tell you which queue is actually starved — don't guess, look.
  • Idempotency keys on jobs save you when overlapping execution inevitably happens.
More workers is a multiplier. If the underlying job isn't safe to run twice, you're just multiplying the bug.