
そして数週間後、今度は別の問題が出る。リレーションの定義が使えない。アクセサが動かない。ソフトデリートの前提が抜ける。返り値がモデルではなく stdClass になり、既存のコードが静かに壊れる。速度改善のつもりが、保守性を削って別の炎上を呼ぶ。現場では珍しくない流れだ。
結局のところ、LaravelにおけるEloquentとクエリビルダの使い分けは、「どちらが速いか」だけでは決まらない。データ量、処理の種類、返り値に必要な振る舞い、そしてその処理を半年後に誰が読むかまで含めて境界線を引く必要がある。
この記事では、個人開発で現実的に使える laravel eloquent クエリビルダ 使い分け 基準 を、モデル生成のコスト、大量データ処理、N+1問題、セキュリティ、そして運用上の割り切りまで含めて整理する。
Eloquentが「重い」と言われる理由は、SQLだけではない
Eloquentとクエリビルダの速度比較で、最初に切り分けるべきなのは、データベースがSQLを実行する時間と、Laravel側で結果を扱う時間は別だという点だ。
[Eloquentで User::where('active](/articles/theme-jsontokasutamucssnojing-he/)', true)->get() を実行すると、データベースから返った行は単なる値の集まりではなく、Illuminate\Database\Eloquent\Model を継承したモデルオブジェクトとして組み立てられる。属性の管理、キャスト、リレーション、イベント、アクセサなど、モデルとして振る舞うための準備が必要になる。
一方、DB::table('users')->where('active', true)->get() では、基本的に stdClass のコレクションが返る。クエリビルダはモデルの機能を知らない。そのぶん、モデルオブジェクトを生成する処理や、Eloquent特有の状態管理は発生しない。
返り値の違いをまとめると、こうなる。
| 比較項目 | Eloquent | クエリビルダ |
|---|---|---|
| 主な入口 | User::query()、User::where() | DB::table() |
| 返り値 | モデル、Eloquentコレクション | stdClass、サポートコレクション |
| リレーション | hasMany、belongsTo などを利用可能 | 自動では扱わない |
| ソフトデリート | モデルの設定に従って適用 | 自分で条件を付ける必要がある |
| アクセサ・ミューテータ | 利用可能 | 利用できない |
| モデルイベント | 発火する | 発火しない |
| 大量行の処理 | モデル生成の負荷が増えやすい | 比較的軽く扱いやすい |
| 保守性 | 業務ルールをモデルに寄せやすい | SQLに近く、処理の意図は明確にしやすい |
ここでありがちな誤解がある。「EloquentはSQLの発行自体が遅い」という見方だ。実際には、単純な取得であれば、データベースが実行するSQLの差よりも、取得後にLaravelがモデルを生成し、属性やリレーションを扱える状態にする部分が効いてくることが多い。
つまり、Eloquentが不利になりやすいのは、1件のユーザーを取得する処理ではない。数万件、数十万件のレコードを一度に読み込み、それぞれをモデルとして抱え込むような処理だ。
逆に言えば、管理画面で1ページあたり数十件を表示する程度なら、Eloquentのモデル生成コストだけを理由にクエリビルダへ移行する意味は薄い。そこを雑に高速化すると、コードの読みやすさだけが失われるという罠がある。
Eloquentの問題は「遅いこと」ではなく、モデルとして扱う必要のない大量データまで、律儀にモデルへ仕立て上げてしまうことにある。
標準ルールはEloquentでいい。ただし「モデルを使う理由」がある範囲で
個人開発では、まずEloquentを標準にしてよい。むしろ、そうしたほうがよい。
理由は単純で、EloquentはLaravelのアプリケーション設計と相性がよく、データベース上のレコードを業務上のオブジェクトとして扱えるからだ。User、Order、Article といったモデルに、テーブル名だけでなく、そのデータに関するルールを集められる。
たとえば、注文に紐づく明細を取得するなら、Order に hasMany(OrderItem::class) を定義しておけば、呼び出し側は order->items と書ける。ユーザーを取得したあと、所属組織や権限を参照する処理でも、belongsTo や belongsToMany を使えば、テーブル結合の細部を毎回書き直さずに済む。
ソフトデリートも同じだ。SoftDeletes を使っているモデルでは、通常のクエリに削除済みレコードを含めないという前提が組み込まれる。クエリビルダに書き換えると、この前提は自動では引き継がれない。削除日時のカラムを見て whereNull() を付ける必要がある。
この差は、機能の多さというより、事故の発生箇所の差だ。
Eloquentなら、モデルに定義されたルールを呼び出し側が自然に利用できる。クエリビルダなら、処理ごとに必要な条件を思い出して書く。開発者が一人しかいない個人開発でも、数か月後の自分は別人に近い。昨日の設計判断を忘れた自分が、未来の保守担当者になる。
Eloquentを優先したい処理
次のような処理では、まずEloquentを選ぶのが無難だ。
- 1件から数十件程度の取得で、画面表示やAPIレスポンスに使う
- リレーションを通じて関連データを扱う
- アクセサ、ミューテータ、キャストを利用する
- ソフトデリートやモデルイベントが業務ルールに関係する
- 保存、更新、削除にモデルの振る舞いを伴う
- 同じモデルを複数のユースケースで利用する
- 取得したデータを、単なる表示用の値ではなくドメイン上のオブジェクトとして扱う
特に保存処理では、クエリビルダに逃げる前に一度立ち止まったほうがいい。User::create() や $order->update() が単にSQLを短く書くための機能ではなく、モデルのイベントやキャスト、属性保護などと組み合わさっている場合があるからだ。
もちろん、すべての処理をモデルへ押し込めばよいわけではない。Eloquentモデルに何でも詰め込むと、今度はモデルが巨大な神様クラスになる。そこはサービスクラスやアクション、専用のクエリクラスなどへ分けるべきだ。
ただし、「分離すること」と「クエリビルダを使うこと」は別の話である。Eloquentを使いながら処理を分離することもできる。この二つを混同すると、設計の問題をデータアクセス手法の変更で解決しようとしてしまう。
クエリビルダを選ぶべきなのは、データの性格が変わる場面
クエリビルダはEloquentの下位互換ではない。モデルを必要としないデータを、SQLに近い形で効率よく扱うための道具だ。
たとえば、アクセスログを日別に集計する処理を考える。必要なのが「日付ごとの件数」と「ユーザー種別ごとの合計」だけなら、数万件のログをモデルに変換してからPHPで集計する必要はない。データベースに COUNT や SUM を実行させ、集計済みの結果だけを受け取ればよい。
このとき、Eloquentのモデル機能はほとんど使わない。ログ1件ごとのアクセサも、リレーションも、イベントも不要だ。必要なのは集計結果であり、個々のログを表すモデルではない。
クエリビルダに向いている代表的な処理
COUNT、SUM、AVGなどの集計- 月別・日別・カテゴリ別のグループ集計
- 管理画面の統計カードに表示する数値の取得
- 大量のログや履歴から一部のカラムだけを抽出する処理
- 複数テーブルを結合して、表示用の平坦なデータを作る処理
- バッチや定期処理で、多数の行を順番に処理する処理
- モデルのイベントやアクセサを意図的に発火させたくない処理
ここで大事なのは、「速度が必要だからクエリビルダ」という一言で済ませないことだ。正確には、「モデルとしての振る舞いが不要で、取得する行数や処理量が大きいからクエリビルダ」という判断になる。
同じテーブルを扱っていても、画面表示ではEloquent、集計ではクエリビルダ、という分け方は普通に成立する。テーブル単位でどちらか一方に統一する必要はない。
toBase() はEloquentとクエリビルダの中間地点
既存のEloquentクエリを大きく書き換えたくない場合は、toBase() が使える。
たとえば、条件やスコープをEloquent側で組み立てたあと、モデルとしての復元が不要になった段階で toBase() を呼ぶ。これにより、Eloquentのクエリ構築を利用しつつ、結果を生のデータとして取得できる。
イメージとしては、次のような使い方だ。
User::query()->where('active', true)->select(['id', 'email'])->toBase()->get()
この場合、返ってくるのはEloquentモデルではなく、stdClass のコレクションになる。したがって、$user->display_name のようなアクセサや、$user->profile のようなリレーションは利用できない。
その代わり、モデルオブジェクトを生成する必要がなくなる。大量の行を読み込む場面では、メモリ使用量を抑えやすい。Eloquentのスコープや条件定義を再利用しながら、最後の結果だけ軽くするという、かなり現実的な妥協案だ。
ただし、toBase() は魔法の高速化スイッチではない。データベース側のインデックスが不足していれば、クエリそのものが遅いままだ。不要なカラムを大量に取得していれば、ネットワーク転送やPHP側の処理も減らない。モデル生成の負荷を減らす道具として使い、SQLの実行計画まで改善したつもりにはならないほうがよい。
大量データ処理では、取得方法そのものを見直す
「クエリビルダに変えたのに、バッチがまだ遅い」という相談はよくある。原因は、Eloquentかクエリビルダかではなく、そもそも全件を一度に get() していることだったりする。
モデルを生成しないクエリビルダでも、全件をコレクションへ載せれば、行数に応じてメモリを消費する。データ量が増えれば、当然ながら限界は来る。Eloquentをクエリビルダに変更しただけで、データ処理の設計まで改善されるわけではない。
Laravelには、大量データを分割して扱うための方法が用意されている。代表的なのが chunk()、cursor()、そして用途によっては lazy() 系の考え方だ。
chunk() はまとまりごとに処理する
chunk() は、指定した件数ごとにデータを取得し、まとまり単位で処理する方法だ。全件を一度にメモリへ載せずに済むため、バッチ処理や一括更新で使いやすい。
ただし、単純なページ番号方式で分割すると、処理中にデータが追加・更新されたとき、行の重複や取りこぼしが起きる可能性がある。大量データを安定して処理したいなら、主キーなどを基準にして分割する方式を検討したほうがよい。
処理対象の並び順が必要なのか、途中で対象データが変化するのか、更新によって検索条件から外れるのか。このあたりを曖昧にすると、速度以前に処理結果が不安定になる。
cursor() は1件ずつ流す
cursor() は、データを1件ずつ順番に処理したい場合に向いている。全件をコレクションとして保持しないため、メモリ消費を抑えやすい。
一方で、1件ずつ処理するぶん、関連データの参照や大量の更新を組み合わせると、別の負荷が出ることがある。処理内容によっては、ある程度まとめて処理したほうがデータベースとの往復回数を減らせる。
「メモリが少ないから、とりあえず cursor()」という使い方も危険だ。処理の中で別クエリを発行していないか、トランザクションを長時間保持していないか、外部API呼び出しで止まらないか。データを流す仕組みは、処理本体とセットで考える必要がある。
select() で取得するカラムを絞る
Eloquentかクエリビルダかに関係なく、不要なカラムを取得しないことは基本だ。
一覧画面で表示するのがID、タイトル、公開日時だけなら、テーブルの全カラムを取得する理由はない。select() で必要なカラムに絞るだけでも、データベースからアプリケーションへ移動するデータ量を減らせる。
このとき、Eloquentのモデルを使う場合は注意が必要だ。取得しなかった属性を後から参照すれば、当然ながら値は存在しない。さらに、リレーションやアクセサが特定のカラムを前提にしていることもある。
クエリビルダなら、その返り値がただのデータであることが比較的わかりやすい。表示専用のクエリとして専用クラスに閉じ込めれば、「この結果は更新用のモデルではない」と境界を明確にできる。
Eloquentが重いと感じる前に、N+1問題を疑う
Laravelのデータベース処理で、Eloquentが遅いと判断される原因として特に多いのがN+1問題だ。
たとえば、記事一覧を取得したあと、ループの中で各記事の著者を参照する。記事を1回取得し、著者の取得が記事ごとに発生すれば、記事が増えるほどクエリ数も増えていく。記事の取得が1回、著者の取得が記事件数分という構造になり、データ量が増えたところで急に効いてくる。
この場合、問題はEloquentを使っていることではない。リレーションを遅延ロードしたままループしていることだ。
with() を使って事前に関連データを取得すれば、必要なクエリをまとめられる。すでに取得済みのモデルに対して後から読み込みたい場合は load() を使う。どちらを使うかは、クエリを組み立てる段階で関連データが決まっているかどうかで考えればよい。
N+1を避けるための考え方
1. 一覧で表示する関連データを先に洗い出す
画面に著者名、カテゴリ名、コメント数などを表示するなら、それらがリレーション経由で取得されることを把握しておく。
2. クエリ構築時に with() を指定する
Article::with(['author', 'category'])->latest()->get() のように、必要な関連を明示する。
3. 取得カラムを絞る場合は、関連付けに必要なキーを落とさない
親モデルと関連モデルを細かく select() すると、外部キーや主キーが不足してリレーションが正しく解決できないことがある。
4. 開発環境でクエリ数を確認する
画面が表示されるだけで、裏側で大量のSQLが発行されていないかを見る。Laravel Debugbarなどの開発用ツールを使う方法もあるが、本番へ持ち込まない管理は必要だ。
5. 関連データが本当に必要かを見直す
with() を増やせばよいわけではない。使わないリレーションまで先読みすれば、今度は取得データが膨らむ。
N+1問題を解決すると、クエリビルダへ移行しなくても体感が改善するケースがある。Eloquentの機能を捨てる前に、まずEloquentを正しく使っているかを見る。ここを飛ばすと、必要な機能を削っただけで、根本原因が残るという理不尽な仕様になる。
with() を書かずにEloquentを遅いと言うのは、シートベルトを締めずに車の乗り心地を評価するようなものだ。Eloquentとクエリビルダの境界線を決める実務的な基準
では、実際の開発で何を基準に選べばよいのか。速度比較の記事を読み比べるより、処理の性質を次の順番で確認したほうが判断しやすい。
1. 返り値にモデルの振る舞いが必要か
取得したデータに対して、リレーション、アクセサ、ミューテータ、モデルメソッド、ソフトデリートの前提などを使うなら、Eloquentを選ぶ理由がある。
逆に、表示用の配列を作るだけ、件数を返すだけ、CSV出力用の値を順番に読むだけなら、モデルである必要は薄い。
「あとで使うかもしれないからEloquent」という判断も理解できるが、結果として何でもモデルにすると、処理の意図が曖昧になる。必要になった時点でEloquentへ戻すことはできる。最初から過剰な機能を背負わせる必要はない。
2. 取得件数は増える可能性があるか
現時点で100件しかないテーブルでも、個人開発のサービスが続けば、1年後には事情が変わる。ログ、通知、イベント履歴、アクセス記録などは特に増えやすい。
一方、ユーザー情報や設定値のように、通常の画面操作で取得する件数が大きく変わらないデータもある。すべてを将来の大量データに備えてクエリビルダへする必要はない。
件数が増える見込みがある処理は、Eloquentかどうかより先に、ページネーション、chunk()、cursor()、集計のデータベース移譲を考える。そのうえでモデル生成がボトルネックなら toBase() やクエリビルダを使う。
3. 処理は読み取りか、書き込みか
読み取り処理では、モデルが不要ならクエリビルダを選びやすい。しかし、書き込み処理は事情が違う。
モデルイベントやキャスト、監査ログ、関連処理があるなら、クエリビルダで直接更新すると、それらを飛ばす可能性がある。もちろん、意図的にイベントを発火させたくない一括更新もある。その場合はクエリビルダが適している。
重要なのは、「イベントが発火しない」ことを欠点として見るか、目的に合った性質として使うかだ。一括更新で1件ずつモデルを取得して保存すれば、処理時間もクエリ数も増えやすい。逆に、1件ごとの業務ルールがある処理を一括SQLで済ませれば、必要な副作用が起きない。
4. そのクエリは業務ルールを含むか
単純な検索条件なら、クエリビルダでも読みやすく書ける。しかし、公開状態、権限、削除状態、契約期間、ユーザー所属などのルールが複数絡む場合、Eloquentのスコープやモデルの関係性が役立つ。
クエリビルダを使う場合でも、条件を専用クラスや再利用可能なメソッドへ切り出せば保守性は上げられる。ただし、クエリビルダは自動的に保守しやすいコードを作ってくれない。SQLに近いぶん、書いた人の設計力がそのまま出る。
5. 集計や結合の結果を、何として扱うか
集計結果を「レポートの1行」として扱うなら、クエリビルダで十分なことが多い。逆に、集計結果をもとにモデルのメソッドやリレーションを呼び出すなら、最初からEloquentで取得したほうが自然な場合もある。
この境界を曖昧にすると、stdClass に対してモデルのような振る舞いを期待し始める。結果として、取得後に手作業で配列を組み替え、疑似モデルを作り、結局Eloquentの一部を再実装することになる。力技だが、あまり気持ちのよい設計ではない。
速度比較は「環境依存」を前提に読む
Eloquentとクエリビルダの速度比較については、ベンチマークの数字だけをそのまま自分の環境へ持ち込まないことだ。
実行速度は、サーバーのCPUやメモリ、PHPのバージョン、データベースの種類と設定、テーブルのインデックス、データ件数、取得するカラム、ネットワーク構成などに左右される。ある環境でクエリビルダが有利でも、別の環境で同じ差が出るとは限らない。
さらに、アプリケーション全体の遅さをデータ取得手法だけで説明できるとは限らない。次のような要因が、モデル生成より大きな問題になっていることもある。
- 検索条件に合うインデックスがない
LIKE検索でインデックスを使えない- 不要なカラムや関連データまで取得している
- N+1問題でクエリ数が増えている
- ページネーションせずに全件取得している
- PHP側で大量のソートや集計をしている
- 外部APIやファイル処理がボトルネックになっている
- 同じクエリを繰り返し発行している
「Eloquentは重いからクエリビルダへ」という移行は、原因調査の結果として行うなら意味がある。しかし、計測せずに行うと、改善幅が小さいままコードだけ複雑になる。
少なくとも、対象のクエリが何回発行されているか、どの程度の行を取得しているか、処理時間の大半がデータベースなのかPHP側なのかは確認したい。Laravelのクエリログやアプリケーションログ、データベースの実行計画を使えば、推測よりは正確に状況を見られる。
ベンチマークをする場合も、1回だけ実行して結論を出さない。キャッシュ、データベースの状態、PHPのウォームアップなどで結果は変わる。特定の数字を覚えるより、「自分の処理で、どこに時間とメモリが使われているか」を把握するほうが有益だ。
セキュリティ面では、両者の差より書き方の差が大きい
Eloquentとクエリビルダのどちらを使っても、通常の条件指定ではPDOのプリペアドステートメントとパラメータバインディングが利用される。したがって、両者の選択そのものがSQLインジェクション対策の優劣を決めるわけではない。
where('email', $email) のように値をバインディングする書き方であれば、Eloquentでもクエリビルダでも、値がSQL文へ直接埋め込まれるわけではない。
問題は、速度改善や柔軟性を理由に、文字列連結へ戻ってしまうことだ。
whereRaw() や orderByRaw() は便利だが、外部入力をそのままSQL文字列へ差し込めば危険になる。生SQLが必要な場面でも、値はバインディングし、カラム名や並び順のようにバインディングできない部分は許可リストで制御する。
たとえば、画面から並び順を受け取る場合、「入力値をそのままカラム名にする」のではなく、name は users.name、created は users.created_at のように、アプリケーション側で選択肢を定義する。これはEloquentでもクエリビルダでも同じだ。
また、クエリビルダへ移行するとソフトデリートの条件を自分で書く必要がある。これを忘れると、削除済みデータが一覧や集計へ混ざる。SQLインジェクションのような派手な脆弱性ではないが、個人開発ではこういう「仕様上は見えにくいデータ漏れ」が後から効いてくる。
セキュリティとデータの正しさは、単にSQLが安全に実行されるかだけではない。誰に見せてよいデータなのか、削除済みを含めるのか、テナント境界を越えていないか。Eloquentが暗黙に支えていたルールを、クエリビルダ側でも明示的に守る必要がある。
実装を分けるなら、クエリの意図が読める場所へ置く
Eloquentとクエリビルダを混在させる場合、最も避けたいのは、同じサービスメソッドの中に両者が無秩序に並ぶことだ。
たとえば、最初にEloquentで注文を取得し、その後にクエリビルダで同じ注文の明細を取り直し、さらに別のEloquentリレーションを呼ぶ。こうなると、どのデータがモデルとして保証され、どのデータが単なる集計結果なのかがわかりにくい。
実務では、処理の責務ごとに境界を作ると読みやすい。
- 画面やAPIで業務モデルを扱う部分はEloquent
- 集計や一覧専用の読み取りはクエリビルダ
- Eloquentの条件定義を再利用しつつ結果を軽くする部分は
toBase() - 大量処理は
chunk()やcursor()と組み合わせる - 複雑な読み取りは専用のクエリクラスへ閉じ込める
- 書き込みでモデルイベントが必要ならEloquent、不要な一括更新ならクエリビルダ
ここで、クエリビルダの結果をそのままコントローラーから返す設計も、規模によっては成立する。ただし、APIのレスポンス形式や日付の整形、権限による項目出し分けが増えると、取得処理と変換処理が混ざりやすい。
「取得したデータをどの形式で外へ出すか」を専用のリソースクラスやDTOなどで整理すれば、クエリビルダを使っても境界は保てる。反対に、Eloquentを使っているから自動的に設計がきれいになるわけでもない。道具は道具であり、片付けまではしてくれない。
迷ったときの現実的な判断表
| 状況 | 選びやすい手法 | 理由 |
|---|---|---|
| ユーザー詳細を1件取得する | Eloquent | モデルの属性や関連情報を自然に扱える |
| 記事一覧に著者名を表示する | Eloquent+with() | リレーションを使いながらN+1を避けられる |
| 日別アクセス数を集計する | クエリビルダ | 集計結果だけ取得すればよく、モデルが不要 |
| 大量ログをCSVへ出力する | クエリビルダ+chunk()またはcursor() | モデル生成と全件保持を避けやすい |
| ユーザーを一括で無効化する | 条件次第でクエリビルダ | イベント不要なら一括更新が効率的 |
| 1件ごとに監査ログを残して更新する | Eloquent | モデルイベントや個別処理が必要 |
| Eloquentのスコープを使いながら生データを取得する | Eloquent+toBase() | 条件定義を再利用し、返り値を軽くできる |
この表を絶対的なルールにする必要はない。特に「一括更新だから必ずクエリビルダ」というわけではなく、更新時に何を保証したいかで決まる。
個人開発では「最適化のしやすさ」も性能の一部になる
個人開発で見落とされがちなのが、将来の修正コストだ。
クエリビルダはSQLに近く、処理の形が見えやすい。集計や複雑な結合では、その明快さが強みになる。一方、Eloquentはモデルの定義にルールを集められるため、画面やAPIが増えたときに同じ条件を再利用しやすい。
どちらが保守しやすいかは、処理の種類によって違う。
単純なCRUDをクエリビルダだけで書くと、テーブル名やカラム名、削除条件、関連データの取得方法が各所へ散らばる。これは短期的には軽快だが、仕様変更のたびに修正箇所を探すことになる。
反対に、集計やレポートをすべてEloquentモデル経由で書くと、必要のないモデル機能を通り、大量のオブジェクトを生成することになる。しかも、複雑な集計をモデルのリレーションだけで表現しようとすると、何をしているクエリなのかが見えにくくなる。
個人開発では、開発速度と実行速度を対立させないほうがよい。最初から最速の実装を狙うより、まずEloquentで業務ルールを明確にし、問題が見えた部分だけクエリビルダへ寄せる。そのほうが、最適化の対象と効果がはっきりする。
「念のため高速なほうを使う」という判断は、たいてい念のために保守負債を増やす。性能問題が出る前のコードは、読みやすく変更しやすいことのほうが価値を持つ。
最後に:コードで解決する部分と、運用で割り切る部分
Eloquentとクエリビルダの使い分けに、万能な一行ルールはない。だが、実務上の初期方針なら作れる。
通常の画面表示、APIの詳細取得、リレーションを伴う業務処理、モデルのイベントやソフトデリートを使う書き込みは、Eloquentを基本にする。関連データを読むなら with() や load() を使い、N+1問題を放置しない。
集計、ログ、レポート、大量行の読み取り、モデルの振る舞いを必要としない一覧では、クエリビルダを検討する。既存のEloquent条件を活かしたいなら toBase() を使い、全件取得が必要なら chunk() や cursor() と組み合わせる。
速度が問題になったときは、まずクエリ数、取得カラム、インデックス、N+1、メモリ消費を確認する。Eloquentをクエリビルダへ移すのは、その調査のあとでよい。クエリビルダへ変えたという事実ではなく、ボトルネックが解消されたかどうかが重要だからだ。
運用でカバーする部分もある。管理画面の集計が多少遅いなら、キャッシュや集計テーブル、実行タイミングの分離で十分な場合がある。毎回リアルタイムに正確な値を返す必要がない処理まで、同期リクエスト内で完璧に計算しようとすると、設計が重くなる。
コードで解決する部分は、N+1、不要な全件取得、誤った更新条件、モデルが必要なのに生データで処理している箇所だ。一方で、データ量の増加に合わせてバッチ化する、集計を定期実行する、管理画面の表示を数分遅延させる、といった判断は運用側の仕事になる。
結局のところ、Eloquentは「遅いから避けるもの」ではなく、モデルとして扱う価値があるデータに使う道具だ。クエリビルダは「速いから常に選ぶもの」ではなく、モデルの振る舞いを必要としない大量処理や集計を、余計な荷物なしで進める道具である。
この境界線を守っておけば、Laravelの開発速度とデータベース処理の効率は両立できる。理想的な抽象化だけで全部解決することはない。だからこそ、Eloquentで素直に書く場所、クエリビルダで割り切る場所、そして運用へ逃がす場所を見極める。その地味な線引きが、炎上しない個人開発を支えてくれる。
Related reading: LaravelのAPI連携におけるRESTとGraphQLの長所と個人開発での最適解.