opensource-workshop / connect-cms

コネクトCMS用リポジトリ
https://connect-cms.jp/
MIT License
13 stars 6 forks source link

【メール】キューキック #905

Closed akagane99 closed 3 years ago

akagane99 commented 3 years ago

現在のメール送信は、即時送信のみです。 Laravelのメールキューに溜めてから、別途送信できるように対応したい。

参考URL

akagane99 commented 3 years ago

調査

キューテーブルを作成するコマンドを実行したところ、下記エラーが発生。 CreateJobsTableクラスが既に存在している、との事。

下記にあった。

jobクラスも下記にあり。 https://github.com/opensource-workshop/connect-cms/tree/master/app/Jobs

作業途中か不明のため、要確認。手を付けられず。 既に掲示板に対応している、とコミットコメントあるけど、真偽不明。 ⇒ 確認しました。掲示板も含みメールキューは動いてない。jobsは掲示板で直接呼び出して使っているらしい。 ⇒⇒ プログラムを調査する。

キューテーブル作成コマンドエラー

>php artisan queue:table

   InvalidArgumentException  : A CreateJobsTable class already exists.

  at C:\projects\connect-cms\htdocs\connect-cms\vendor\laravel\framework\src\Illuminate\Database\Migrations\MigrationCreator.php:90
    86|             }
    87|         }
    88| 
    89|         if (class_exists($className = $this->getClassName($name))) {
  > 90|             throw new InvalidArgumentException("A {$className} class already exists.");
    91|         }
    92|     }
    93| 
    94|     /**

  Exception trace:

  1   Illuminate\Database\Migrations\MigrationCreator::ensureMigrationDoesntAlreadyExist("create_jobs_table", "C:\projects\connect-cms\htdocs\connect-cms\database/migrations")
      C:\projects\connect-cms\htdocs\connect-cms\vendor\laravel\framework\src\Illuminate\Database\Migrations\MigrationCreator.php:50

  2   Illuminate\Database\Migrations\MigrationCreator::create("create_jobs_table", "C:\projects\connect-cms\htdocs\connect-cms\database/migrations")
      C:\projects\connect-cms\htdocs\connect-cms\vendor\laravel\framework\src\Illuminate\Queue\Console\TableCommand.php:80

  Please use the argument -v to see more details.
akagane99 commented 3 years ago

調査2と検討

おおまかに、キューに溜めこむ部分と、キューを実行する(キューワーカ)部分に別れる。

対応案:キューの溜めこみ

設定変更

config/queue.php 'default' => env('QUEUE_CONNECTION', 'sync'), ↓ .env QUEUE_CONNECTION=sync

デフォルト(初期値)はsync .envで、databaseに変更する。かなぁ。

対応案:キューワーカ

案4でいく

案4:\Symfony\Component\Process\Process を使って非同期でqueue:workコマンド実行

メール送信する時だけキューワーカ動かして、送信終わったらキューワーカーを停止する。

symfonyのProcess componentで遊ぶ - Qiita バックグラウンド実行はstart()を指定

>>> $process = new Process(['/bin/sleep', '10'])
>>> $process->start()

キューされたすべてのジョブを処理し、終了する

php artisan queue:work --stop-when-empty

【Laravel5.7】WindowsとLinux両方で非同期処理したい - Qiita ⇒ WIN(xampp)とLinuxの処理の切り分け参考

    if (strpos(PHP_OS, 'WIN') !== false) {
        // Windows
    } else {
        // Linux
    }

実行可能な PHP バイナリの検索 - プロセスコンポーネント(Symfony Docs)

use Symfony\Component\Process\PhpExecutableFinder;

$phpBinaryFinder = new PhpExecutableFinder();
$phpBinaryPath = $phpBinaryFinder->find();
// $phpBinaryPath = '/usr/local/bin/php' (the result will be different on your computer)

プロセスタイムアウト - プロセスコンポーネント(Symfony Docs) タイムアウトは非同期のため、無視されてそう

案2:Laravelのタスクスケジュール&cron

利点

欠点

案3:laravel-async-queueライブラリ

微妙だったので廃案

・~~\Symfony\Component\Process\Process を使ってコマンドの非同期実行をしているが、タイムアウト指定をしていないため、 デフォルト60秒でプロセス停止されそう。~~  ⇒ 非同期コマンドだから即終了すので、プロセス停止はないだろう。 ・基本的にDatabaseQueueを上書きして実装していました。  $expire = 60 となっているけど、上記と連動していないため、ここを変更しても上記で落ちそう。  ⇒ 非同期でコマンド投げっぱなしのため、expireが機能するか不明。多分機能しない。 ・バックグラウンドコマンドで、標準エラー出力捨ててる。キューが衝突する事がある?

利点

欠点

案1:Supervisor

レンタルサーバの使用はほぼ無理のため、対象外とする。

利点

欠点

要確認

メモ

キューワーカを修正したら、キューワーカのリスタートが必要。

キューワーカとデプロイ

キューワーカは長時間起動プロセスであるため、リスタートしない限りコードの変更を反映しません。 ですから、キューワーカを使用しているアプリケーションをデプロイする一番シンプルな方法は、デプロイ処理の間、ワーカをリスタートすることです。queue:restartコマンドを実行することで、全ワーカを穏やかに再起動できます。

php artisan queue:restart

dispatchNowメソッド

failedメソッドは、ジョブがdispatchNowメソッドでディスパッチされた場合には呼び出されません。

akagane99 commented 3 years ago

追加調査・対応メモ

Jobのリトライさせない = $tries = 1にする。【済】

https://readouble.com/laravel/6.x/ja/queues.html#max-job-attempts-and-timeout

<?php

namespace App\Jobs;

class ProcessPodcast implements ShouldQueue
{
    /**
     * 最大試行回数
     *
     * @var int
     */
    public $tries = 1;
}

