SPIDERPLUS Tech Blog

建設SaaS「スパイダープラス」のエンジニアとデザイナーのブログ

PHPのメモリ超過問題を調査した件

はじめに

皆さん、こんにちは。

技術開発部のWです。
普段はWebエンジニアとしてフロントエンドやサーバーサイドの開発に携わっています。

最近、PHPのアプリケーションでメモリ超過エラーが発生する事象に遭遇しました。
このエラーは、Webサーバーがメモリを使い切ってしまい、処理が停止されてしまう現象です。
この記事では、PHPのメモリ超過問題の調査についてまとめます。

問題の概要

当社のプロダクトにてエクセル出力時に多くのセルへの出力を行う場合の調査時に予期しないメモリ使用量を消費し、最終的に「Allowed memory size of X bytes exhausted」というエラーが表示されました。

この問題が発生すると、処理が完了せず、システム全体のパフォーマンスが低下することがあります。

発生したエラー

PHP Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 20480 bytes) in /path/to/script.php on line 123

原因の仮説

1.データの読み込み方法の可能性

データベースから一度に大量のレコードを取得している処理にてメモリを消費している可能性があります。

2.不要なオブジェクトが残っている可能性

処理が終了した後に不要なオブジェクトや配列がメモリに残っていることが、メモリの無駄遣いにつながっている可能性があります。

3.メモリ制限設定の可能性

PHPmemory_limit設定が原因である可能性もありますが、これについては根本的な解決策にはならないと考えました。

調査

1. PHP設定の確認

まず、PHPの設定を確認しました。PHPのメモリ制限 (memory_limit) はphp.iniで設定できます。

確認方法:

php -i | grep memory_limit

2. メモリ使用量の確認

ログ出力

計測用のログを仕込み、特にメモリ使用量が増加している処理を特定しました。また、パフォーマンスへの影響を調査するため、CPU使用率も計測して出力しています。

private function startLog($startMemory)
{
    $loadAvg = sys_getloadavg(); // 1分、5分、15分平均のCPU負荷
    $endMemory = memory_get_usage();
    $memoryUsed = $endMemory - $startMemory;
    $memoryUsedMB = number_format($memoryUsed / 1024 / 1024, 2) . ' MB'; // メモリ使用量(MB単位)
    Log::info('start', [
        'memory_used' => $memoryUsedMB, // メモリ使用量
        'memory_usage' => number_format($endMemory / 1024 / 1024, 2) . ' MB', // 終了時のメモリ使用量(MB単位)
        'cpu_load_1m' => number_format($loadAvg[0], 2) . '%', // 1分間の平均CPU負荷
        'cpu_load_5m' => number_format($loadAvg[1], 2) . '%', // 5分間の平均CPU負荷
        'cpu_load_15m' => number_format($loadAvg[2], 2) . '%', // 15分間の平均CPU負荷
    ]);
}

private function endLog($iniStartTime, $iniStartMemory)
{
    // リクエスト終了時間を計測
    $endTime = hrtime(true);
    $duration = $endTime - $iniStartTime;
    $duration_seconds = number_format($duration / 1e9, 6);
    $tmpSeconds = $duration / 1e9;
    // 時間、分、秒に変換
    $hours = floor($tmpSeconds / 3600);
    $minutes = floor(($tmpSeconds % 3600) / 60);

    // 終了時のメモリ使用量を記録
    $endMemory = memory_get_usage();
    $memoryUsed = $endMemory - $iniStartMemory;
    $memoryUsedMB = number_format($memoryUsed / 1024 / 1024, 2) . ' MB'; // メモリ使用量(MB単位)

    // システムのCPU負荷を再度取得
    $loadAvg = sys_getloadavg();

    // 計測結果をログに出力
    Log::info('end', [
        'duration' => "処理時間: " . $hours . "時間" . $minutes . "分" . $duration_seconds . " 秒", // フォーマットされたリクエスト処理時間(時間:分:秒)
        'memory_used' => $memoryUsedMB, // メモリ使用量
        'memory_usage' => number_format($endMemory / 1024 / 1024, 2) . ' MB', // 終了時のメモリ使用量(MB単位)
        'cpu_load_1m' => number_format($loadAvg[0], 2) . '%', // 1分間の平均CPU負荷
        'cpu_load_5m' => number_format($loadAvg[1], 2) . '%', // 5分間の平均CPU負荷
        'cpu_load_15m' => number_format($loadAvg[2], 2) . '%', // 15分間の平均CPU負荷
    ]);
}
Laravelのログへ出力
$startTime = hrtime(true);
$startMemory = memory_get_usage();

// 計測開始
$this->startLog($startMemory);
// 特定の処理
// 計測終了
$this->endLog($startTime, $startMemory);

これをスクリプト内の複数の場所に挿入することで、メモリがどの時点で急激に増加しているかを追跡しました。
結果、SQLで大量のデータを一度に取得している箇所が、メモリ超過エラーの原因の大部分を占めていました。

ログ出力例
[2024-12-10 17:48:32] local.INFO: start {"memory_used":"0.00 MB","memory_usage":"131.99 MB","cpu_load_1m":"5.26%","cpu_load_5m":"5.56%","cpu_load_15m":"5.12%"} 

[2024-12-10 17:49:52] local.INFO: end {"duration":"処理時間: 0時間1分79.795247 秒","memory_used":"54.56 MB","memory_usage":"186.55 MB","cpu_load_1m":"5.38%","cpu_load_5m":"5.69%","cpu_load_15m":"5.20%"} 

対処例

