LaravelのwithoutOverlappingによるスクリプト遅延や失敗の問題

Laravelでは、everyMinuteで頻繁に実行されるタスクにおいて、前のスクリプトが完了する前に次のスクリプトが実行されてしまうことを防ぐために、withoutOverlappingメソッドを使用します。これにより、想定外のエラーやデータの不整合を避けることができます。

$schedule->command('NoPersonPrize')->everyMinute()->withoutOverlapping();

しかし、複数のwithoutOverlappingを使用したタスクと、それを使わないタスクを同じKernel内に配置すると、一部のスクリプトが実行されなかったり、遅れて実行されたりすることがあります。その原因は何でしょうか?

public function withoutOverlapping($expiresAt = 1440)
{
    $this->withoutOverlapping = true;
    $this->expiresAt = $expiresAt;

    return $this->then(function () {
        $this->mutex->forget($this);
    })->skip(function () {
        return $this->mutex->exists($this);
    });
}

withoutOverlappingは内部的にキャッシュを使って互斥ロック(Mutex)を実装しています。LaravelのキャッシュがRedisの場合、Redisがこのロックのキャッシュキーとして使用されます。デフォルトのキャッシュドライバはファイルです。

$schedule->command('a')->everyMinute()->withoutOverlapping();
$schedule->command('b')->everyMinute();
$schedule->command('c')->everyMinute();
$schedule->command('d')->everyMinute()->withoutOverlapping();

多くの人が、これらのスクリプト(a, b, c, d)は毎分同時に実行されると考えがちですが、実際には異なります。順番は「a → (b, c) → d」になります。(bとcはほぼ同時に実行されますが、aが終了してからdが実行されます。)

この問題は、互斥ロックによって一つのスクリプトが完了してから次のスクリプトが実行される仕組みによるものです。各スクリプトが独立して実行されるわけではありません。

例:

laravel_cache:framework/schedule-0e06f675a49cb9a0010858533b27d6a525caea88
内容:b:1;

これにより、いくつかのスクリプトが遅延したり、実行されないことがあります。

以下のような対策を検討してください:

  1. withoutOverlappingを使用しないタスクを分離する withoutOverlappingを使用するタスクと使用しないタスクを明確に分離しましょう。
$schedule->command('b')->everyMinute();
$schedule->command('c')->everyMinute();

// 区切り線
$schedule->command('a')->everyMinute()->withoutOverlapping();
$schedule->command('d')->everyMinute()->withoutOverlapping();
  1. 不要なeveryMinute+withoutOverlappingの組み合わせを避ける 必要がない場合、毎分実行するタスクにこの組み合わせを使用するのは避け、コード内で重複実行を防ぐロジックを追加します。

  2. 特定時間での実行で競合を避ける 複数のwithoutOverlappingを使用するタスクや使用しないタスクがある場合、->dailyAt('18:33')のように特殊な時間帯で実行することで、すべての整点での競合を回避できます。

  3. withoutOverlappingの使用を減らすまたは排除する 可能であれば、withoutOverlappingの使用を最小限に留めることで、このような問題を回避できます。

タグ: laravel Scheduling mutex

8月2日 20:52 投稿