Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
akagane99 opened this issue Jun 15, 2021 · 12 comments
Closed

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

akagane99 opened this issue Jun 15, 2021 · 12 comments
Assignees
Labels
additional request 要望・機能追加 core Connect-CMSコア関係

Comments

@akagane99
Copy link
Contributor

akagane99 commented Jun 15, 2021

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

参考URL

@akagane99 akagane99 self-assigned this Jun 15, 2021
@akagane99 akagane99 changed the title キューキック 【メール】キューキック Jun 15, 2021
@akagane99
Copy link
Contributor Author

akagane99 commented Jul 5, 2021

調査

キューテーブルを作成するコマンドを実行したところ、下記エラーが発生。
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
Copy link
Contributor Author

akagane99 commented Jul 5, 2021

調査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

利点

  • レンサバでcronが使えれば、レンサバでも利用できる。

欠点

  • connect毎に、cron設定(php artisan schedule:run コマンド)の1行追加が必要。

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

微妙だったので廃案

\Symfony\Component\Process\Process を使ってコマンドの非同期実行をしているが、タイムアウト指定をしていないため、
デフォルト60秒でプロセス停止されそう。

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

利点

  • キューワーカーを立てずに非同期できる。
  • レンサバで使える。
  • connect毎にcron設定が不要になる。

欠点

  • laravel-async-queueライブラリが、Laravel5系対応。今後のLaravel6系の対応が不明。(githubでは2か月前にリリースはあったので生きてるプロジェクトの模様)
  • リトライしない。

案1:Supervisor

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

利点

欠点

  • サーバへSupervisorをインストールする必要があるため、レンタルサーバの使用はほぼ無理だろう。
  • Supervisorはpython製のため、別途pythonのインストールも必要

要確認

  • laravel-async-queueライブラリはLaravel 5系のライブラリだけど6系でも使える?要確認
    ⇒ 動いた。
  • 1サーバに複数Connect-CMSがあった場合、Connect-CMSとqueue:workは1対1になる必要がありそう。
    ⇒ Connect-CMSとqueue:workの繋がりは、実行したartisanコマンドでConnect-CMS(Laravel)を特定できるので、それでつながっているだろう。
  • キュードライバ:sync(デフォルト)のままなら、キューに保存しても同期で動作しそう(要動作確認)
    ⇒ 同期(即時送信)しました。

メモ

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

キューワーカとデプロイ

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

php artisan queue:restart

dispatchNowメソッド

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

@akagane99
Copy link
Contributor Author

akagane99 commented Jul 7, 2021

追加調査・対応メモ

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の各種デフォルト値を確認【済】

  • timeout :タイムアウト
    子プロセスが実行できる秒数 --timeout[=TIMEOUT] The number of seconds a child process can run [default: "60"]
  • tries : 最大試行回数
    ログ記録に失敗する前にジョブを試行する回数 --tries[=TRIES] Number of times to attempt a job before logging it failed [default: "1"]
  • sleep:スリープ(秒)
    使用可能なジョブがない場合にスリープする秒数 --sleep[=SLEEP] Number of seconds to sleep when no job is available [default: "3"]
  • ディレイ
    失敗したジョブを遅延させる秒数 --delay[=DELAY] The number of seconds to delay failed jobs [default: "0"]
  • メモリ(単位:MB)
    メガバイト単位のメモリ制限 --memory[=MEMORY] The memory limit in megabytes [default: "128"]

コマンド例

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重起動時の問題検証【済】

#925 (comment)

調査結果
#905 (comment)

プログラム修正【済】

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

参考画面
image

@akagane99
Copy link
Contributor Author

akagane99 commented Jul 9, 2021

再調査

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

symfony/process@4.4...5.2

@akagane99
Copy link
Contributor Author

akagane99 commented Jul 9, 2021

2重起動時の問題検証

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

結果、問題なしでした。

詳細

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

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

  • キューワーカBでキュー1は重複処理されない。⇒ 問題なし
  • databaseキューの処理は、attempts reserved_at で管理されてそう。
    • 未処理キュー:attempts=0, reserved_at=null
    • 処理中キュー:attempts=1, reserved_at=1625816xxxx ←多分時間のint

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が起動した場合

  • キュー1をキューワーカAが処理続行
  • キュー2をキューワーカBが処理開始

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

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

  • 後に起動したキューワーカBで、キュー1が処理される。
  • キューワーカAは何もしない。

⇒ 問題なし。

@akagane99
Copy link
Contributor Author

akagane99 commented Jul 9, 2021

追加調査

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
Copy link
Contributor Author

akagane99 commented Jul 11, 2021

キューの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
Copy link
Contributor

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

@akagane99
Copy link
Contributor Author

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

@akagane99
Copy link
Contributor Author

akagane99 commented Jul 12, 2021

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

@akagane99
Copy link
Contributor Author

akagane99 commented Jul 12, 2021

対応しました。

概要

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

【利点】
⇒ キューワーカが必要な時だけ非同期実行して、実行終わったら停止してもらう(キューワーカを常駐させない)
⇒⇒ これによって、キューワーカの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重起動するのか動作検証

#905 (comment)

修正後画面

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

image

修正プログラム

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

@akagane99
Copy link
Contributor Author

akagane99 commented Sep 1, 2022

#905 (comment)

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

  • Linuxでテストする。
    • ※ windows ではタイムアウトが機能しない
  • pcntl PHP extensionをインストールする。
    • ※ ないとタイムアウト機能しない

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

Note: The timeout feature is optimized for PHP 7.1+ and the pcntl PHP extension. (pcntl のphpエクステンション必要)

現在、このモジュールは非 Unix 環境(Windows)では動作しません。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
additional request 要望・機能追加 core Connect-CMSコア関係
Projects
None yet
Development

No branches or pull requests

2 participants