モジュールのレッスン (4/4)
N+1 問題
バックエンド開発において、API パフォーマンスを壊滅的に低下させる最も頻繁な間違いの 1 つは、悪名高い「N + 1 クエリ」問題です。
これは通常、「ORM」(オブジェクト リレーショナル マッパー: Prisma や Sequelize など) のようなライブラリ抽象化を使用して Node.js からデータベースと通信している開発者には気づかれないように忍び込みます。
災害の解剖学
アプリが e コマース ホームページのリスト全体を返したいと考えているとします。すべての商品と、それぞれが属するカテゴリ名の文字列。 Node.js 側から見ると、経験の浅いプログラマーは反復ループを作成します。
products = await db.query("SELECT * FROM products");(1 つの大きくてかさばる操作 1)- 開発者は、見つかった 100 個の配列内の各
productを反復 (for..of) します。 - 各ステップで、Node.js で反復サブクエリ
categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id);を待機し、それを結果に埋め込みます (N 回の低速で小さなクエリの二次反復!)
Postgres デーモンへのリクエストの合計: 1 + 100 ローカル反復 = 101 Node.js と DB 間のマイクロ ネットワーク ラウンド トリップ。 101 ラウンド トリップにより、ネットワーク オーバーヘッドが膨大になり、遅延が大きくなります。実際の規模では、N は 100 ではなく、合計請求書の 10,000 行を調査する必要があるかもしれません。 API は 5xx タイムアウトに達するまで戻りません。
本当のネイティブの答え: 単一クエリでの JOIN または集計
Node の簡単な ORM コマンドの代わりに、手動で記述された DBMS 抽象化を巧みに使用することで、実行がミリ秒単位で 1 つの無敵のクエリに統合され、「N+1」の混乱が解決されます。
SELECT
p.id,
p.name,
c.name AS category_name
FROM products p
JOIN categories c ON p.category_id = c.id;合計 API/DB ラウンドトリップ: 1 つの固有のクエリが融合され、Postgres 独自のインデックスによって数学的に最適化され、追加のネットワーク遅延が 0 ミリ秒で完全に処理されます。
JSON 集約の代替手段
最新の Postgres バージョンでは、json_agg を使用して JSON としてネストされた複雑な結合全体を返すこともできるため、JavaScript はローカル スクリプト グルーを使用せずに、書式設定されたレビューのサブ配列を含む products 配列を受け取ることができます。 (高度なパスと課題では、Postgres の JSONb 句を学習します。)
あなたの番です
作成中の実際の N+1 問題を解決してみましょう。\n今朝出荷された注文の要約が必要な場合、ノードはグローバル選択を実行し、すべての顧客を反復して名前を検索します。\n単一のネイティブ ショットで「顧客」 (元のテキスト エイリアス 'c' を使用) と '注文' (エイリアス 'o') の間の JOIN を出力することで解決します。\n 2 つの簡単なフィールドを抽出します (形式的なフィールドを強制します)エイリアス接頭辞): 'c.first_name' (顧客から) および 'o.status' (保留中の注文から)。
ヒントを表示
構文: SELECT c.first_name, o.status FROM 顧客 c JOIN 注文 o ON c.id = o.customer_id;
3 回の試行後に解決策が利用可能になります
GROUP BY の効率性を探る
行およびカテゴリごとの合計数を取得するためにデータベース内で論理 Group-By が使用されていない場合、Node.js は DB を際限なく呼び出し、N+1 にまで膨れ上がります。\nターミナル キーワードを使用して、「customers」 から「city」 フィールドと標準の Postgres カウント「COUNT(id)」を選択する単一のクエリを作成し、それらを名前で一意に集計します。
ヒントを表示
簡単な例: 都市を選択し、顧客から COUNT(id) を都市ごとにグループ化します。
3 回の試行後に解決策が利用可能になります