queue:workの各種デフォルト値を確認【済】

コマンド例

php artisan queue:work --tries=3

help

>php artisan queue:work --help
Description:
  Start processing jobs on the queue as a daemon

Usage:
  queue:work [options] [--] [<connection>]

Arguments:
  connection               The name of the queue connection to work

Options:
      --queue[=QUEUE]      The names of the queues to work
      --daemon             Run the worker in daemon mode (Deprecated)
      --once               Only process the next job on the queue
      --stop-when-empty    Stop when the queue is empty
      --delay[=DELAY]      The number of seconds to delay failed jobs [default: "0"]
      --force              Force the worker to run even in maintenance mode
      --memory[=MEMORY]    The memory limit in megabytes [default: "128"]
      --sleep[=SLEEP]      Number of seconds to sleep when no job is available [default: "3"]
      --timeout[=TIMEOUT]  The number of seconds a child process can run [default: "60"]
      --tries[=TRIES]      Number of times to attempt a job before logging it failed [default: "1"]
  -h, --help               Display this help message
  -q, --quiet              Do not output any message
  -V, --version            Display this application version
      --ansi               Force ANSI output
      --no-ansi            Disable ANSI output
  -n, --no-interaction     Do not ask any interactive question
      --env[=ENV]          The environment the command should run under
  -v|vv|vvv, --verbose     Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

2重起動時の問題検証【済】

https://github.com/opensource-workshop/connect-cms/pull/925#issuecomment-875211029

調査結果 https://github.com/opensource-workshop/connect-cms/issues/905#issuecomment-876994341

プログラム修正【済】

・メール設定画面から送信方式はなくす【済】  ・即時送信かスケジュール送信(バックグラウンド送信)はなくして一本化。  ・即時送信かスケジュール送信かは、.envのQUEUE_CONNECTION=sync (初期値:即時送信)又は database (スケジュール送信)で切り替える。 ・UserPluginBase.phpのメール送信修正【済】  ・$bucket_mail->timingは見なくて、dispatch()に一本化する。  ・$this->asyncQueueWork() は、.envのQUEUE_CONNECTION=database の時のみ動かす修正入れる。

