🦁

[Laravel]Bulk Insertしたときにメモリエラーが起こる原因と対処法

2024/10/29に公開

先に解決策

Bulk Insertに限らず、クエリ実行時にQueryExecutedイベントが発行されていることでメモリ消費が増大してしまいます。これを解決するために、unsetEventDispatcherを利用してイベントの発行を停止させます。

\DB::connection()->unsetEventDispatcher();
Model::insert($data);

概要

CSVを使って、万単位のレコードを一括挿入する必要がありました。
1行ずつ保存すると処理時間が増大してしまうため、
処理時間の短縮およびメモリ消費対策としてBulk Insertを使用しましたが、実行時にメモリエラーが発生しました。

調査した点

1. トランザクション内でのメモリ保持

トランザクションを使用しているため、メモリ不足が原因かと考えました。
しかし、トランザクションを外しても同じエラーが発生したため、
トランザクションによるメモリ問題は除外しました。

2. 大量のデータを変数に保持しているか

大量のデータを変数に保持しすぎているのかと疑いましたが、
データはファイルに保持されており、メモリにはそれほど影響がないことが確認できました。

3. スクリプトのGC(ガベージコレクション)が意図したタイミングで動いていない

保存処理の前後でガベージコレクションが動作していないのではないかと考え、gc_collect_cycles()を試しましたが、メモリ消費量に変化はありませんでした。

解決に至る

Model::insert($data)の前後でメモリ消費量を確認したところ、この時点でメモリ消費が急増していることが判明しました。
ただ、何故insert()でメモリ消費が増えるのか理解できずにいたところ、以下のGitHubのissueを発見しました。
https://github.com/laravel/framework/issues/27539#issuecomment-646112782
このissueによると、クエリの実行タイミングでQueryExecutedイベントが発行されており、
Bulk Insertの場合、インサートする全てのレコードに対してイベントログが溜まってしまうことが原因であることが分かりました。

この問題を解決するために、unsetEventDispatcherを使用してイベントを無効化し、メモリの消費を抑えることができます。

\DB::connection()->unsetEventDispatcher();
Model::insert($data);

これにより、Bulk Insert時のメモリエラーを回避することができました。

Discussion