cakephp3でのメモリ節約

2018年のAdventCalendar 12日目

 

ユーザーデータのエクスポート機能を開発していた時のことです。

とあるユーザーデータのエクスポート機能を開発していた際、

エクスポートを実行すると

Fatal error: Allowed memory size of 134217728 bytes exhausted

上記のようにメモリが不足してるよ!というエラーが発生した。

php.iniで設定していたmemory_limitの値が128Mとなっていたため引き起った現象のためmemory_limitの値を512Mまで引き上げて改めてエクスポートしてみると改善された。

ここまでであれば設定ミスってたね、どんまい。で済むのだが

後日ユーザーの件数を増やしたところ同じ問題が発生した。

memory_limitの値を1Gまで引き上げてみたのだが足らず用意していた開発環境の持つ全メモリ2Gを設定してようやくエクスポートできた。

開発環境のスペックが高くないとはいえ予想より少し悪い結果だったのでプログラムの作りを見直し、少しメモリを節約する方向で考えてみた。

 

エクスポートに関するサンプル処理は以下の通り。

// CSV出力用データ取得
$data = $this->_getData();

// CSV出力用に整形
$contents = $this->_format($data);

// CSVダウンロード
$this->_download($contents);

 メモリ使用量を計測した感じだとほとんどデータ取得と整形の処理が多く使用していた。

_getDataの中身は以下

$data = $this->SampleUsers
->find()
->select([
'user_name' => 'SampleUsers.name',
'age' => 'SampleUsers.age',
'sex' => 'SampleUsers.sex',
'group_name' => 'SampleGroups.group_name',
])
->contain(['SampleGroups'])
->order(['SampleUsers.id' => 'asc'])
->toArray();
return $data;

 

SampleUsersテーブルとSampleGroupsテーブルをjoinしてデータ取得を行っている。

SampleUsersテーブルに入っているレコード分出力する想定。

 

_formatの中身は以下

$contents = [];
foreach ($data as $user) {
$row = [
$user->user_name,
$user->age,
$user->sex,
$user->group_name,
];
$contents[] = $row;
}
mb_convert_variables(
'SJIS-win',
'UTF-8',
$contents
);
return $contents;

オブジェクト型のデータを1回配列に詰め替えている...

 

300000件エクスポートした際の使用しているメモリ量を確認した。

 

結果は910.03Mも使っていた。

 

cakephp3ではDBより値を取得するfindを使用するとオブジェクト型で取得する。

そのため無駄な詰め替え処理が入りメモリを浪費していた。

今度はfindしないパターンにしてみた。

 

修正後の_getDataの中身は以下

$sql = "SELECT `sample_users`.`name` AS 'user_name', "
. "`sample_users`.`age` AS 'age', "
. "`sample_users`.`sex` AS 'sex', "
. "`sample_groups`.`group_name` AS 'group_name' "
. "FROM `sample_users` "
. "INNER JOIN `sample_groups` ON (`sample_groups`.`id`=`sample_users`.`sample_group_id`) "
. "ORDER BY `sample_users`.`id` ASC";
$connection = ConnectionManager::get('default');
$data = $connection->execute($sql)->fetchAll('assoc');

return $data;

SQLを直接流す方式に変更。

取得するデータの形式を配列へ。

 

_formatの中身は以下

mb_convert_variables(
'SJIS-win',
'UTF-8',
$data
);
return $data;

DBより取得されるデータは配列なので詰め替え処理を削除

 

300000件エクスポートした際の使用しているメモリ量を確認した。

結果は667.13Mとなっていた。

 

この時点で約2/3近く使用率が落ちた。

しかも心なしかエクスポートの時間が短いように感じたので試しに計測してみる。

 

修正前

35.8177380562秒

修正後

16.4849538803秒

心なしかではなく圧倒的に早くなっていた。

 

少しの見直しで大幅な改善ができる可能性があることを学びました。

この後、ユーザー情報が増えたとしてもメモリの使用率の上昇を抑えるように改修下のですがそれはまた別の機会で。

 

以上