参考画面 image

akagane99 commented 3 years ago

再調査

Process は現時点では使えない事がわかったので、古式ゆかしいexecで対応見直しかなぁと。

詳細

・\Symfony\Component\Process\Process を使って非同期処理。 現在のバージョンは、Laravelの依存で、v4.4.22 だった。

・Process の非同期は、親プロセスが止まると強制停止される仕様だった。 ・Process 5.2から、親プロセスが止まっても動き続けるオプションが追加された。 ⇒ このため、現時点ではProcess を使って非同期処理は使えず、今後Laravelバージョンアップして、Process のバージョンも5.2以上になれば、利用可能な代物でした。 ⇒ 以前Linuxで非同期動いた!と思ったのは勘違いでした。💦💦 ⇒ (Process の5.2のソースだけもらってきて、オーバーライドして使う手もあるけど、対応としては微妙かなぁ)

$process = new Process(['...', '...', '...']);
// this option allows a subprocess to continue running after the main script exited
$process->setOptions(['create_new_console' => true]);

参考URL

https://github.com/symfony/process/compare/4.4...5.2

akagane99 commented 3 years ago

2重起動時の問題検証

https://qiita.com/mpyw/items/15d14d920250a3b9eb5a 上記話題は、$schedule->command() 上での話で、今回はキューの話なのでちょっと扱い違うため、参考程度に読みました。

結果、問題なしでした。

詳細

※ キューワーカA, Bはどちらも --queue オプション指定なしで起動します。(実質--queue=default です)

キュー1をキューワーカAが処理中に、キューワーカBが起動した場合

jobsテーブル

