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

Проблема N+1

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

Обычно это незаметно для разработчика, который использует библиотечные абстракции, такие как «ORM» (объектно-реляционные картографы: например, 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/БД: 1 уникальный запрос, объединенный и математически оптимизированный с помощью собственных индексов Postgres, полностью обработанный с дополнительной задержкой в ​​сети 0 миллисекунд.

Альтернатива агрегации JSON

Современные версии Postgres даже позволяют возвращать все сложное соединение, вложенное в формат JSON с json_agg, что позволяет вашему JavaScript получать массив products, включая его подмассив форматированных обзоров, без использования локальных скриптов! (В продвинутых путях и задачах вы изучите предложение Postgres JSONb.)

Ваша очередь

Упражнение#sql.m10.l4.e1
Попыток: 0Загрузка…

Давайте решим настоящую проблему N+1.\nNode должен был выполнить глобальный выбор, а затем выполнить итерацию по каждому клиенту, чтобы найти их имена, если бы он хотел получить сводку заказов, отправленных сегодня утром.\nРешите ее, напечатав в одном нативном кадре СОЕДИНЕНИЕ между «клиентами» (используйте исходный текстовый псевдоним «c») и «заказами» (псевдоним «о»).\nИзвлеките 2 тривиальных поля (принудительное префикс формального псевдонима): «c.first_name» (от клиента) и «o.status» (от отложенного ордера).

Загрузка редактора…
Показать подсказку

Синтаксис: ВЫБЕРИТЕ c.first_name, o.status ОТ клиентов c ПРИСОЕДИНЯЙТЕСЬ к заказам o ON c.id = o.customer_id;

Решение доступно после 3 попыток

Исследование эффективности GROUP BY

Упражнение#sql.m10.l4.e2
Попыток: 0Загрузка…

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

Загрузка редактора…
Показать подсказку

Тривиально: ВЫБЕРИТЕ город, COUNT(id) FROM клиентов GROUP BY city;

Решение доступно после 3 попыток