[Laravel]CSVデータの一括登録処理を実装したらエラー「Allowed memory size of 104857600 bytes exhausted」が発生した

[Laravel]CSVデータの一括登録処理を実装したらエラー「Allowed memory size of 104857600 bytes exhausted」が発生した
2021年05月10日2023年10月10日

Laravelで管理画面からCSVアップロードで一括登録する機能を開発することになりメモリ不足のエラー「Allowed memory size of 104857600 bytes exhausted」と格闘したので実装方法をまとめます。

13万件のデータ(12MB)を一括登録

今回一括登録したデータは13万件分、サイズは12MBとけっこう大きいCSVデータになります。
(郵便番号のデータです)

このデータを1件ずつ登録すると、かなり時間がかかるので1,000件ずつCSVデータを読み込んで一括登録するようにしました。

$fp = fopen($request->file('file')->getPathname(), 'r');
$dataList = [];
while (($row = fgetcsv($fp)) !== FALSE) {

    $dataList[] = [
        'jis_code' => $row[0],
        'postal5' => $row[1],
        'postal7' => $row[2],
        'created_at' => Carbon::now()->format('Y-m-d H:i:s'),
        'updated_at' => Carbon::now()->format('Y-m-d H:i:s')
    ];

    // 1,000件になったら一括登録
    if (1000 <= count($dataList)) {

        $this->bulkImport($dataList);

        $dataList = [];
    }
}
// 1,000件未満の場合があるので一括登録
if ($dataList) {
    $this->bulkImport($dataList);
}

// 一括登録処理
private function bulkImport($dataList)
{
    (new TblMPostalAddress())->insert($dataList);
}

発行SQLも1件ずつではなく、下記のように一括Insert文を発行してくれます。

insert into table-name (a,b,c) values (?,?,?),(?,?,?)

後は実行するだけ!

なぜかメモリ不足エラー

いざCSVアップロードを実行するとメモリ不足エラーが発生しました。

Symfony \ Component \ Debug \ Exception \ FatalErrorException (E_UNKNOWN)
Allowed memory size of 104857600 bytes exhausted (tried to allocate 409600 bytes)

メモリ不足にならないように、

  • CSVデータは1件ずつ読み込む
  • Insertする時は1,000件ずつおこなう

を注意して実装したにもかかわらずエラーが発生しました。

メモリ使用状況のログを入れてみる

ループ分の中にメモリ使用状況のログを仕込んでみました。

    private function bulkImport($dataList)
    {
        $this->memorylog();
        (new TblMPostalAddress())->insert($dataList);
    }

    private function memorylog()
    {
        $mem = number_format(memory_get_usage());
        $peakmem = number_format(memory_get_peak_usage());
        print("Memory:{$mem} / Peak Memory:{$peakmem}");
        print "\n";
    }

再度実行してみると、確かに1,000件ずつループするたびに使用メモリが増えている・・・

Memory:7,529,440 / Peak Memory:21,792,376
Memory:12,328,536 / Peak Memory:21,792,376
Memory:17,102,912 / Peak Memory:21,792,376
Memory:21,881,248 / Peak Memory:25,960,656
Memory:26,680,728 / Peak Memory:30,735,120
Memory:31,509,744 / Peak Memory:35,583,720
Memory:36,338,024 / Peak Memory:40,416,576
Memory:41,171,776 / Peak Memory:45,249,240
Memory:45,968,424 / Peak Memory:50,082,800
Memory:50,750,464 / Peak Memory:54,814,392

原因はデバッグ用のDebugbar

色々調べていくと原因を突き止めることができました。

原因はデバッグ用のためにインストールしてあったDebugbarでした。

たしかにSQLは大量に発行されるし実行が終わるまでプールしているのでループするたびにメモリが増えるわな!って感じでした。

CSVアップロード時はDebugbarをOFFにする

原因がわかったので対処します。Debugbarが動作しないようにconfigディレクトリ配下に「debugbar.php」を作成しCSVアップロードのパスを追加します。

config/debugbar.php
<?php
return [
    'except' => [
        '/postal/csv-import'
    ]
];
configディレクトリ配下にdebugbar.phpファイルを作成しexceptにパスを指定すると、exceptに追加したパスはdebugbarを動作しないように設定することができます。

設定ファイルの追加が完了したら再度実行してみます。

Memory:6,812,360 / Peak Memory:21,078,752
Memory:6,795,800 / Peak Memory:21,078,752
Memory:6,794,968 / Peak Memory:21,078,752
Memory:6,794,352 / Peak Memory:21,078,752
Memory:6,818,752 / Peak Memory:21,078,752
Memory:6,823,568 / Peak Memory:21,078,752
Memory:6,823,808 / Peak Memory:21,078,752
Memory:6,825,136 / Peak Memory:21,078,752
Memory:6,789,552 / Peak Memory:21,078,752
Memory:6,804,416 / Peak Memory:21,078,752
Memory:6,803,552 / Peak Memory:21,078,752
Memory:6,787,320 / Peak Memory:21,078,752
Memory:6,809,784 / Peak Memory:21,078,752
Memory:6,795,936 / Peak Memory:21,078,752
Memory:6,795,832 / Peak Memory:21,078,752
Memory:6,808,320 / Peak Memory:21,078,752
Memory:6,791,432 / Peak Memory:21,078,752

今度は、使用メモリが増えずにループ処理することができました。

さいごに

この問題を解決するために半日かかってしまいました。

なぜdebugbarが原因と判明したかというと、ローカルではエラー、開発サーバーではエラーにならず一括登録ができました。

開発サーバーはデバッグモードをOFFにしていたためdebugbarが動作しない状況でした。

そこからデバッグモードのみにこの現象が発生することがわかりdebugbarが原因ということがわかりました。

コメント

コメントを残す

お名前(任意)
コメント:新規