Eloquent パフォーマンスのコツ:数千のレコードでLaravelを飛ばす
2024-02-05 * 9分で読める min de lecture Web Development
Laravelを始めたとき、Eloquentは魔法のように感じます。数行のコードで、美しく表現力豊かな構文でデータベースをクエリできます。しかし、プロジェクトが成長し、数千(または数百万)の行を処理し始めると、その魔法は突然、遅いクエリ、メモリスパイク、遅延するページに変わることがあります。
良いニュース?LaravelのEloquent ORMは本当にうまくスケールできます - いくつかのパフォーマンスのコツを知っていれば。この記事では、Eloquentのエレガンスを放棄することなく、大規模アプリケーションを最適化するのに私と私のチームを助けたテクニックを共有します。
1. select()を使用して必要なものだけを取得する
デフォルトでは、Eloquentはデータベースからすべての列(SELECT *)を取得します。小さなテーブルでは問題ありません。しかし、大きなテーブルと広いスキーマでは、これはメモリを浪費し、クエリを遅くします。
// ❌ 避ける:すべての列を取得
$users = User::all();
// ✅ より良い:必要なフィールドのみを取得
$users = User::select('id', 'name', 'email')->get();
👉 この小さな変更で、大きなクエリでメガバイトのメモリを節約できます。2. 大きなクエリをチャンクで処理する
数千のレコードを処理する必要があるとき、一度にすべてを読み込まないでください。チャンキングを使用してバッチで処理します。
User::chunk(500, function ($users) {
foreach ($users as $user) {
// ユーザーを処理
}
});
100k行をメモリに読み込む代わりに、これは一度に500を取得します。Laravelがバックグラウンドでページネーションを処理します。
順序が重要(例:一貫したバッチ処理)な場合は、idでソートすることを忘れないでください。3. ストリーミングにlazy()またはcursor()を使用する
本当に巨大なデータセットを扱っている場合、チャンキングでもまだメモリを消費しすぎる可能性があります。その場合は、カーソルを使用してください:
foreach (User::cursor() as $user) {
// レコードを一つずつストリーミング
}
これは大きなチャンクをメモリに読み込むことを避け、データベースから直接行をストリーミングします。行ごとは遅いですが、メモリ効率は極めて高いです。4. リレーションを事前読み込みする(ただし過負荷にしない)
N+1クエリ問題は、Laravelアプリで最大のパフォーマンスキラーの一つです。
// ❌ 悪い:各投稿に対して新しいクエリを実行
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
}
// ✅ 良い:ユーザーを事前読み込み
$posts = Post::with('user')->get();
これにより、数十のクエリがわずか2つに削減されます。
⚠️ ただし、すべてを事前読み込みしないでください。本当に必要なリレーションのみを読み込み、そうしないと未使用のデータでメモリを圧迫します。5. データベース列にインデックスを付ける
時々、ボトルネックはEloquentではありません - データベース自体です。適切なインデックスを追加することで、クエリ時間を秒からミリ秒に短縮できます。
Schema::table('users', function (Blueprint $table) {
$table->index('email');
$table->index(['status', 'created_at']);
});
👉 WHERE、ORDER BY、またはJOIN句で頻繁に使用される列は常にインデックスを付けてください。6. すべてを取得する代わりにページネーションを使用する
リストやダッシュボードでは、一度に数千のレコードを読み込まないでください。代わりにページネーションを使用します。
$users = User::paginate(50);
これにより、きれいなページネーションリンクが得られ、メモリ使用量を小さく保てます。
無限スクロールが必要な場合は、simplePaginate()がさらに高速です。7. 一括でupdate()とdelete()を使用する
多くのレコードを更新または削除する必要がある場合、ループで処理しないでください。一つのクエリで行います。
// ❌ 悪い:行ごとに更新
foreach ($users as $user) {
$user->update(['active' => false]);
}
// ✅ 良い:単一の一括更新
User::where('active', true)->update(['active' => false]);
これにより、数千のクエリを避け、アプリを飛ばせます。8. 意味があるときにキャッシュを使用する
同じ重いクエリを繰り返し実行している場合は、結果をキャッシュすることを検討してください。
$users = Cache::remember('active_users', 600, function () {
return User::where('active', true)->get();
});
Laravelはこれを簡単にし、キャッシュは遅いDBクエリを高速なメモリ検索に変えることができます。9. クエリのプロファイリング
ボトルネックがどこにあるかを推測しないでください。DB::enableQueryLog()またはLaravel Telescopeを使用してクエリをプロファイリングしてください。
DB::enableQueryLog();
$users = User::where('status', 'active')->get();
dd(DB::getQueryLog());
または、Eloquentが生成するものを確認するためにtoSql()を使用してください:
dd(User::where('status', 'active')->toSql());
10. 必要に応じて...生のクエリに下がる
Eloquentはエレガントですが、時には生のパフォーマンスが必要です。Laravelはクエリビルダーや生のSQLに戻ることを簡単にします。
// クエリビルダー
$users = DB::table('users')->where('status', 'active')->get();
// 生のSQL
$users = DB::select('SELECT id, name FROM users WHERE status = ?', ['active']);
アプローチを混ぜることは失敗ではありません - それは実用的なエンジニアリングです。Conclusion
LaravelのEloquent ORMは小さなプロジェクトだけのものではありません。正しいコツ - チャンキングとカーソルから、イーガーローディングと一括更新まで - で、Laravelを特別にする読みやすさと保守性を放棄することなく、数百万の行を効率的に処理できます。
一日の終わりに、Laravelでのパフォーマンス最適化はEloquentを放棄することではありません。その強みを理解し、その落とし穴を避け、いつ生の力を混ぜるべきかを知ることです。
そして、はい、これらすべての年が経った後でも、私はまだLaravelでコーディングすることを愛しています。それは私の最初のサイドプロジェクトから今日のCTOとしての役割まで、私と一緒に成長してきました。PHP自体のように、それは死ぬことを拒否します - そして正直に言って、私はそれで大丈夫です。🚀