タイトルで若干ネタバレしてますが、ほんとしょうもない話です。ただ、おそらく同様のことで悩まれている方が何人かいるようなので、記事にしてみます。最後の方にちょっとした知見もありますが、ほぼ読み物です。
ちなみにSQSキューワーカーでメモリリークが起きるという記事がQiitaにありますが、今回の記事はこの件と直接関係はありません。
参考)
https://qiita.com/sh-ogawa/items/d64cafce2a2646b7abc6
単純な処理でなぜか延々と膨れ上がるメモリ使用量
Laravel 6.xでデータベース(今回はMySQLですがこの話に関係はない)に数万行のデータをインポートしようと試みたある日のことでした。CSVファイルから1000行ずつデータを読み出して1行ずつインサートするという手抜き超簡単なプログラムです(Laravel Excelを使用)。インポートプログラムが完成して数万行あるCSVファイルを食わせてバッチを実行したところ、数千行まで処理が走った瞬間に
PHP Fatal error: Allowed memory size of xxx bytes exhausted.
「ああん? いやいや、たかが数千行でメモリ食い潰すほどのことはなかろう。てか、1000行ずつデータとってBULK INSERTすらもせずゆっくりインサートしてるのにどこでこんなにメモリなんか使ってるんだ??? ははーん・・・・、これはなんかメモリリーク的なことが起きてるのでは????」
そもそもPHPでは意図的にメモリを確保したり解放したりなんてまずしませんし、メモリリーク的なことが起きるとしても循環参照が起きるなどでかなりレアケースではありますが…。以下を参考。
【PHP7.4新機能】弱参照(WeakReference)とGCとメモリリークについて整理したよ!
https://qiita.com/miracle-FJSW/items/f35c3e90a5d14eb6eba3
なんて変に勘ぐってしまったのが事の始まりでした。
プロセスのメモリ使用量を監視してみると、確かに徐々にメモリ使用量が膨れ上がり、1分間くらいで数十MBずつくらい物理メモリ(RES)が盛り盛りと増えていくのです。これは何事か!?
4000行くらい入れただけで436MBも使っちゃってる・・・
さらっと調べてみてもどうも同様のケースは見つけられない
データを読み込んで、そのデータをDBにインサートするだけという普通のことをしているだけなので、さすがに調べたら見つかるだろうと思ったのですが、どうにも見つかりません。でてくるのは「クエリログがメモリを食い潰しているのでそれを切れ!(\DB::connection()->disableQueryLog();
)」的な記事がちらほら出てくるのですが、どうもLaravel5.5時代の話のようです。
参考
Memory leak on database insert function
https://github.com/laravel/framework/issues/30012
今はデフォルト設定でそのようなことは起きない模様。
ただ、DBのインサートをする行だけコメントアウトしてプログラムを実行すると、たしかにメモリ使用量は増えないので、Eloquentあたりになにかバグでもあってそれを踏んでいるのだろうか?なんてこの時点では思ってしまいました。
Laravel Telescope の存在に気づく
こまったなぁ、と思いつつ解決できないこと数か月・・・(一時的にphp.iniの設定をいじり、メモリがっつり増やせばデータは全件入るのでめちゃくちゃ困ってるわけではなかった)。
ある日、ぼんやりとプロジェクトファイル全体を眺めていたら、Laravel Telescopeを入れていることにふと気づきました。今までまったく使ってないのになんでこんなもん入れてるんだ? なんて思いつつ、すごく嫌な予感がしました。Laravel Telescopeをご存じでない方に、説明ページの冒頭を引用させていただきます。
Laravel TelescopeはLaravelフレームワークのエレガントなデバッグアシスタントです。Telescopeはアプリケーションへ送信されたリクエスト、例外、ログエンティティ、データクエリ、キュージョブ、メール、通知、キャッシュ操作、スケジュールされたタスク、さまざまなダンプなどを提示します。TelescopeはLaravelローカル開発環境における、素晴らしいコンパニオンです。
あー、これは危険な香りがプンプンする!!!ということで、これを無効にする設定(.envにTELESCOPE_ENABLED=false
)をいれてphp artisan config:cache
したあとにスクリプトを実行すると・・・ はいビンゴでした。
君か・・・ 数か月も見つけられなかったよ・・・
Laravel Telescope の一部機能を無効にする方法は色々ある模様
上の例では機能全体を無効にしましたが、一部機能だけ無効にすることもできます。詳しくはLaravel Telescopeのドキュメントを見ていただくといいと思いますが、今回の場合だと、Queryワッチャーをオフにすればメモリの使用量は大幅に減ります。ただ、Queryワッチャー以外でもメモリは使用しているので、ちょろちょろとメモリ使用量が増えてはいきます。
コード内の一部のみLaravel Telescopeを無効にしたい場合は
use Laravel\Telescope\Telescope;
//〜略〜
Telescope::stopRecording();
//Laravel Telescopeでメモリ食いの処理
Telescope::startRecording();
という感じで書けばメモリ問題も上手く捌けるようです。
参考)
Query builder memory leak on large insert
https://github.com/laravel/framework/issues/27539
↑
この記事はLaravel Telescopeの件で気づいた後に見つけたけど、やっぱりメモリリークって勘違いするよなぁw
結論
Laravel Telescopeはメモリ食うから気をつけろ。
そして、入れたことを忘れるな(普通は忘れないよねw)