0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

トランザクション内でのジョブの取扱に注意!

Posted at

エラーの詳細

トランザクション内でユーザー登録を行い、その直後に登録したユーザーへ認証情報をメールで送信するため、トランザクション内でジョブをディスパッチしていました。

サンプル
try {
    DB::beginTransaction();
    // ユーザー登録処理
    $user = User::create($params);

    // 〜〜その他関連モデルの登録処理〜〜(省略)

    // ジョブ内でユーザーにメール送信
    AuthCredentialsNotificationJob::dispatch($user);

    DB::commit();
} catch (Exception $e) {
    DB::rollBack();
    Log::error(__METHOD__ . ' ユーザー登録処理でエラーが発生しました。 ' . $e);
    return back()->withInput()->withErrors('ユーザーの登録に失敗しました');
}

この処理を実行すると、約30%の確率で以下のようなエラーが発生していました。

Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Models\User].
in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:688

#0 Illuminate\Database\Eloquent\Builder->firstOrFail()
#1 App\Jobs\AuthCredentialsNotificationJob->restoreModel()
#2 App\Jobs\AuthCredentialsNotificationJob->__unserialize()
// 以下省略

エラーの原因

調査の結果、原因はトランザクション中にジョブをディスパッチしていたことでした。

何が起きているのか?

AuthCredentialsNotificationJobクラスでは、次のようにUserモデルを直接プロパティとして渡しています。

AuthCredentialsNotificationJob.php
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;

class AuthCredentialsNotificationJob implements ShouldQueue
{
    use Queueable, SerializesModels;

    public function __construct(
        public User $user // ← ここでUserモデルを渡している
    ) {
    }

    // 省略
}

Laravelのジョブはキュー投入時にモデルをシリアライズし、復元時にはfindOrFail($id)のように再度DBからそのモデルを取得しようとします。

しかし、トランザクションがまだコミットされていない場合、DBにはユーザー情報が存在していません。
そのため、ジョブ復元時にModelNotFoundExceptionが発生していた、というわけです。

解決策

Laravelでは、ジョブのディスパッチをトランザクションのコミット後に遅らせることができます。
そのための設定が after_commit オプションです。

queue.php
        'sqs' => [
            'driver' => 'sqs',
            'key' => env('SQS_KEY', 'your-public-key'),
            'secret' => env('SQS_SECRET', 'your-secret-key'),
            'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
            'queue' => env('SQS_QUEUE', 'your-queue-name'),
            'region' => env('SQS_REGION', 'us-east-1'),
            'after_commit' => true,  // これを追加!
        ],

この設定により、トランザクション中にジョブをディパッチしても、トランザクションがコミットされるまではジョブが実際にキューへ送られないようになります。

この設定を適用して以降、エラーは一切発生しなくなりました。

まとめ

  • トランザクション中にモデルを含んだジョブをディスパッチすると、ジョブ復元時にモデルが見つからずエラーになる可能性がある
  • after_commit: true を設定することで、トランザクションのコミット後にジョブがキュー投入される

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?