質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

Q&A

解決済

2回答

975閲覧

Cakephp2でrenderをループした際のメモリ増加を解決する方法

Riku_Sasaki

総合スコア6

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

CakePHP

CakePHPは、PHPで書かれたWebアプリケーション開発用のフレームワークです。 Ruby on Railsの考え方を多く取り入れており、Railsの高速性とPHPの機動性を兼ね備えています。 MVCやORMなどを「規約優先の考え方」で利用するため、コードを書く手間を省くことができます。 外部のライブラリに依存しないので、単体での利用が可能です。

0グッド

0クリップ

投稿2020/05/15 02:18

編集2020/05/15 05:43

現在、Cakephp2を利用したメール送信機能を実装しています。
今回CakeEmailを使用せずに独自のメソッドでメール送信機能を実装し、その中でrenderを使用してメールテンプレートをロードしています。
送信機能をループさせて複数の宛先にメールを送信しようとしていますが、大量のメールを送信するとメモリが圧縮されて処理速度が遅くなっていく現象が起きています。
具体的には、メール送信前のメモリ使用量は8.7MBですが、300通送信すると53MBに上昇しその後も上昇を続けます。
各プロセスのメモリ増加量を調べると、Controller :: renderを呼び出す部分がほとんどを占めているため、レンダリングの際にメモリを使っているのだと思いますが、render()をループすることによってメモリ使用量が増加する原因または解決策がありましたらご教授いただきたいです。

よろしくお願いします。

追記:
メール送信

php

1// 送信するメール取得 2$mails = $this->MailList->getSendMails(); 3foreach ($mails as $mail) { 4 // 送信先保存 5 if (!$this->saveTargetData($mail)) { 6 continue; 7 }; 8 $limit = 300; 9 $offset = 0; 10 while ($targets = $this->MailDeliveryTarget->getTargets($mail, $limit, $offset)) { 11 foreach ($targets as $target) { 12 // メール送信 13 $result = $this->Sys->sendMailTemplate('reserved_mail', ['input' => $input]); 14 if ($result) { 15 // 送信成功時処理 16 } else { 17 // 送信失敗時処理 18 } 19 } 20 unset($targets); 21 $offset += $limit; 22 } 23}

sendMailTemplateメソッド

php

1function sendMailTemplate($tpl, $data = array()) 2 { 3 $to = $this->getMailTo($tpl, $data); 4 $from = $this->getMailFrom($tpl, $data); 5 return $this->sendMail($to, $from, $this->getMailSubject($tpl, $data), $this->getMailBody($tpl, $data)); 6 } 7 8function getMailTo($tpl, $data) 9 { 10 return trim(convert_nl($this->_getMailTemplate($tpl."/to", $data), "")); 11 } 12// getMailFrom, getMailSubject, getMailBodyも_getMailTemplateに渡す引数以外は同じ処理 13 14function _getMailTemplate($tpl, $data) 15 { 16 if (!is_file(dirname(dirname(dirname(__FILE__))).DS."Template".DS."mail".DS.str_replace("/", DS, $tpl).".txt")) { 17 return ""; 18 } 19 20 $bk = array(); 21 foreach (array("layout", "ext") as $v) { 22 $bk[$v] = $this->Controller->$v; 23 } 24 $bk_output = $this->Controller->response->body(); 25 26 $this->Controller->layout = ""; 27 $this->Controller->ext = ".txt"; 28 $this->Controller->response->body(""); 29 30 $this->Controller->set($data); 31 $this->Controller->mail_render = true; 32 $this->Controller->render("/".ltrim($tpl, "/")); // この部分での処理速度が伸びていく 33 $out = $this->Controller->response->body(); 34 $this->Controller->mail_render = false; 35 36 $this->Controller->response->body($bk_output); 37 foreach (array("layout", "ext") as $v) { 38 $this->Controller->$v = $bk[$v]; 39 } 40 41 return $out; 42 }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nojimage

