0

What happens to the model's eager-loaded relationship when the model is passed to another Job's dispatch method?

Context:

There is a job that retrieves all ongoing games and this job should create an entry for users who forgot to create one when the entry registration was still open.

Current implementation:

There is only one Job that handles the retrieval of ongoing games and creation of entries. Basically, in this single job, thousands of entries are being created for our users. This is a problem since it's very prone to Timeout Exception.

And what's more concerning is, relationship are being lazy-loaded inside the function that creates the entry for each user. So that is already N+1.

Optimization Attempt:

Instead of doing all of these work in a single job, I have created two jobs:

  1. PrepareOngoingGamesJob - retrieves all ongoing games.
  2. CreateEntryJob - gets dispatched from the loop in the PrepareOngoingGamesJob.

This way, each entry creation will be treated as a single job.

Problem/Confusion:

To omit the N+1 problem, I eager-loaded the necessary relationships from the PrepareOngoingGamesJob, for example, $games = Games::with(['related_1', 'related_2', 'related_3']).

But what happens when I do this: $games->each(fn ($game) => CreateEntryJob::dispatch($game))

If I access related_1, related_2, related_3 in CreateEntryJob's handle() method, will it be queried from the database again? or is it already there?

3
  • 1
    This seems to be documented here. It will re-retrieve the relationships. On the other hand here it mentions that using toArray will convert the model and relationships to arrays and you can probably dispatch a job with the array instead to prevent re-retriveving. However keep in mind queue services like SQS have a limit on message size Commented Aug 28 at 5:13
  • 2
    everything is re queried from the database, game as well as related_1, related_2, related_3. This is bc the SerializesModels trait keeps only the primary keys of each model. The other option would be to pass the entire model, but in this case when you run the job you don't know if the models in have been updated/deleted since the job was dispatched. Commented Aug 28 at 7:28
  • 2
    in any case 4 pk lookups should be very fast still Commented Aug 28 at 7:29

1 Answer 1

-2

When you dispatch a job in Laravel and pass a model instance into it (e.g. CreateEntryJob::dispatch($game)), the model is serialised before being pushed onto the queue.

Laravel does not serialise the whole model object with all its loaded relationships. Instead, only the model's class name and primary key are stored. When the job is actually processed by a worker, the model is re-hydrated from the database using that primary key.

That means:

  • Any eager-loaded relationships you had on the model will not survive across jobs.

  • Inside your CreateEntryJob, when you call $game->related_1 (or related_2, etc.), Laravel will lazy-load those relationships again from the database.

  • So yes — unless you explicitly pass the relationship data yourself, it will result in additional queries in the job handler.


Quick demo

// In your PrepareOngoingGamesJob
$game = Game::with('related_1')->first();

dd($game->relationLoaded('related_1')); 
// true

CreateEntryJob::dispatch($game);

// -------------------------------
// In your CreateEntryJob handle()
public function handle()
{
    dd($this->game->relationLoaded('related_1'));
    // false  → relationship was lost when serialised
}

How to avoid N+1 in your case:

  1. Pass the raw data instead of the whole model
$games->each(function ($game) {
    CreateEntryJob::dispatch([
        'id'        => $game->id,
        'related_1' => $game->related_1,
        'related_2' => $game->related_2,
        'related_3' => $game->related_3,
    ]);
});

Then inside your job, you already have the relationships without extra queries.

  1. Or just pass the ID and reload with relationships in the job
CreateEntryJob::dispatch($game->id);

// In your Job
public function handle()
{
    $game = Game::with(['related_1', 'related_2', 'related_3'])
                ->find($this->gameId);

    // use $game safely, with eager-loaded relationships
}
  1. Or strip relationships if you don’t need them
$games->each(fn($game) => CreateEntryJob::dispatch($game->withoutRelations()));

📌 Key takeaway:
Jobs only serialise the model identifier, not its loaded state. If you need the relationships in the job, you must either re-query them there, or pass the relevant data along when dispatching.


Sign up to request clarification or add additional context in comments.

2 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
Your question look a lot like something ChatGPT would say. Please keep in mind that AI generated answers are NOT ALLOWED on stackoverflow.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.