146 lines
3.2 KiB
Markdown
146 lines
3.2 KiB
Markdown
# Queue & Job Best Practices
|
|
|
|
## Set `retry_after` Greater Than `timeout`
|
|
|
|
If `retry_after` is shorter than the job's `timeout`, the queue worker re-dispatches the job while it's still running, causing duplicate execution.
|
|
|
|
Incorrect (`retry_after` ≤ `timeout`):
|
|
```php
|
|
class ProcessReport implements ShouldQueue
|
|
{
|
|
public $timeout = 120;
|
|
}
|
|
|
|
// config/queue.php — retry_after: 90 ← job retried while still running!
|
|
```
|
|
|
|
Correct (`retry_after` > `timeout`):
|
|
```php
|
|
class ProcessReport implements ShouldQueue
|
|
{
|
|
public $timeout = 120;
|
|
}
|
|
|
|
// config/queue.php — retry_after: 180 ← safely longer than any job timeout
|
|
```
|
|
|
|
## Use Exponential Backoff
|
|
|
|
Use progressively longer delays between retries to avoid hammering failing services.
|
|
|
|
Incorrect (fixed retry interval):
|
|
```php
|
|
class SyncWithStripe implements ShouldQueue
|
|
{
|
|
public $tries = 3;
|
|
// Default: retries immediately, overwhelming the API
|
|
}
|
|
```
|
|
|
|
Correct (exponential backoff):
|
|
```php
|
|
class SyncWithStripe implements ShouldQueue
|
|
{
|
|
public $tries = 3;
|
|
public $backoff = [1, 5, 10];
|
|
}
|
|
```
|
|
|
|
## Implement `ShouldBeUnique`
|
|
|
|
Prevent duplicate job processing.
|
|
|
|
```php
|
|
class GenerateInvoice implements ShouldQueue, ShouldBeUnique
|
|
{
|
|
public function uniqueId(): string
|
|
{
|
|
return $this->order->id;
|
|
}
|
|
|
|
public $uniqueFor = 3600;
|
|
}
|
|
```
|
|
|
|
## Always Implement `failed()`
|
|
|
|
Handle errors explicitly — don't rely on silent failure.
|
|
|
|
```php
|
|
public function failed(?Throwable $exception): void
|
|
{
|
|
$this->podcast->update(['status' => 'failed']);
|
|
Log::error('Processing failed', ['id' => $this->podcast->id, 'error' => $exception->getMessage()]);
|
|
}
|
|
```
|
|
|
|
## Rate Limit External API Calls in Jobs
|
|
|
|
Use `RateLimited` middleware to throttle jobs calling third-party APIs.
|
|
|
|
```php
|
|
public function middleware(): array
|
|
{
|
|
return [new RateLimited('external-api')];
|
|
}
|
|
```
|
|
|
|
## Batch Related Jobs
|
|
|
|
Use `Bus::batch()` when jobs should succeed or fail together.
|
|
|
|
```php
|
|
Bus::batch([
|
|
new ImportCsvChunk($chunk1),
|
|
new ImportCsvChunk($chunk2),
|
|
])
|
|
->then(fn (Batch $batch) => Notification::send($user, new ImportComplete))
|
|
->catch(fn (Batch $batch, Throwable $e) => Log::error('Batch failed'))
|
|
->dispatch();
|
|
```
|
|
|
|
## `retryUntil()` Needs `$tries = 0`
|
|
|
|
When using time-based retry limits, set `$tries = 0` to avoid premature failure.
|
|
|
|
```php
|
|
public $tries = 0;
|
|
|
|
public function retryUntil(): \DateTimeInterface
|
|
{
|
|
return now()->addHours(4);
|
|
}
|
|
```
|
|
|
|
## Use `WithoutOverlapping::untilProcessing()`
|
|
|
|
Prevents concurrent execution while allowing new instances to queue.
|
|
|
|
```php
|
|
public function middleware(): array
|
|
{
|
|
return [new WithoutOverlapping($this->product->id)->untilProcessing()];
|
|
}
|
|
```
|
|
|
|
Without `untilProcessing()`, the lock extends through queue wait time. With it, the lock releases when processing starts.
|
|
|
|
## Use Horizon for Complex Queue Scenarios
|
|
|
|
Use Laravel Horizon when you need monitoring, auto-scaling, failure tracking, or multiple queues with different priorities.
|
|
|
|
```php
|
|
// config/horizon.php
|
|
'environments' => [
|
|
'production' => [
|
|
'supervisor-1' => [
|
|
'connection' => 'redis',
|
|
'queue' => ['high', 'default', 'low'],
|
|
'balance' => 'auto',
|
|
'minProcesses' => 1,
|
|
'maxProcesses' => 10,
|
|
'tries' => 3,
|
|
],
|
|
],
|
|
],
|
|
``` |