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 22, 20264 min read

Laravel Queues and Fork-Join Pattern: Parallel Processing Strategies

Master Laravel Queues and the Fork-Join pattern. Learn to implement parallel processing with Redis Lua scripting for atomic task decomposition and scaling.

LaravelPHPRedisQueuesParallel ProcessingArchitectureBackend

When you're processing a 50,000-row CSV import or generating complex PDF reports for thousands of users, a single serial queue worker becomes a bottleneck. I recently spent about three days refactoring a legacy reporting service that was timing out; the solution wasn't just "adding more workers," but implementing a robust Fork-Join pattern to break the work down.

Why Standard Batching Isn't Always Enough

Laravel’s built-in Bus::batch is excellent for tracking completion, but it lacks the granular control needed for complex dependency trees. We initially tried firing off individual jobs and tracking them with a simple database counter. It failed during a high-concurrency event because of race conditions on the counter increment—the classic "lost update" problem.

To solve this, we moved to a more deterministic approach using Redis as our source of truth. By leveraging Laravel Queues and Redis Lua for Atomic Job Batching, we moved the state management out of the relational database and into memory, where it belongs for high-frequency operations.

Architecting Task Decomposition

The goal is to split a monolithic "parent" task into N "child" workers, then execute a "join" callback only after every child reports success. Here is how I structure the decomposition:

  1. The Dispatcher: Analyzes the workload and calculates the chunk size.
  2. The Fork: Dispatches individual jobs with a shared batch_id.
  3. The State Store: Uses a Redis hash to track pending job IDs.
  4. The Join: A final job that clears the state and triggers the post-processing logic.

Instead of relying on standard database transactions, I use Redis Lua scripts to ensure that the decrement of the remaining task count is atomic. If you're building this for multi-tenant systems, remember that Laravel Redis Lua Scripting for Deterministic Rate Limiting is a vital companion to ensure your parallel workers don't overwhelm your downstream APIs.

Implementing the Join Mechanism

To ensure the join operation only fires once, we use a Redis SETNX (set if not exists) approach or a simple atomic decrement. Here is a simplified version of the logic we use:

PHP
#6A9955">// Inside your child job's handle() method
$remaining = Redis::connection()->eval(
    "return redis.call('decr', KEYS[1])",
    1,
    "batch:{$this->batchId}:remaining"
);

if ($remaining === 0) {
    #6A9955">// This worker is the last one; trigger the Join
    dispatch(new ProcessJoinResult($this->batchId));
}

This approach is highly performant. In our tests, it handled roughly 2,500 jobs per minute across 10 workers without a single collision. If you need to handle job pre-emption or priority management during this process, consider integrating Laravel Horizon Job Pre-emption: Managing Priority Queues with Lua to ensure urgent tasks don't get stuck behind a massive parallel batch.

Dealing with Failure

What happens when a child job fails? In a true fork-join implementation, you need a strategy for partial completion. We mark the batch as "failed" in Redis immediately upon the first child failure.

We also implement a "watchdog" job that runs every 10 minutes. It checks for stale batch_id keys in Redis that haven't reached zero. If a batch is stuck in limbo, the watchdog alerts the team. It’s not elegant, but it’s reliable. For more complex distributed systems, you might want to look into Laravel Distributed Task Scheduling: Implementing Redis Leader Election to ensure your cleanup tasks don't run on every single node simultaneously.

The Trade-offs

The biggest downside to this manual parallel processing approach is the overhead of managing Redis keys. You are effectively building your own state machine. If you don't implement a TTL (Time To Live) on your Redis keys, you'll eventually bloat your memory usage. We set a 24-hour expiration on all batch keys, which has been sufficient for our needs.

I'm still tinkering with how to handle "retry-all" scenarios. Currently, if a batch fails, we have to clear the Redis state and re-dispatch the entire set. It's not ideal for massive datasets, but for the scale we're hitting, it's the most predictable path forward.

FAQ

Q: Can I use standard Laravel Batches instead of manual Redis keys? A: Yes, if your requirements fit the standard Bus::batch feature. I only move to manual Redis Lua when I need custom atomic logic that the framework doesn't provide out of the box.

Q: How do I debug a stuck batch? A: Use redis-cli to inspect the batch:*:remaining keys. If the count is greater than zero but no jobs are running, your workers have likely crashed or been terminated.

Q: Does this affect queue throughput? A: Heavily. Forking one job into 100 small jobs can flood your queue. Make sure your Redis instance has enough memory and your workers are scaled appropriately before running large decompositions.

Ultimately, Laravel Queues are powerful enough to handle complex task decomposition if you're willing to manage the state manually. It's a trade-off between framework abstraction and granular control, but for high-performance pipelines, the extra code is worth the reliability.

Back to Blog

Similar Posts

LaravelPHPJune 22, 20264 min read

Laravel Queues and Redis Lua for Atomic Job Batching

Laravel Queues and Redis Lua enable atomic job batching. Learn to implement deterministic stream processing to handle high-concurrency tasks reliably.

Read more
LaravelPHPJune 22, 2026
4 min read

Laravel Queues: Building a Dead Letter Queue for Production Jobs

Master Laravel Queues by implementing a robust Dead Letter Queue (DLQ) pattern. Learn how to use Redis for reliable job failure handling and automated replay.

Read more
LaravelPHPJune 22, 20264 min read

Laravel Job Queuing: Architecting Weighted Fair Queuing with Redis

Laravel Job Queuing often struggles with priority starvation. Learn how to architect a Weighted Fair Queuing system using Redis Sorted Sets for better throughput.

Read more