📗

Allowed memory size of *** bytes exhaustedエラーが出た!?

2024/03/22に公開

■ はじめに

こんにちは。エンジニアの西崎です。

最近、大規模なデータの一括操作でメモリ上限に達する問題あり、そちらの解決策について調べたので、記事にまとめていきたいと思います。

Laravel を使用して大規模なデータセットを処理する際に、メモリ上限に達する問題を解決するための効果的な方法があります。この記事では、それぞれの方法を実際のサンプルコードとともに紹介します。


■1. Eager Loading を使用する

関連データを取得する際、Eager Loading を使用することで一度にまとめて取得するようクエリを最適化します。

Eager Loading の最適化にはwith()またはload()を使用することで、一度に必要な関連データを取得するためメモリ使用量が削減されます。

Before

//
$posts = Post::get();

foreach ($posts as $post) {
    $comments = $post->comments()->get(); // ここで毎回クエリされる(N+1)
    // 処理
}

After

// with()
$posts = Post::with('comments')->get();
foreach ($posts as $post) {
    $comments = $post->comments; // 既にロードされているため追加のクエリは不要
    // 処理
}

// load()
$posts = Post::get();
$posts->load('comments');
foreach ($posts as $post) {
    $comments = $post->comments; // 既にロードされているため追加のクエリは不要
    // 処理
}

■2. 必要なカラムのみを指定

不要なカラムを取得せず、必要なカラムのみを指定して取得します。個人的に普段あまり意識していなかったですが、関連データが大量に存在する場合、お手軽なのに一定の効果を発揮します。

Before

$posts = Post::all();

After

$posts = Post::select('title', 'body')->get();

■3. ページネーションの使用

ページネーションを使用することで、一度に大量のデータを処理する必要がなくなり、メモリ使用量を削減できます。

Before

$posts = Post::all();

After

$posts = Post::paginate(10);

■4. クエリのチャンク

大規模なデータの場合は一度に全てのデータを取得するとメモリ使用量が増加します。この際に、chunk()chunkById()を使用することで、メモリの使用量を効果的に制御し、メモリ上限に達する問題を解決できます。

Before

$posts = Post::all();
foreach ($posts as $post) {
    // 処理
}

After

// chunk
Post::chunk(1000, function ($posts) {
    foreach ($posts as $post) {
        // 処理
    }
});

// chunkById
Post::chunkById(1000, function ($posts) {
    foreach ($posts as $post) {
        // 処理
    }
});

chunk()chunkById()の使い分けについて

chunk()は以下の問題があるため、chunkById()が使える局面ではこちらを使うことをおすすめします。

  • 大量データを処理する場合に処理速度の問題
  • 結果をチャンクしつつデータベースレコードを更新すると、チャンク結果が意図しない変化を起こす可能性がある

■ 感想

以上、それぞれの方法を実際のサンプルコードとともに紹介しました。
どの方法も比較的簡単に取り入れられるので、Laravel を使用して大規模なデータセットを処理する際に試していただければと思います。

ソーシャルデータバンク テックブログ

Discussion