Laravelでスケジュールの並列実行

Laravelの[タスクスケジュール機能](https://readouble.com/laravel/5.5/ja/scheduling.html)を使用して並列処理を行う必要があったのでやり方を調べてみました。

スケジューラーという事なので当然時刻が被った場合も問題なく並列処理を行ってくれると思っていたのですがうまくいきませんでした。
**並列処理のオプション等を見逃している可能性がありますのでありましたらご指摘ください。**

実行コマンド（本来はcronから実行しますが、確認では直接実行して試しています）

```
php artisan schedule:run
```

クーロンに設定するならこんな感じ（環境によって参照するDBを変えている等の場合のために環境設定を付けておく）

```
* * * * * php /path-to-your-project/artisan schedule:run --env=production >> /dev/null 2>&1
```

# クロージャーで設定してみる

```php:app/Console/Kernel.php

use Illuminate\Support\Carbon;


    protected function schedule(Schedule $schedule)
    {
        $time = Carbon::now()->format('H:i');

        $schedule->call(function () {
            sleep(5);
            var_dump(1, Carbon::now());
        })->dailyAt($time);

        $schedule->call(function () {
            sleep(5);
            var_dump(2, Carbon::now());
        })->dailyAt($time);

        $schedule->call(function () {
            sleep(5);
            var_dump(3, Carbon::now());
        })->dailyAt($time);

        var_dump('test');

        // $schedule->command('inspire')
        //          ->hourly();
    }
```

Schedule内部では[Carbon](https://github.com/briannesbitt/Carbon)を使用しているのでCarbonで合わせました。

## 実行結果

```
# php artisan schedule:run
string(4) "test"
Running scheduled command: Closure
int(1)
object(Illuminate\Support\Carbon)#3241 (3) {
  ["date"]=>
  string(26) "2018-02-27 22:55:55.862911"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
Running scheduled command: Closure
int(2)
object(Illuminate\Support\Carbon)#3242 (3) {
  ["date"]=>
  string(26) "2018-02-27 22:56:00.864827"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
Running scheduled command: Closure
int(3)
object(Illuminate\Support\Carbon)#3243 (3) {
  ["date"]=>
  string(26) "2018-02-27 22:56:05.867566"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
```

## 結果

**同期処理**

※上記を見ると指定時刻が過ぎても実行はされる様子。

```php
$time = Carbon::now()->addMinute(1)->format('H:i');
```

としたところ１つも実行されなかった。

先に`dailyAt`設定時に
`vendor/mtdowling/cron-expression/src/Cron/CronExpression.php`
の`isDue`で判定される様子。
クロージャーは後から実行されました。

# [Artisanコマンド](https://readouble.com/laravel/5.5/ja/artisan.html)にしてcommandで設定してみる

```
php artisan make:command ParallelSchedule
```

```php:app/Console/Commands/ParallelSchedule.php

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;

class ParallelSchedule extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:batch {id?}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Parallel processing for schedule';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $id = $this->argument('id');
        Log::info((string)$id.':'.(string)Carbon::now());
        sleep(5);
    }
}
```

```php:app/Console/Kernel.php
protected $commands = [
    Commands\ParallelSchedule::class
];

protected function schedule(Schedule $schedule)
{
    $time = Carbon::now()->format('H:i');

    $schedule->command('command:batch 1')->dailyAt($time);
    $schedule->command('command:batch 2')->dailyAt($time);
    $schedule->command('command:batch 3')->dailyAt($time);

    // $schedule->command('inspire')
    //          ->hourly();
}
```

## 実行結果

```
# php artisan schedule:run
Running scheduled command: '/usr/bin/php' 'artisan' command:batch 1 > '/dev/null' 2>&1
Running scheduled command: '/usr/bin/php' 'artisan' command:batch 2 > '/dev/null' 2>&1
Running scheduled command: '/usr/bin/php' 'artisan' command:batch 3 > '/dev/null' 2>&1
```

```
[2018-02-27 23:39:04] test.INFO: 1:2018-02-27 23:39:04  
[2018-02-27 23:39:11] test.INFO: 2:2018-02-27 23:39:11  
[2018-02-27 23:39:17] test.INFO: 3:2018-02-27 23:39:17  
```

## 結果

**同期処理**

# [Artisanコマンド](https://readouble.com/laravel/5.5/ja/artisan.html)にしてexecを設定してみる

```php:app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $time = Carbon::now()->format('H:i');

        $schedule->exec('php '.base_path().'/artisan command:batch 1 --env=production')->dailyAt($time);
        $schedule->exec('php '.base_path().'/artisan command:batch 2 --env=production')->dailyAt($time);
        $schedule->exec('php '.base_path().'/artisan command:batch 3 --env=production')->dailyAt($time);

        // $schedule->command('inspire')
        //          ->hourly();
    }
```

## 実行結果

```
# php artisan schedule:run
Running scheduled command: php /home/public/app/artisan command:batch 1 --env=production > '/dev/null' 2>&1
Running scheduled command: php /home/public/app/artisan command:batch 2 --env=production > '/dev/null' 2>&1
Running scheduled command: php /home/public/app/artisan command:batch 3 --env=production > '/dev/null' 2>&1
```

```
[2018-02-27 23:48:57] test.INFO: 1:2018-02-27 23:48:57  
[2018-02-27 23:49:03] test.INFO: 2:2018-02-27 23:49:03  
[2018-02-27 23:49:10] test.INFO: 3:2018-02-27 23:49:10  
```

## 結果

**同期処理**

# PHP標準のexec()をクロージャー内で使ってみる

```php:app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $time = Carbon::now()->format('H:i');

        $schedule->call(function () {
            //  > /dev/null & でレスポンスを待たない
            exec('php '.base_path().'/artisan command:batch 1 --env=production > /dev/null &');
            exec('php '.base_path().'/artisan command:batch 2 --env=production > /dev/null &');
            exec('php '.base_path().'/artisan command:batch 3 --env=production > /dev/null &');
        })->dailyAt($time);

        // $schedule->command('inspire')
        //          ->hourly();
    }
```

## 実行結果

```
# php artisan schedule:run
Running scheduled command: Closure
```

```
[2018-02-28 00:05:43] test.INFO: 2:2018-02-28 00:05:43  
[2018-02-28 00:05:43] test.INFO: 1:2018-02-28 00:05:43  
[2018-02-28 00:05:43] test.INFO: 3:2018-02-28 00:05:43
```

## 結果

**非同期処理**

# **runInBackground**を使用してみる
（コメントで教えていただきました。）

## クロージャーで設定してみる(runInBackground)

```php:app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $time = Carbon::now()->format('H:i');

        $schedule->call(function () {
            sleep(5);
            var_dump(1, Carbon::now());
        })->dailyAt($time)->runInBackground();

        $schedule->call(function () {
            sleep(5);
            var_dump(2, Carbon::now());
        })->dailyAt($time)->runInBackground();

        $schedule->call(function () {
            sleep(5);
            var_dump(3, Carbon::now());
        })->dailyAt($time)->runInBackground();
    }
```

### 実行結果

```
# php artisan schedule:run
Running scheduled command: Closure
int(1)
object(Illuminate\Support\Carbon)#4905 (3) {
  ["date"]=>
  string(26) "2018-02-28 23:59:11.466811"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
Running scheduled command: Closure
int(2)
object(Illuminate\Support\Carbon)#4906 (3) {
  ["date"]=>
  string(26) "2018-02-28 23:59:16.467282"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
Running scheduled command: Closure
int(3)
object(Illuminate\Support\Carbon)#4907 (3) {
  ["date"]=>
  string(26) "2018-02-28 23:59:21.467938"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(10) "Asia/Tokyo"
}
```

### 結果

**同期処理**

## Artisanコマンドにしてcommandで設定してみる(runInBackground)

```php:app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $time = Carbon::now()->format('H:i');

        $schedule->command('command:batch 1 --env=production')->dailyAt($time)->runInBackground();
        $schedule->command('command:batch 2 --env=production')->dailyAt($time)->runInBackground();
        $schedule->command('command:batch 3 --env=production')->dailyAt($time)->runInBackground();
    }
```

### 実行結果

```
# php artisan schedule:run
Running scheduled command: ('/usr/bin/php' 'artisan' command:batch 1 --env=production > '/dev/null' 2>&1 ; '/usr/bin/php' 'artisan' schedule:finish "framework/schedule-d72fef692ea6d086899b393775691de2c59d998b") > '/dev/null' 2>&1 &
Running scheduled command: ('/usr/bin/php' 'artisan' command:batch 2 --env=production > '/dev/null' 2>&1 ; '/usr/bin/php' 'artisan' schedule:finish "framework/schedule-fece6325cae11a5e0d0771be0ffe2207c4822238") > '/dev/null' 2>&1 &
Running scheduled command: ('/usr/bin/php' 'artisan' command:batch 3 --env=production > '/dev/null' 2>&1 ; '/usr/bin/php' 'artisan' schedule:finish "framework/schedule-e3321e90e5e5f89efc46e6cecd0c8b2441bac03a") > '/dev/null' 2>&1 &
```

```
[2018-03-01 00:05:39] test.INFO: 3:2018-03-01 00:05:39  
[2018-03-01 00:05:39] test.INFO: 2:2018-03-01 00:05:39  
[2018-03-01 00:05:39] test.INFO: 1:2018-03-01 00:05:39  
```

### 結果

**非同期処理**


## Artisanコマンドにしてexecで設定してみる(runInBackground)

```php:app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $time = Carbon::now()->format('H:i');

        $schedule->exec('php '.base_path().'/artisan command:batch 1 --env=production')->dailyAt($time)->runInBackground();
        $schedule->exec('php '.base_path().'/artisan command:batch 2 --env=production')->dailyAt($time)->runInBackground();
        $schedule->exec('php '.base_path().'/artisan command:batch 3 --env=production')->dailyAt($time)->runInBackground();
    }
```

### 実行結果

```
# php artisan schedule:run
Running scheduled command: (php /home/public/app/artisan command:batch 1 --env=production > '/dev/null' 2>&1 ; '/usr/bin/php' 'artisan' schedule:finish "framework/schedule-ff228d98e15354119ff37ba8d14716d0ecc420fa") > '/dev/null' 2>&1 &
Running scheduled command: (php /home/public/app/artisan command:batch 2 --env=production > '/dev/null' 2>&1 ; '/usr/bin/php' 'artisan' schedule:finish "framework/schedule-9360705c67bd8666b81b8f8948dd63f611012551") > '/dev/null' 2>&1 &
Running scheduled command: (php /home/public/app/artisan command:batch 3 --env=production > '/dev/null' 2>&1 ; '/usr/bin/php' 'artisan' schedule:finish "framework/schedule-1faae3f394252d1eb297830de3be729f104fb086") > '/dev/null' 2>&1 &
```

```
[2018-03-01 00:08:14] test.INFO: 2:2018-03-01 00:08:14  
[2018-03-01 00:08:14] test.INFO: 1:2018-03-01 00:08:14  
[2018-03-01 00:08:14] test.INFO: 3:2018-03-01 00:08:14  
```

### 結果

**非同期処理**

# まとめ

* 並列処理を行いたい場合はArtisanコマンド化が必要
* runInBackgroundを設定する（commandとexecは効くが、クロージャーには効かない）
* runInBackgroundを使わない場合はPHP標準のexecに頼る