id queue payload attempts reserved_at available_at created_at
24 default {"displayName":"App\Jobs\PostNoticeJob","job":"I... 0 NULL 1625816897 1625816897

キュー1をキューワーカAが処理中に、キュー2を追加してキューワーカBが起動した場合

それぞれ別のキューを処理するため、問題なし。

2つのキューワーカA、Bが常駐してる時に、キュー1がきた場合

⇒ 問題なし。

akagane99 commented 3 years ago

追加調査

2つのキュー1、2があり、1つ目で失敗した場合

タイムアウトを20秒、Jobでsleep(30);を入れてテスト。

タイムアウト20秒

$php_artisan_command = "{$php} \"{$artisan}\" queue:work --stop-when-empty --timeout=20";

・結果、キュー1で失敗⇒ 失敗したキューは failed_jobs に入りました。 ・キュー2は処理しませんでした。 ・失敗してもログに出力なし。⇒ ログ出力するよう対応しました。

failed_jobsテーブル

キュー1 id connection queue payload exception failed_at
1 database default {"displayName":"App\Jobs\PostNoticeJob","job":"I... Illuminate\Queue\MaxAttemptsExceededException: App... 2021-07-09 17:45:38

コマンドで確認

$ php artisan queue:failed
+----+------------+---------+------------------------+---------------------+
| ID | Connection | Queue   | Class                  | Failed At           |
+----+------------+---------+------------------------+---------------------+
| 1  | database   | default | App\Jobs\PostNoticeJob | 2021-07-09 17:45:38 |
+----+------------+---------+------------------------+---------------------+

jobsテーブル

キュー2 id queue payload attempts reserved_at available_at created_at
25 default {"displayName":"App\Jobs\PostNoticeJob","job":"I... 0 NULL 1625820318 1625820318

失敗jobのリトライ

リトライコマンドを実行したら、failed_jobsテーブル⇒jobsテーブルに移動しました。

リトライコマンド

$ php artisan queue:retry 1
The failed job [1] has been pushed back onto the queue!

failed_jobsテーブル⇒空

jobsテーブル

(id=25) キュー2 (id=26) キュー1 ←失敗job  の順

id queue payload attempts reserved_at available_at created_at
25 default {"displayName":"App\Jobs\PostNoticeJob","job":"I... 0 NULL 1625820318 1625820318
26 default {"displayName":"App\Jobs\PostNoticeJob","job":"I... 0 NULL 1625821206 1625821206

キューワーカ実行(全てのJob実行したら自動停止)

$ php artisan queue:work --stop-when-empty --timeout=3600
[2021-07-09 18:07:17][25] Processing: App\Jobs\PostNoticeJob
[2021-07-09 18:07:17][25] Processed:  App\Jobs\PostNoticeJob
[2021-07-09 18:07:17][26] Processing: App\Jobs\PostNoticeJob
[2021-07-09 18:07:17][26] Processed:  App\Jobs\PostNoticeJob

これで失敗Jobが再実行されました。

akagane99 commented 3 years ago

キューのguiは必要かなぁ?要検討 こんなんあった。

【Laravel】データベースキューを GUI で監視するためのライブラリ Laravel-Queue-Monitor の紹介 https://cpoint-lab.co.jp/article/202106/20419/

laravel6 も対応してそう。 https://github.com/romanzipp/Laravel-Queue-Monitor/blob/master/composer.json#L17

ルーティングは/manage/jobs/ に変更して別ウィンドウ表示とかかなぁ。 https://github.com/romanzipp/Laravel-Queue-Monitor#routes

masaton0216 commented 3 years ago

デバッグの初動調査が早くなりそうですしアリかと思います。

akagane99 commented 3 years ago

サンキュー。月曜にでも試し実装してみる

akagane99 commented 3 years ago

・QUEUE_CONNECTION=sync もどす。.env ・キューのguiは入れない。

akagane99 commented 3 years ago

対応しました。

概要

・キューをセット後に非同期でキューワーカを実行します。 ・キューワーカは、キューされたすべてのジョブを実行後に、プロセス停止します。

【利点】 ⇒ キューワーカが必要な時だけ非同期実行して、実行終わったら停止してもらう(キューワーカを常駐させない) ⇒⇒ これによって、キューワーカのcronでの実行不要、アプリケーション変更時のキューワーカのリスタート不要になります。

※ キューのメールJobは自動リトライしません。  (大量メール送信時の途中停止を想定して、自動リトライしない設定にしました。自動リトライすると初めの方に送った人に同じメールが飛ぶため) ※ キューワーカのタイムアウトは1時間に設定。  (非同期実行で投げっぱなしのため、万が一プロセルが動きつづけても1時間で強制停止します。1時間もかかるメール処理はないだろう想定。) ※ キューワーカのリトライ時間を1時間1分に設定(config\queue.php)  (タイムアウト前にリトライ時間がくるとエラーが出るため。タイムアウトよりも後の時間に設定。)  (config\queue.php の connections => [database => [retry_after]]

・メールの「送信方式」は削除して1本化。  ・.envの設定で「即時送信」か「スケジュール送信」を切り替えられる事がわかったため。   ・.envのQUEUE_CONNECTION=sync は「即時送信」   ・.envのQUEUE_CONNECTION=databaseは「スケジュール(バックグラウンド)送信」   参考:メールの「送信方式」 124560714-14b85a80-de78-11eb-8977-e8671f10d07b

要設定変更:キュードライバを database に変更する。

.env

###QUEUE_CONNECTION=sync
QUEUE_CONNECTION=database

※ 以下は既に対応済み

※ キューは2重起動するのか動作検証

https://github.com/opensource-workshop/connect-cms/issues/905#issuecomment-876994341

修正後画面

メール設定「送信方式」がなくなりました。

image

修正プログラム

https://github.com/opensource-workshop/connect-cms/pull/925/files

akagane99 commented 2 years ago

https://github.com/opensource-workshop/connect-cms/issues/905#issuecomment-877034070

キューワーカのテスト前提メモ

詳細:windows ではタイムアウトが機能しない