ORMのパフォーマンスを最適化して、アプリケーションをスケーラブルにします

記事の翻訳は、コース「PHPのバックエンド開発者」の開始前夜に作成されました










こんにちは!私はイタリアの開発者であり、プラットフォームのCTOであるValerioですInspector.dev



この記事では、バックエンドサービスを開発するときに使用する一連のORM最適化戦略を共有します。



私たち一人一人が、サーバーまたはアプリケーションの実行速度が遅い(またはまったく機能していない)こと、そしてコーヒーマシンで長いリクエストの結果を待っている間、不平を言う必要があったと確信しています。



それを修正する方法は?

確認してみましょう!



データベースは共有リソースです



データベースが非常に多くのパフォーマンスの問題を引き起こしているのはなぜですか?

他のクエリから独立しているクエリはないことを忘れがちです。

一部のクエリが遅い場合でも、他のクエリにはほとんど影響しないと思います...しかし、本当にそうですか?



データベースは、アプリケーションで実行されるすべてのプロセスによって使用される共有リソースです。データベースにアクセスするための設計が不十分な方法が1つでも、システム全体のパフォーマンスを混乱させる可能性があります。



したがって、「このコードが最適化されていなくても大丈夫です」と考えて、起こりうる結果を忘れないでください。データベースへのアクセスが1回遅いと、データベースが過負荷になる可能性があり、その結果、ユーザーエクスペリエンスに悪影響を与える可能性があります。



N +1データベースクエリの問題



N + 1の問題とは何ですか?



これは、ORMを使用してデータベースと対話する場合の一般的な問題です。 SQLコードを書くことではありません。



EloquentのようなORMシステムを使用する場合、どのクエリがいつ実行されるかが常に明確であるとは限りません。この特定の問題のコンテキストで、関係と熱心な読み込みについて話しましょう。



どのORMシステムでも、エンティティ間の関係を宣言でき、データベース構造をナビゲートするための優れたAPIを提供します。

以下は、「Article」エンティティと「Author」エンティティの良い例です。



/*
 * Each Article belongs to an Author
 */
$article = Article::find("1");
echo $article->author->name; 
/*
 * Each Author has many Articles
 */
foreach (Article::all() as $article)
{
    echo $article->title;
}


ただし、ループ内でリレーションシップを使用する場合は、コードを慎重に作成する必要があります。



以下の例を見てください。



記事のタイトルの横に作者の名前を追加したいと思います。ORMを使用すると、記事と作成者の1対1の関係を使用して、作成者の名前を取得できます。



すべてが単純なようです:



// Initial query to grab all articles
$articles = Article::all();
foreach ($articles as $article)
{
    // Get the author to print the name.
    echo $article->title . ' by ' . $article->author->name;
}


しかし、それから私たちは罠に陥りました!



このループは、すべての記事を取得するための1つの初期要求を生成します。



SELECT * FROM articles;


各記事の作成者を取得し、作成者が常に同じであっても、「名前」フィールドの値を表示するためのさらにN個のクエリ。



SELECT * FROM author WHERE id = [articles.author_id]


正確にN + 1のリクエストを受け取ります。



これは大したことではないように思われるかもしれません。さて、15または20の追加リクエストを作成しましょう-大したことではありません。ただし、この記事の最初の部分に戻りましょう。



  • — , .
  • , , .
  • , .


:



Laravelのドキュメントによると、プロパティ($article->authorとしてEloquentリレーションシップにアクセスすると、リレーションシップデータが遅延ロードされるため、N +1クエリの問題が発生する可能性が高くなります



これは、最初にプロパティにアクセスするまで、関係データがロードされないことを意味します。



ただし、簡単な方法を使用すると、すべての関係データを一度にロードできます。次に、プロパティとしてEloquentリレーションにアクセスする場合、データがすでにロードされているため、ORMシステムは新しいクエリを実行しません。



この戦術は「イーガーローディング」と呼ばれ、すべてのORMでサポートされています。



// Eager load authors using "with".
$articles = Article::with('author')->get();
foreach ($articles as $article)
{
    // Author will not run a query on each iteration.
    echo $article->author->name;
}


Eloquentは、with()関係を熱心にロードするための方法提供します



この場合、2つのクエリのみが実行されます。

最初の記事は、すべての記事をダウンロードするために必要です。



SELECT * FROM articles;


2つ目はメソッドによって実行され、with()すべての作成者をフェッチします。



SELECT * FROM authors WHERE id IN (1, 2, 3, 4, ...);


Eloquentの内部エンジンはデータをマッピングし、通常の方法でアクセスできます。



$article->author->name;


オペレーターを最適化する select



長い間、selectクエリでフィールドの数を明示的に宣言しても、パフォーマンスが大幅に向上することはないと思っていたので、簡単にするために、クエリのすべてのフィールドを取得しました。



さらに、特定のselectステートメントのフィールドのリストをハードコーディングすると、そのようなコードをさらに維持することが難しくなります。



この議論の最大の落とし穴は、データベースの観点から、これは確かに真実かもしれないということです。



ただし、ORMを使用するため、データベースから選択されたデータはPHP側のメモリにロードされ、ORMシステムがさらに管理できるようになります。キャプチャするフィールドが多いほど、プロセスに必要なメモリも多くなります。



Laravel Eloquentは、クエリを必要な列のみに制限するためのselectメソッドを提供します。



$articles = Article::query()
    ->select('id', 'title', 'content') // The fields you need
    ->latest()
    ->get();


フィールドを除外することにより、PHPインタープリターは不要なデータを処理する必要がないため、メモリ消費を大幅に削減できます。



フルフェッチを回避すると、データベース自体が結果としてメモリを節約できるため、並べ替え、グループ化、およびマージのパフォーマンスも向上します。



MySQLでビューを使用する



ビューは、他のテーブルに基づくSELECTクエリであり、データベースに保存されます。



1つ以上のテーブルを選択すると、データベースは最初にSQLステートメントをコンパイルし、エラーがないことを確認してから、データをフェッチします。



ビューはプリコンパイルされたSELECTステートメントであり、処理されると、MySQLはビューの基になる内部クエリをすぐに実行します。



また、データのフィルタリングに関しては、MySQLは通常PHPよりも賢いです。 PHP関数を使用してコレクションまたは配列を処理するよりも、ビューを使用すると、パフォーマンスが大幅に向上します。



データベースを多用するアプリケーションを開発するためのMySQLの機能について詳しく知りたい場合は、次のすばらしいサイトをチェックしてください:www.mysqltutorial.org



Eloquentモデルをビューにリンクする



ビューは「仮想テーブル」とも呼ばれます。ORMの観点からは、通常のテーブルのように見えます。



したがって、Eloquentモデルを作成して、ビューにあるデータを照会できます。



class ArticleStats extends Model
{
    /**
     * The name of the view is the table name.
     */
    protected $table = "article_stats_view";
    /**
     * If the resultset of the View include the "author_id"
     * we can use it to retrieve the author as normal relation.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}


強制やページネーションなどと同様に、リレーションシップは通常どおり機能します。パフォーマンスの低下はありません。



結論



これらのヒントが、より信頼性が高くスケーラブルなソフトウェアの開発に役立つことを願っています。



すべてのコード例は、ORMとしてEloquentを使用して記述されていますが、これらの戦略はすべての主要なORMで同じように機能することに注意してください。



よく言うように、効果的な戦略を実行するためのツールが必要です。そして、戦略がなければ、話すことは何もありません。



記事を最後までお読みいただき、誠にありがとうございます。Inspectorについて詳しく知りたい場合は、当社のWebサイトwww.inspector.devにご招待しますご不明な点がございましたら、お気軽にチャットまでお問い合わせください。



以前にここに投稿されました:www.inspector.dev/make-your-application-scalable-optimizing-the-orm-performance





続きを読む:






All Articles