Перейти до основного вмісту
Модуль 10 · Урок 4 із 440/57 у курсі~12 min
Модульні уроки (4/4)

Проблема N+1

Однією з найпоширеніших помилок у розробці бекенда, яка катастрофічно знижує продуктивність API, є сумнозвісна проблема «N + 1 запитів».

Зазвичай це невідомо прокрадається розробнику, який використовує бібліотечні абстракції, такі як "ORM" (Object-Relational Mappers: наприклад, Prisma або Sequelize), щоб спілкуватися з базою даних із Node.js.

Анатомія катастрофи

Уявіть, що програма хоче повернути весь список домашньої сторінки електронної комерції: Усі продукти разом із рядком назви категорії, до якої належить кожен. Якщо дивитися з боку Node.js, недосвідчений програміст написав би ітераційний цикл:

  1. products = await db.query("SELECT * FROM products"); (Одна велика, громіздка операція 1)
  2. Розробник повторює (for..of) кожен product в масиві зі 100 знайдених
  3. На кожному кроці в Node.js вони очікують ітераційного підзапиту categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id); та вбудовують його в результат (N вторинних ітерацій повільних маленьких запитів!)

Загальна кількість запитів до демона Postgres: 1 + 100 локальних ітерацій = 101 мікромережі між Node.js і БД. 101 туди й назад викликають жахливі витрати на мережу та жахливу затримку. У реальному масштабі N може складатися не з 100, а з 10 000 рядків рахунків-фактур для вивчення! API не повернеться до досягнення тайм-ауту 5xx.

Справжня рідна відповідь: JOIN або агрегація в одному запиті

Майстерне використання абстракцій СУБД, написаних вручну, замість тривіальних команд ORM у Node, об’єднує виконання за мілісекунди в єдиний неперевершений запит, який вирішує халепу «N+1»:

SQL
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 з json_agg, дозволяючи вашому JavaScript отримувати масив products, включаючи їхній підмасив відформатованих оглядів, без будь-якого локального з’єднання сценаріїв! (У розширених шляхах і завданнях ви вивчатимете пункт JSONb Postgres.)

Ваша черга

вправи#sql.m10.l4.e1
Спроби: 0Завантаження…

Давайте розв’яжемо справжню проблему N+1, яка створюється.\nNode запустив би глобальний вибір, а потім повторив би кожного клієнта, щоб знайти їхні імена, якщо він хотів би отримати підсумок замовлень, відправлених сьогодні вранці.\nРозв’яжіть її, надрукувавши в одному нативному кадрі JOIN між «клієнтами» (використовуйте оригінальний текстовий псевдонім «c») і «замовленнями» (псевдонім «o»).\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

вправи#sql.m10.l4.e2
Спроби: 0Завантаження…

Якщо логічні Group-Bys не використовуються в базі даних для отримання загальної кількості рядків і категорій, Node.js нескінченно викликатиме БД, збільшуючись до N+1.\nНапишіть єдиний запит, який вибирає поле «місто» з «клієнтів» і стандартного підрахунку Postgres «COUNT(id)», використовуючи ключове слово терміналу, щоб агрегувати їх унікально за назвою.

Завантаження редактора…
Показати підказку

Тривіальний: ВИБЕРІТЬ місто, КІЛЬКІСТЬ (ідентифікаторів) FROM клієнтів, ГРУПУВАЙТЕ ПО місту;

Рішення доступне після 3 спроб