Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
LaravelPHPJune 20, 20263 min read

Reliable background jobs: mastering Laravel queues, retries, and idempotency

Reliable background jobs are essential for production Laravel apps. Learn to implement idempotent logic and custom backoff strategies to prevent data corruption.

LaravelPHPQueuesBackendPerformanceReliability
Close-up of a computer screen displaying an authentication failed message.

When a third-party API times out or a database deadlock hits, your background workers shouldn't just crash and burn. I learned this the hard way three years ago when a botched retry policy sent 5,000 duplicate emails to our users in under ten minutes.

Mastering Reliable background jobs in Laravel

Reliable background jobs require more than just putting a class in a queue:work loop. You need a strategy that assumes failure is the default state of any network-dependent task. If you're running complex workflows, scaling Laravel queues on Kubernetes with KEDA can handle the volume, but it won't fix logic that isn't idempotent.

The Problem with Naive Retries

By default, Laravel’s tries property is a lifesaver, but it’s also a foot-gun. If you set public $tries = 3; on a job that charges a credit card, you might end up charging that card three times if the failure happens after the charge but before the job marks itself as complete.

We initially tried wrapping everything in a massive try-catch block, but that just masked the underlying issue. The real fix is ensuring your jobs are idempotent.

Designing for Idempotency

An idempotent job is one that can be run multiple times without changing the result beyond the initial execution. In practice, this means checking the state of your system before executing the side effect.

PHP
public function handle(): void
{
    #6A9955">// Check if we already processed this
    if ($this->payment->is_processed) {
        return;
    }

    $response = $this->gateway->charge($this->amount);

    if ($response->successful()) {
        $this->payment->update(['is_processed' => true]);
    }
}

If you’re working with external APIs, look for "Idempotency-Key" headers. Many modern providers (like Stripe or Adyen) allow you to pass a unique string—often the job ID or a UUID—to ensure that even if you send the request twice, they only process it once.

Configuring Exponential Backoff

Linear retries are rarely what you want. If an API is down, hammering it every 60 seconds isn't going to help it recover. Instead, use exponential backoff to give the service breathing room.

In your job class, define the backoff method:

PHP
public function backoff(): array
{
    #6A9955">// Wait 10s, then 30s, then 60s
    return [10, 30, 60];
}

This simple change significantly reduces the load on struggling infrastructure. We’ve found that using a range of 10 to 60 seconds handles about 90% of transient network flickers in our production environment.

Handling Job Failures Gracefully

Businessman in a suit sitting distressed on concrete stairs, facing challenges.

Even with perfect idempotency, some jobs will eventually hit the max retry limit. When that happens, you need a way to clean up or alert the team. I prefer using the failed method to log specific context.

PHP
public function failed(Throwable $exception): void
{
    Log::error('Job failed after max retries', [
        'job' => get_class($this),
        'id' => $this->payment->id,
        'error' => $exception->getMessage()
    ]);
}

Don't just let the job disappear into the failed_jobs table. If you're building a clean service layer, keep your business logic inside the service and use the Job class as a thin wrapper that invokes that service. This makes testing significantly easier because you can test the service logic independently of the queue driver.

Frequently Asked Questions

How do I know if a job is truly idempotent? Ask yourself: "If this code runs twice with the exact same input, does the final state of the database or external API change?" If the answer is yes, it's not idempotent.

Should I use database or redis for queues? For small to medium projects, database is fine. Once you have high throughput, move to redis. The performance gain is massive, and it handles the locking mechanisms much better under heavy load.

What about jobs that shouldn't be retried? Set public $tries = 1; for jobs that involve irreversible actions where retries could cause data inconsistency. Not every job needs a second chance.

I still struggle with determining the perfect backoff duration for every unique external API. Some services recover in seconds; others take minutes. My advice? Start conservative, monitor your error logs, and adjust based on the actual behavior you see in production. Don't over-engineer the retry logic until you have the telemetry to back it up.

Back to Blog

Similar Posts

Man in safety gear adjusting traffic light on a clear day from lift.
LaravelPHPJune 20, 20264 min read

Laravel Horizon Graceful Shutdowns: Mastering Signal Handling for Workers

Laravel Horizon graceful shutdowns are critical for reliable background processing. Learn to implement signal handling to prevent data loss in high-concurrency.

Read more
Explore the airy and geometric space of a modern library with intricate staircases and minimalist design.
LaravelPHPJune 20, 20264 min read

Eliminating N+1 queries in Eloquent: A Pragmatic Approach

Eliminating N+1 queries in Eloquent is essential for Laravel performance. Learn how to identify, debug, and solve these database bottlenecks in production.

Read more
Explore the classic architecture of London's Lyceum Theatre with striking column details.
LaravelPHPJune 20, 20264 min read

Laravel Eloquent Performance: Mastering PostgreSQL Generated Columns

Optimize Laravel Eloquent performance by leveraging PostgreSQL generated columns. Learn to move complex transformations to the DB for faster read models.

Read more