DEV Community

Cover image for Stop One User From Hogging Your Laravel Queue
Yan Gus
Yan Gus

Posted on

Stop One User From Hogging Your Laravel Queue

The Problem I Ran Into

I was building an AI image generation service. Users submit prompts, jobs go into
a Laravel queue, workers process them. Simple enough - until one power user
submitted 50 jobs at once.

Result? Everyone else waited. The queue was FIFO, and that one user owned it.

Queue: [A1][A2][A3]...[A50][B1][B2][C1]
User B and C wait for ALL 50 of User A's jobs to finish.
Enter fullscreen mode Exit fullscreen mode

I needed fair distribution - not "first come first served", but "everyone gets
a turn". That's what laravel-balanced-queue does.


How It Works

The package splits the queue into partitions (one per user/tenant) and rotates
between them:

Partition A: [A1][A2][A3]...[A50]
Partition B: [B1][B2]
Partition C: [C1][C2]

Execution: A1 → B1 → C1 → A2 → B2 → C2 → A3 → ...
Enter fullscreen mode Exit fullscreen mode

Three strategies available:

  • round-robin (recommended) — strict A→B→C→A→B→C rotation
  • random — stateless, great for high load
  • smart — boosts smaller queues, prevents starvation

Plus concurrency limiting per partition - e.g., max 2 AI jobs per user
simultaneously, while other users keep running.


Setup in 4 Steps

Install:

composer require yangusik/laravel-balanced-queue
php artisan vendor:publish --tag=balanced-queue-config
Enter fullscreen mode Exit fullscreen mode

Add connection to config/queue.php:

'balanced' => [
    'driver' => 'balanced',
    'connection' => 'default',
    'queue' => 'default',
    'retry_after' => 90,
],
Enter fullscreen mode Exit fullscreen mode

Create a job:

use YanGusik\BalancedQueue\Jobs\BalancedDispatchable;

class GenerateAIImage implements ShouldQueue
{
    use BalancedDispatchable; // Instead of standard Dispatchable

    public function __construct(
        public int $userId,
        public string $prompt
    ) {}

    public function handle(): void
    {
        // AI generation logic
    }
}
Enter fullscreen mode Exit fullscreen mode

Dispatch:

// $userId is auto-detected as partition key
GenerateAIImage::dispatch($userId, $prompt)
    ->onConnection('balanced')
    ->onQueue('ai-generation');
Enter fullscreen mode Exit fullscreen mode

Run your workers as usual - no changes needed:

php artisan queue:work balanced --queue=ai-generation
Enter fullscreen mode Exit fullscreen mode

Partition Keys

The trait auto-detects $userId, $user_id, $tenantId, $tenant_id as
partition keys. For custom logic:

public function getPartitionKey(): string
{
    return "merchant:{$this->order->merchant_id}";
}
Enter fullscreen mode Exit fullscreen mode

Or set it at dispatch time:

MyJob::dispatch($data)->onPartition($companyId)->onConnection('balanced');
Enter fullscreen mode Exit fullscreen mode

Monitoring

Built-in live monitor:

php artisan balanced-queue:table --watch
Enter fullscreen mode Exit fullscreen mode
+------------+--------+---------+--------+-----------+
| Partition  | Status | Pending | Active | Processed |
+------------+--------+---------+--------+-----------+
| user:456   | ●      | 15      | 2      | 45        |
| user:123   | ●      | 8       | 2      | 120       |
| user:789   | ○      | 3       | 0      | 12        |
+------------+--------+---------+--------+-----------+
  Total: 26 pending, 4 active - Strategy: round-robin | Max concurrent: 2
Enter fullscreen mode Exit fullscreen mode

Also supports Prometheus + Grafana via built-in HTTP endpoints, and
Laravel Horizon for worker management (with some caveats - pending job
count won't show in Horizon's UI due to the different Redis key structure).


When to Use This

Perfect for multi-tenant apps where:

  • Users submit batch jobs (AI generation, report exports, video processing)
  • You need fair resource sharing across tenants
  • Some users consistently submit more work than others

Not ideal if you need strict priority queues or real-time SLA guarantees per user
(though you can build that with a custom Strategy).


Links

Feedback welcome - especially on the smart strategy and adaptive limiter,
those are areas I'm still tuning.

Top comments (0)