agentsclimarketplace

Laravel queue design

Skill kwhorne/elyra-skills/skills/laravel-queue-design

Design Laravel queue jobs that survive production - idempotency, retries and backoff, failure handling, batching, chaining, and Horizon tuning. Use when the user creates or reviews queued jobs, debugs stuck or duplicated jobs, asks about retries or failed_jobs, or moves slow work out of the request cycle.From its SKILL.md

Install
npx -y skills add kwhorne/elyra-skills --skill laravel-queue-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.0 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it

Laravel Queue Design

A queued job will run twice, run late, or run after the world changed under it. Design every job for those three facts and production gets boring — in the good way.

When to use

  • Creating or reviewing queued jobs, listeners, or notifications
  • "Jobs are stuck / duplicated / silently failing"
  • Moving slow work (mail, exports, API calls) out of the request
  • Setting up or tuning Horizon / queue workers

Principles

  • At-least-once means duplicates. Every handler must tolerate running twice — idempotency is the entry fee, not a nice-to-have.
  • Small payload, fresh state. Pass IDs, re-fetch inside the job. The world at dispatch time is not the world at run time.
  • Failure is a feature. Decide per job what retry, backoff, and final failure mean — the defaults are guesses.
  • One job, one outcome. A job that does five things fails five ways and retries all of them.

Process

1. Design the job

class ProcessRefund implements ShouldQueue
{
    public function __construct(public int $orderId) {}   // ID, not model state you'll trust later

    public $tries = 3;
    public $backoff = [10, 60, 300];          // seconds; spread retries out
    public $timeout = 120;                     // must be < retry_after in queue config!

    public function handle(): void
    {
        $order = Order::findOrFail($this->orderId);   // fresh state
        if ($order->refunded) return;                  // idempotency check
        // ...
    }
}
  • Eloquent models in constructors are fine (SerializesModels re-fetches), but decisions made on dispatch-time state are stale — re-check conditions in handle()
  • $timeout < retry_after (queue config) or the job runs twice concurrently — the classic Laravel queue foot-gun

2. Make it idempotent

Pick the mechanism that fits:

  • State check: if ($order->refunded) return; — re-read, then act
  • Unique jobs: ShouldBeUnique to prevent duplicate dispatch
  • DB constraint: unique index + firstOrCreate/upsert absorbs the duplicate
  • External calls: pass an idempotency key to the payment/API provider

3. Decide the failure story

  • failed(Throwable $e) method: notify, mark the domain object, compensate
  • Don't retry the unretryable: validation-type failures → $this->fail($e) immediately
  • Poison-pill protection: $tries/$maxExceptions so one bad payload doesn't loop forever
  • Monitor failed_jobs — a table nobody looks at is a black hole. Alert on growth; queue:retry after fixing the cause

4. Compose bigger work

NeedTool
Steps in order, abort on failureJob chain (Bus::chain)
Fan-out N items + completion hookBatch (Bus::batch(...)->then(...)->catch(...))
Per-tenant/user serializationWithoutOverlapping middleware
External API rate limitsRateLimited middleware, dedicated queue

For batches: items must be independently retryable; allowFailures() if one bad item shouldn't sink the batch.

5. Separate queues by SLA

  • high (user-facing: mail verification, receipts), default, low (exports, sync)
  • Workers: --queue=high,default,low priority order, or dedicated workers per queue
  • One slow queue must not starve fast ones — that's the whole point of separation

6. Operate it

  • Horizon: balance/processes per queue, alert on wait time, watch failed rate
  • Deploys: queue:restart (workers hold old code in memory until restarted)
  • Track queue wait time (latency from dispatch to start) — it's the user-facing number

Output format

## Queue review: <job/feature>

| Job | Idempotent via | tries/backoff | timeout<retry_after | On final failure |
|-----|----------------|---------------|---------------------|------------------|
| …   | state check    | 3 / 10-60-300 | ✅ 120<300          | notify + mark    |

### Gaps (ranked)
1. <job>: <risk> → <fix>

### Queue topology
high: …, default: …, low: … — workers: …

Anti-patterns

  • ❌ Serializing a model and trusting its attributes at run time (stale decisions)
  • $timeoutretry_after — concurrent double execution by design
  • ❌ Default retries on a non-idempotent job touching money
  • ❌ One giant job processing 10 000 rows instead of a batch of small ones
  • failed_jobs as a write-only table with no alerting
  • ❌ Forgetting queue:restart on deploy, then debugging "old code" for an hour
  • ❌ Everything on the default queue — exports starving password-reset mails

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,629. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.