1
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?

Laravelでメモリ管理を徹底解説!チャンク・カーソル・N+1問題の解決策

Posted at

前回はPCの5大装置としてのメモリ管理を学びました。
引き続きPHP・Laravelのプログラム具体例を参考にメモリについて学びたいと思います。

memory_get_usage()関数を使ってメモリを測る

$startMemory = memory_get_usage();

// 処理を実行

$endMemory = memory_get_usage();
$usedMemory = $endMemory - $startMemory;

// バイト → メガバイトへの変換
echo "使用メモリ: " . round($usedMemory / 1024 / 1024, 2) . " MB";

コントローラーでの実践的なメモリ計測

リクエスト単位でのメモリ計測は、コントローラーのビューレンダリング前に行います。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Log;

class UserController extends Controller
{
    public function index()
    {
        // 処理開始時間とメモリ使用量を記録
        $startTime = microtime(true);
        $startMemory = memory_get_usage();
        
        // 1. データの取得や処理
        $users = User::with('posts')->get();
        $stats = $this->calculateUserStats($users);
        
        // 2. ビューに渡すデータの準備
        $viewData = [
            'users' => $users,
            'stats' => $stats,
            'title' => 'ユーザー一覧'
        ];
        
        // 3. ビューレンダリング前の計測
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        
        // 処理時間とメモリ使用量を計算
        $executionTime = round(($endTime - $startTime) * 1000, 2); // ミリ秒単位
        $usedMemory = $endMemory - $startMemory;
        
        Log::info('ユーザー一覧のパフォーマンス', [
            'execution_time' => $executionTime . ' ms',
            'memory_usage' => round($usedMemory / 1024 / 1024, 2) . ' MB',
            'peak_memory' => round(memory_get_peak_usage() / 1024 / 1024, 2) . ' MB',
        ]);
        
        // 4. ビューのレンダリング
        return view('users.index', $viewData);
    }
}

処理単位のメモリ使用量・処理時間目安

処理内容 推奨ピークメモリ 推奨処理時間 注意点
単純なページ表示 5-15MB 50-150ms Bladeテンプレート、軽いクエリ
CRUD処理 10-30MB 100-300ms リレーション、Eager Loading
ファイルアップロード 20-50MB 200-500ms ファイルサイズに依存
バッチ処理 30-100MB 1-5秒 チャンク処理推奨
データエクスポート 50-200MB 5-30秒 カーソル処理推奨
レポート生成 100-500MB 10-60秒 複雑な集計処理

※ 1000ms(ミリ秒)= 1秒 です。

※ メモリ使用量はピークメモリ(memory_get_peak_usage())を基準としています

メモリ消費量を考慮したコーディング

メモリの計測は分かった!
じゃあ実際にどのようにメモリ効率を考慮してコーディングしていけばいいのか?
基本的なところを見ていきたいと思います。

1. チャンク処理の活用

大量のデータを処理する際は、チャンク処理を使用してメモリ使用量を抑えることができます:

チャンク処理とは、大量のデータを小さな塊(チャンク)に分けて処理する方法です。
一度に全てのデータをメモリに読み込むのではなく、指定した数ずつ処理することで、メモリ使用量を大幅に削減できます。

// 非推奨
$users = User::all();
foreach ($users as $user) {
    // 処理
}

// 推奨:レコード1000ずつ処理していく
User::chunk(1000, function ($users) {
    foreach ($users as $user) {
        // 処理
    }
});

2. カーソルの使用

大量のレコードを処理する際は、カーソルを使用することでメモリ効率を改善できます。

カーソルとは、データベースからレコードを1つずつ取得して処理する方法です。チャンク処理と異なり、一度に1つのレコードのみをメモリに保持するため、メモリ使用量を最小限に抑えることができます。特に大量のレコードを順次処理する場合に効果的です。

foreach (User::cursor() as $user) {
    // 処理
}

チャンク vs カーソルの違い・使い分け

  • チャンク処理: 指定した件数ずつ取得 → メモリ使用量は多いが処理速度が速い
  • カーソル処理: 1件ずつ取得 → メモリ使用量は最小だが処理速度は遅い

3. クエリの最適化

取得するクエリ根本を考えることも大切です。超基本ですが、
必要なカラムのみを取得することで、メモリ使用量を削減できます:

// 非推奨
$users = User::all();

// 推奨
$users = User::select('id', 'name', 'email')->get();

N+1問題の回避

N+1問題は、メモリ使用量を大幅に増加させる原因の一つです。リレーションを含むデータを取得する際は、Eager Loadingを使用しましょう

  • Lazy Loading: リレーションにアクセスした時点でクエリ実行 → N+1問題の原因
  • Eager Loading: 事前に関連データを取得 → メモリ効率的
// 非推奨(N+1問題)
$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count(); // Lazy Loading: 各ユーザーごとにクエリが実行される
}

// 推奨(Eager Loading)
$users = User::with('posts')->get();
foreach ($users as $user) {
    echo $user->posts->count(); // 事前に取得済み(Lazy Loadingなし)
}
1
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
1
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?