Inside DB::transaction I create an Order and dispatch ProcessOrder::dispatch($order). Sometimes the job fails with ModelNotFoundException, yet the order exists afterwards. Laravel 12, database queue on the same DB, Redis in production.
DEV3 Developer Forum
My queued job cannot find the record I just created. Why?
Every post is reviewed first. Usually reviewed within 24 hours.
1 reply
This is a race between the transaction commit and the worker. The job was pushed to the queue before the transaction committed, and a fast worker (especially Redis, which is instant) picked it up and queried the order while it was still invisible to other connections.
Fix 1: dispatch after commit
ProcessOrder::dispatch($order)->afterCommit();
Laravel waits until the outermost transaction commits and discards the job if it rolls back.
Fix 2: make it the default for a connection
// config/queue.php
'redis' => [
'driver' => 'redis',
'after_commit' => true,
// ...
],
Or on the job class: public $afterCommit = true;. Same options exist for events (ShouldDispatchAfterCommit), mailables and notifications.
Fix 3: dispatch outside the transaction
$order = DB::transaction(function () use ($data) {
return Order::create($data);
});
ProcessOrder::dispatch($order);
Why the database driver does not show it
With the database queue driver on the same DB connection the job row is inside the same transaction, so it also only becomes visible at commit. That is why it only appears in production with Redis.
Related pitfalls
- Job fails with
ModelNotFoundExceptionwhen a model is deleted before it runs. Addpublic $deleteWhenMissingModels = true;if that is acceptable. - In tests,
Queue::fake()plusQueue::assertPushed(...)is the simplest way to assert a job was dispatched, whether or not it usesafterCommit. - Idempotency: assume jobs can run twice and guard with a status check.