2020/05/15 04:32

コードが提示されていないので何とも言えないのですが、メールを1通だけ送信した場合のメモリ使用量はどうなっていますか? 送信のための300通分のデータはループの前に一括取得していませんか?
nojimage

2020/05/15 04:36

あと、メモリが原因で速度が低下するというのはスワップが発生しない限り無いと思うのですが、スワップが発生しているということですか?(53M程度で足りないというのは昨今のサーバースペックを考慮すると速度低下は別の原因かなと思います。
Riku_Sasaki

2020/05/15 05:37

返答遅くなりまして申し訳ありません。 まず、説明不足な部分が多く申し訳ありません。 前提として、今回は最大50000件程度のメールを配信することを想定しており、現状の300件の送信自体は目立った遅延がなく送信できております。 ただ、300件程度までは1秒間に5~7通送信できているところ、1000通ほどになってきますと3~5秒間に1通のペースで送信されるようになってしまいます。 そのため1通あたりの送信速度を落とさずにしたいというのが今回の相談です。
Riku_Sasaki

2020/05/15 05:42 編集

そのため各プロセスの処理速度を確認したところrenderを実行している部分での処理速度が徐々に伸びていることが確認できたのでレンダリング時のメモリ使用量が問題なのでは?と考えました。 そのためメモリ使用量が原因ではない可能性も十分あります。(スワップの発生は確認方法が分からず確認できておりません) 質問に対しての回答としましては メールを1通だけ送信した場合のメモリ使用量:9.9MB 送信のための300通分のデータはループの前に一括取得しているか:しています。ただ上記説明の通り300通程度の送信は問題ありません。現状取得した送信先データを300通ごと取得して送信する処理をしております。 なお、コードを追記しましたので参考いただけるとありがたいです。
guest

回答2

0

ベストアンサー

コードを大きく変えたくないのであれば、 _getMailTemplate の中で、ローカル変数としてControllerインスタンスの生成を毎回するようにしてみたらどうでしょうか。

そうすると、インスタンス生成のコストは発生しますが、 _getMailTemplate を抜ける時にインスタンスが開放されるのでメモリリークは抑えられると思います。

また、ClassRegistryがモデルオブジェクト等を保持しているので、ループ内で毎回 ClassRegistry::flush を実行し、モデルオブジェクトを開放しておくとよいかもしれません。

投稿2020/05/15 06:49

nojimage

総合スコア959

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Riku_Sasaki

2020/05/15 11:07

その通りで現状のコードはかなり多くの部分に紐づいているためあまり変更を加えたくないのです。。。 具体的な対策を提示いただきありがとうございます!! 今日は試す時間がありませんでしたので現状では結果はお伝えできませんが、結果分かり次第お伝えさせていただきます。
guest

0

コードが提示されていないので、一般論として回答します。

大量のメール送信を自前で行い、メール文のレンダリング等にコストがかかるのであれば、1件または許容範囲の件数ごとにメール送信を行えるように、ジョブキューの仕組みを導入するとよいでしょう。

メインの処理では、処理対象となるidなど必要最低限の情報のみ抽出してジョブキューに登録します。ジョブワーカー側では、そのキー情報を元に送信に必要な情報を取得し、メールを生成して送るようにします。

CakePHP 2で使えるジョブキュープラグインは以下にありますので、案件にあったものを選択してください。
(dereuromark/cakephp-queueがコアメンバーの作ったプラグインなのでお勧めしておきます。

Queue | FriendsOfCake/awesome-cakephp at cake2

また、ループの中でオブジェクトインスタンスの生成を行っているのであれば、インスタンスを使用しなくなった時点で unset しておくとメモリを開放できます。

他、Controller::renderをメール内容を生成するために使用しているのでしょうが、Viewクラスのみを使用した方が無駄が少なくメモリを節約できます。CakeEmailもそのような実装になっています。

投稿2020/05/15 05:13

nojimage

総合スコア959

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問