1. データの読み込み方法の改善

データベースから一度に大量のレコードを取得するのではなく、少しずつデータを取得して処理する方法に変更します。これにより、一度にメモリに読み込むデータ量を減らし、メモリ消費を抑制できます。具体的には、LIMIT句を使ってSQLクエリで一度に取得するレコード数を制限します。

$offset = (int) $offset; // 整数にキャストして不正な値を防ぐ
$query = "SELECT * FROM users LIMIT 1000 OFFSET :offset";
$results = DB::select($query, ['offset' => $offset]);

2. 不要なオブジェクトの破棄

処理が終わった後に不要なオブジェクトや配列をunset()で明示的に破棄し、メモリを解放します。

unset($largeObject);

3. メモリ制限設定の変更

根本的な解決にならないため行わない予定ですが、php.inimemory_limitを一時的に大きく設定し、メモリ超過エラーが発生しないようにします。

memory_limit = 512M

まとめ

今回の調査により、一部のSQLで大量のデータを一度に取得している箇所が、メモリ超過エラーの原因の大部分を占めることが判明したため、データの読み込み方法の改善に取り組むことになりました。

最初にデータ取得方法を見直し、その後メモリ管理の最適化を進める予定です。現在、解決策の実施にはまだ至っていませんが、これらの改善策を順次実行し、システムのパフォーマンス向上を目指して作業を進めていきます。

ところで、スパイダープラスでは仲間を募集中です。 スパイダープラスにちょっと興味が出てきたなという方がいらっしゃったらお気軽にご連絡ください。

AWS re:Invent2024参加レポート!紫の一週間

あけましておめでとうございます!

プラットフォーム開発部のわにわに🐊です。

2024年3月から始めたスパイダープラス技術ブログの新年初記事は2024年12月2日〜5日にラスベガスで開催されたAWS re:Invent 2024の熱気あふれる様子をお送りいたします!

reinvent.awsevents.com

初めてのアメリカ、そしてラスベガス

今回のre:Inventが私にとって初のアメリカ訪問です。

続きを読む

技術ブログを始めてみた!1年目の軌跡

年末の挨拶

皆様こんにちは!技術ブログ運営チームのテトリス本因坊です。

2024年3月6日の初記事投稿以後、ほぼ毎週更新を維持してきたSPIDERPLUS Tech Blogですが、いよいよ年内最終投稿となりました。

様々な経路から当ブログにお越しになり、記事を読んでくださったすべての方への感謝の気持ちといたしまして、今回はブログ運用の裏側と、独断による自薦記事特集をお送りいたします。

続きを読む

NeovimでNvChad×Copilotを試す

1. はじめに

こんにちは、DevOpsチームの宮囿です。

 

弊社のDevOpsチームはあまりコーディングをする部署ではないんですが、必要に駆られてたまにコーディングもします。

私はVSCode+Copilotプラグインでなんとなく書いていたんですが、ターミナル上で作業を完結させたいという思いが募り、この度 Neovim※1+NvChad※2+Copilotの環境に移行することにしました。

その際、簡単に動作確認する環境を組んだのでその紹介をします。

 

※1 Neovim:vimの派生エディタ。拡張性と使いやすさが向上しているとのこと。

※2 NvChad:Neovimの使いやすい設定セットのようなもの。本田さんに教えてもらいました。

2. 対象読者

  • VSCodeちょっと飽きちゃってNeovimに興味が出てきている方
  • サーバー作業ではVim使ってるけどコーディングの為の設定を詰めるのが面倒でコーディング時にはVSCodeを使ってる方(私) 続きを読む

Reactコンポーネント以外のロジックコードのHMR化

はじめに

プラットフォーム開発部のわにわに🐊です。

今回は、前回の記事の続きで、Reactコンポーネント以外のロジックのjsをHMR対応した時のことを書きます。

techblog.spiderplus.co.jp

 

ReactコンポーネントはViteのプラグイン(@vitejs/plugin-react)を使うことでHMR対応できますが、そのままではその他のjsファイルはHMR対応とならず、コードを変更したときにページ全体リロードが発生してしまいます。

なので、Reactコンポーネント以外のロジックコードをHMR対応できると便利です。

続きを読む

エンジニアリングマネージャー1年目の開発チーム運営思想

はじめに

こんにちは!スパイダープラスでエンジニアリングマネージャーを務めている本田です。

私は入社して3年目、エンジニアリングマネージャーとしては1年目になります。
これまで約10年間ソフトウェアエンジニアとして働いてきましたが、開発リーダー的なポジションの経験はあるものの、マネージャーとして明確に自分のチームを持つのは弊社が初めてでした。

マネージメント職にチャレンジした理由は、エンジニアとして培った「こうすればもっと良くなる」というアイデアを、弊社の持つ環境でなら実現できると確信したからです。

この記事を書くことにした動機は、長い間マネージメント職を敬遠していた私が、今ではとても楽しくマネージャーとして仕事をしていることが挙げられます。

本記事のテーマは私が開発チームの運営をする上で大切にしている価値観や考え方です。
もし、この記事を通じてエンジニアのマネージメント職に対する新たな視点や魅力を感じてもらえたら嬉しいです。

続きを読む

アプリのログを直接Google Driveに格納して省力化した件

はじめに

システム運用部の🐧です。

システム運用部は名前の通りサービスの運用に関するインフラ寄りのあれこれを請け負うチームなのですが、部署間の情報の受け渡しや、業務の効率化に伴うツールの作成等を行うこともあり、SRE的なお仕事も行っております。

経緯

続きを読む