← Laravel: requests, data and application decisions
09 / 10 · 65 MIN

Queues, retries and scheduling

Relate connection, queue, commit and worker lifecycle to work delivery. Distinguish uniqueness, overlap prevention and effect idempotency, including distributed scheduling limits.

Connection, queue and consumer

The queue connection selects the configured backend; the queue identifies a set of jobs within that connection. onQueue("receipts") does not itself change the backend or the SQL connection used by job queries. Check where the producer dispatches and which queues workers consume, including priority.

A job accepted for dispatch may remain unprocessed if no consumer watches that queue. Operational analysis should connect these three elements before concluding that job code failed.

Dispatch after data commitment

A worker can receive a job before the request transaction commits the order it needs to read. after_commit on the queue connection or afterCommit on the job defers dispatch until the relevant open transactions commit. If the transaction rolls back, pending jobs from that context are discarded.

A fixed delay does not observe commit. This ordering addresses the stated visibility race; it is neither a distributed transaction with the external provider nor a guarantee that every effect occurs exactly once.

Sequence, failure and an already accepted effect

A chain runs jobs in order of success; if B permanently fails after A, C does not proceed. This does not automatically undo A’s effect. Also distinguish unique dispatches from attempts: ShouldBeUnique uses a lock to limit dispatches, but the same job can retry after external acceptance and local failure.

Define stable identity and reconciliation with the provider. Unique job constraints do not apply to jobs inside batches, so that scope needs its own analysis.

Attempts, timeouts and overlap

For drivers using retry_after, effective worker timeout should be several seconds shorter than that interval. If the job becomes available again before the first worker terminates, another can process it concurrently. Check job overrides and timeout enforcement.

WithoutOverlapping controls concurrency by key, but releasing a job due to overlap also consumes an attempt. releaseAfter sets delay, dontRelease deletes the overlapping job and expireAfter bounds lock duration. None of these options should be confused with a durable record of unique effects.

Active workers and published code

queue:work keeps a long-lived process that does not automatically absorb published changes. queue:restart uses cache to signal graceful exit after the current job; a process monitor should restart consumers. Check cache configuration, exit, restart and effective revision in subsequent work.

Successful web deployment does not prove workers changed. Repeating every job before resolving version activation can duplicate effects using the same old code.

One server and no overlap

onOneServer coordinates the same task across schedulers using a lock in a supported shared central cache service. withoutOverlapping avoids starting the task while the previous instance still holds its lock. Define an appropriate duration and distinguish the command from work it merely dispatches: if a command finishes immediately after queueing a job, the scheduler lock does not automatically cover that job’s execution.

Independent local caches and matching key names do not create distributed coordination.

Business period and version change

A local time can repeat or not exist during a daylight-saving change. Documentation warns about repeated or missing tasks; define the business period to process and reconcile its state. UTC can simplify the technical calendar, but the business rule remains explicit.

For sub-minute tasks, schedule:run can retain old code until the minute ends; schedule:interrupt after deployment interrupts those invocations. Do not confuse this with interrupting every already dispatched job or proving the daily report completed.

<?php
// Application excerpts for reading: imports and domain classes omitted.
DB::transaction(function () use ($invoice) {
    $invoice->status = 'approved';
    $invoice->save();
    DeliverReceipt::dispatch($invoice->id)
        ->onQueue('receipts')
        ->afterCommit();
});

// Separate schedule definition; report:build performs its work synchronously.
Schedule::command('report:build')
    ->dailyAt('03:00')
    ->timezone('UTC')
    ->onOneServer()
    ->withoutOverlapping(15);
// Shared supported central cache and completion before lock expiry assumed.
IN PRACTICE

The first fragment assumes the model uses the same transactional connection and the job receives stable identity. afterCommit addresses visibility of approval to the worker; it does not prove one-time receipt delivery. The second fragment schedules a synchronous command in UTC and coordinates servers and overlap through shared cache.

Fifteen minutes is an example assumption rather than a universal value. Classes, imports, configuration and failure handling are not supplied. The activity is interpreting limits without executing code.

Common pitfalls

Treating onQueue as a backend connection; replacing commit with a fixed delay; expecting global rollback from a chain; ignoring middleware-consumed attempts; attributing exactly-once effects to ShouldBeUnique; assuming published files update active workers.

Related topics: Transactions, concurrency and migrations · Container, providers and instance lifetimes · Tests, cache and release diagnosis

Take this idea with you

Queue, transaction, lock and external effect have their own boundaries. Define work identity, observe when it becomes executable, control the concurrency interval and reconcile final acceptance. Scheduling and restart commands require evidence of execution that actually occurred.

Create account

Reference: Queue connection and queue name · Laravel 13.x; PHP 8.3+