Модульні уроки (4/4)
Проблема N+1
Однією з найпоширеніших помилок у розробці бекенда, яка катастрофічно знижує продуктивність API, є сумнозвісна проблема «N + 1 запитів».
Зазвичай це невідомо прокрадається розробнику, який використовує бібліотечні абстракції, такі як "ORM" (Object-Relational Mappers: наприклад, Prisma або Sequelize), щоб спілкуватися з базою даних із Node.js.
Анатомія катастрофи
Уявіть, що програма хоче повернути весь список домашньої сторінки електронної комерції: Усі продукти разом із рядком назви категорії, до якої належить кожен. Якщо дивитися з боку Node.js, недосвідчений програміст написав би ітераційний цикл:
products = await db.query("SELECT * FROM products");(Одна велика, громіздка операція 1)- Розробник повторює (
for..of) коженproductв масиві зі 100 знайдених - На кожному кроці в 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»:
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.)
Ваша черга
Давайте розв’яжемо справжню проблему 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
Якщо логічні Group-Bys не використовуються в базі даних для отримання загальної кількості рядків і категорій, Node.js нескінченно викликатиме БД, збільшуючись до N+1.\nНапишіть єдиний запит, який вибирає поле «місто» з «клієнтів» і стандартного підрахунку Postgres «COUNT(id)», використовуючи ключове слово терміналу, щоб агрегувати їх унікально за назвою.
Показати підказку
Тривіальний: ВИБЕРІТЬ місто, КІЛЬКІСТЬ (ідентифікаторів) FROM клієнтів, ГРУПУВАЙТЕ ПО місту;
Рішення доступне після 3 спроб