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 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.

LaravelPHPRedisQueuesConcurrencyArchitectureBackend

Last month, I spent about three days debugging a race condition in a multi-tenant payment processing system. We were using Laravel’s standard Bus::batch() implementation, but our custom state tracking kept drifting during high-concurrency spikes. The culprit was a classic read-modify-write cycle that broke under heavy load.

If you’re building systems where job states must be perfectly synchronized across distributed workers, standard database-backed batching often isn't enough. To achieve true atomic consistency, you need to push that logic into the memory layer.

The Problem with Standard Laravel Queues

When you dispatch a batch in Laravel, the state is stored in the job_batches table. While reliable, every update to a job's status—like incrementing a progress counter or marking a batch as complete—requires a database transaction.

In a high-concurrency environment, you’ll inevitably hit row locking issues. If you’re processing 500+ jobs per second, your database becomes the bottleneck. We initially tried adding SELECT FOR UPDATE locks, but the latency overhead jumped by around 280ms per job, which was unacceptable for our throughput requirements.

Architecting Deterministic Job Batching

To solve this, I moved the state-tracking logic into Redis using Lua scripts. By doing this, we treat the batch state as an atomic stream. The database now acts as a durable record of truth, but Redis acts as the high-speed execution engine for state transitions.

If you are looking to scale this, you might also find Laravel Job Queuing: Architecting Weighted Fair Queuing with Redis useful, as it provides a similar foundation for managing job priority.

Implementing Atomic State Transitions

A Lua script ensures that the check and the update happen as a single operation. No other worker can intervene between the read and the write. Here is the core logic I implemented to track job completion status within a batch:

LUA
-- KEYS[1]: The batch status key
-- ARGV[1]: The job ID
-- ARGV[2]: The total jobs in batch
local current = redis.call('HINCRBY', KEYS[1], 'completed', 1)
if tonumber(current) >= tonumber(ARGV[2]) then
    redis.call('SET', KEYS[1] .. ':finished', 1)
    return 1
end
return 0

By executing this via Redis::eval(), we eliminate the risk of race conditions. This is a foundational technique I often use when preventing race conditions in distributed transactions for Node.js and Laravel.

Integrating with Laravel's Batch API

You don't need to abandon Bus::batch(). Instead, use it for the orchestration and lifecycle management, but offload the high-frequency state updates to your Lua-backed Redis service.

  1. Initialize: When dispatching the batch, create a hash in Redis representing the batch state.
  2. Execute: Every job in the batch runs the Lua script upon completion.
  3. Notify: Once the Lua script returns 1, trigger a final cleanup job that updates the primary database record.

This hybrid approach keeps your database load low while ensuring that your job batching remains deterministic. I’ve found this works exceptionally well when you’re also handling Laravel performance optimization: building content-aware batching pipelines to manage downstream API load.

Why Lua Scripting Wins

Using Redis Lua for job batching is superior for a few specific reasons:

  • Atomicity: The script is atomic by design. Redis blocks other commands until the script completes.
  • Network Efficiency: Instead of making multiple round trips to the database, you send one script execution command.
  • Predictability: Because it’s a script running in the Redis memory space, execution time is extremely consistent, which is vital for high-concurrency systems.

Trade-offs and Lessons Learned

The biggest risk here is complexity. If your Lua script fails or Redis goes down, your state tracking can desync from your database. You must implement a reconciliation job—a cron task that periodically compares the Redis state against the job_batches table and corrects any discrepancies.

I’m still experimenting with using WATCH/MULTI blocks for even tighter integration, but for now, the Lua script approach has saved us from the constant locking contention we faced earlier. If I were to start over, I’d probably build a dedicated BatchManager class to encapsulate the Lua logic rather than calling Redis::eval directly in the job classes. It would have made our unit testing much cleaner.

Frequently Asked Questions

Does this replace the job_batches table? No. Treat the database as your durable storage. Use the Redis state for the "hot" path of high-concurrency updates.

How do you handle Redis failures? Always wrap your Redis calls in a try-catch block. If Redis is unreachable, revert to a standard database update, even if it’s slower. Consistency is more important than speed.

Is this overkill for small batches? Absolutely. If your batch size is under 50 jobs, the overhead of managing Redis state isn't worth the architectural complexity. Stick to Laravel's native implementation until you see clear bottlenecks.

Back to Blog

Similar Posts

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.

Read more
LaravelPHP
June 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