2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FusicAdvent Calendar 2024

Day 6

【Laravel11】CSV書き込み処理のメモリ使用量と処理時間の検証

Last updated at Posted at 2024-12-06

はじめに

初記事なので、言葉遣いが変だったり読みにくかったらぜひご意見お待ちしてます!
今後につなげていきます!

自己紹介

初記事なのでここで自己紹介書かせていただきます!
PHP(Laravel)を書いてるエンジニアです。
最近はReactを触り始めてます。
まだLaravelを触り始めて半年のぺーぺーです。

技術記事なので自己紹介はこのぐらいで...。

本題

なぜこの検証を行ったのか

大量のデータ(1万件以上)をcsvでダウンロードするとメモリ不足に陥りました。

なぜこの記事を書いたのか

csv書き込み処理のメモリ使用量や処理時間の検証を行ってる記事が少なかったので、自分のアウトプットと、同じ悩みを抱えてる人の参考になれば良いなと思い、書くことにしました。

local.ERROR: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) 
{"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) at
/var/www/html/vendor/maximebf/debugbar/src/DebugBar/DataFormatter/HasXdebugLinks.php:76) [stacktrace] #0 {main} "} 

動作環境

  • Laravel:11.30.0
  • OS:macOS Sequoia 15.1.1

事前説明

fopenfwritefputcsvfcloseという関数がありますが、長いので全てを指すときはまとめてfシリーズと呼ばせていただきます。

計測方法

書き込みパターンの比較

ロジックを4つのパターンに分け、データ件数も4つで比べてみました。

以下の表は、4つの書き込み処理パターンをまとめたものです。

パターン 書き込みタイミング 使用メソッド
パターン1️⃣ 全てのデータを一度に書き込む fシリーズのみ
パターン2️⃣ 全てのデータを一度に書き込む fシリーズ + lazy (chunk) メソッド
パターン3️⃣ データを繰り返し書き込む fシリーズのみ
パターン4️⃣ データを繰り返し書き込む fシリーズ + lazy (chunk) メソッド

以下の表は比較したデータの件数と、その時のパターン2️⃣、4️⃣のchunk数をまとめたものです。

データ件数 chunk数
500 100,1000,10000
1000 100,1000,10000
10000 100,1000,10000
100000 100,1000,10000,100000

パターン1️⃣の例

PatternOne.php
$filePath = // ファイルパス
$file = fopen($filePath, 'w');

fwrite($file, "\xEF\xBB\xBF");

$csvData = [];
$csvData[] = [
    // ヘッダー
];

$users = User::get();
foreach ($users as $user) {
    $csvData[] = [
        // $user
    ];
}

fputcsv($file, $csvData);
fclose($file);

パターン4️⃣の例

PatternFour.php
$file = fopen($filePath, 'w');

fwrite($file, "\xEF\xBB\xBF");
fputcsv($file, [
    // ヘッダー
]);

User::chunk(1000, function ($users) use ($file) {
    foreach ($users as $user) {
        fputcsv($file, [
             // データ
        ]);
    }
});

fclose($file);

メモリ使用量や処理時間の測定

メモリはmemory_get_usage()、時間はmicrotime(true)で、それぞれの差分で出すことができます。

// メモリ測定開始
$startMemory = memory_get_usage();

// 開始時刻を記録
$startTime = microtime(true);

// ファイル書き込み処理

// メモリ使用量の記録
$endMemory = memory_get_usage();
$memoryUsed = ($endMemory - $startMemory) / (1024 * 1024);
Log::info("メモリ: {$memoryUsed} 【MB】");

// 処理時間の記録
$endTime = microtime(true);
$timeTaken = $endTime - $startTime;
Log::info("処理時間: {$timeTaken} 秒");

参考

計測結果

まとめ

  • パターン1️⃣ はシンプルですが、大量のデータを扱うとメモリ不足に陥る可能性があります。
  • パターン2️⃣lazy (chunk) メソッドを使用することでメモリ使用量を抑えつつ、一度に書き込む利便性を保持します。
  • パターン3️⃣ はデータを繰り返し処理するため、メモリ効率が向上しますが、処理時間が増加する可能性があります。
  • パターン4️⃣chunk メソッドと繰り返し処理を組み合わせることで、最もメモリ効率が良く、安定した処理が可能です。

パターン1️⃣

データが1万件を超えるとメモリー不足


パターン2️⃣

1万件超えでメモリ不足

データ1000件時のログ

image.png


パターン3️⃣

10万件超えるとメモリ不足

データ1000件

image.png

データ10000件

image.png


パターン4️⃣

4つのパターンで処理速度最速、メモリ使用量最低。
データが1万件の場合、メモリが2.30~2.54M、処理速度が0.08~0.26秒でした。

その後の変更点

php.ini
memory_limit = 1024M

変更した結果

データが1万件超えでも全てのパターン処理されるようになりました!

image.png

が...
データが10万件超えるとパターン4️⃣以外はメモリ不足になるのは変わらずです。

結果

10万件を超える場合は、パターン4️⃣の中でもchunk(10000)メソッドが処理速度が早いし、メモリ使用量も少なかったです。

image.png

パターン4️⃣でデータ40万件を試してみると、時間こそかかりましたがメモリ不足に陥ることはなかったです。

まとめ

csvファイルにデータを書き込む処理では、fopen,fwrite,fputcsv,fcloseを使いつつ、chunk(lazy)(できればchunk(lazy)ById)メソッドを使っていくのがおすすめ!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?