LaravelにおけるN+1問題の特定と不要なクエリの削減

パフォーマンス改善の作業は、往々にして退屈なものです。

常に「レイテンシを80%削減した」といった英雄的な物語があるわけではありません。大抵の場合は、クリーンアップ作業です。知らずに実行していたクエリを見つけ、それを削除していくのです。

Laravelアプリが成長しているなら、おそらく次のような問題に直面しているはずです。

  1. 隠れたN+1クエリ

見えないものは修正できません。ローカル環境では、beyondcode/laravel-query-detector のようなツールを使用してください。ループ内での遅延読み込み(lazy-load)パターンを使用しているときに教えてくれます。

本番環境では実行しないでください。オーバーヘッドが発生します。アプリがlocalまたはtestingモードのときのみ登録するようにしてください。

  1. Eager Loadingの無駄

N+1問題を解決するために with() を追加することがよくありますが、これが新たな問題を生みます。ビューで既に使用されていないリレーションシップを、誤ってEager Loadingしてしまう可能性があるからです。

デザイン変更によってテーブルからカラムが削除された場合、コントローラー内の対応する with() 呼び出しも削除しなければなりません。捨ててしまうデータに対して、コストを支払っていることになります。

すべてのEager Loadingを「要求(claim)」として扱ってください。データを使用しないのであれば、その要求を削除しましょう。

  1. 同じデータの再計算

リクエスト全体を通して変わらない値があります。それらを何度も計算すると、CPUを無駄に消費します。これを解決するにはメモ化(memoization)を利用してください。

例:

protected ?string $defaultConnection = null;

public function getDefaultConnectionName(): string
{
    return $this->defaultConnection ??= $this->resolveDefaultConnection();
}

??= 演算子は、値を一度だけ計算し、そのリクエストの残りの間、その値を再利用します。

  1. ダッシュボードのクエリの罠

ダッシュボードでは、カードごとに個別の count() クエリを実行してしまうことがよくあります。カードが6枚あれば、6つのクエリが実行されます。

代わりに、次の2つのことを行ってください:

  • カウントをグループ化する。1つのテーブルの合計値を、単一のクエリですべて取得します。
  • 短いキャッシュを利用する。ダッシュボードの統計情報は、秒単位でリアルタイムである必要はありません。5分間キャッシュしましょう。

1人の訪問者がクエリのコストを負担すれば、他の全員はキャッシュされた結果を受け取れます。

最後に:リグレッション(退行)を防ぐ

後で開発者が悪いコードを再度追加してしまうと、クリーンアップは失敗に終わります。Pestを使用してクエリ数をアサート(検証)しましょう。1ページで実行できるクエリ数に上限を設定します。もし開発者がN+1を追加した場合、テストが失敗するようにします。

最適化とは、主に「削除」することです。使われていないロードを削除する。再計算される値を削除する。不要なクエリを削除する。

Source: https://dev.to/nasrulhazim/a-day-of-performance-hardening-hunting-n1s-and-killing-wasted-queries-in-laravel-568p