Уроки модуля (4/4)
Проблема N+1
В серверной разработке одной из наиболее частых ошибок, которые катастрофически снижают производительность API, является печально известная проблема «N + 1 запросов».
Обычно это незаметно для разработчика, который использует библиотечные абстракции, такие как «ORM» (объектно-реляционные картографы: например, 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/БД: 1 уникальный запрос, объединенный и математически оптимизированный с помощью собственных индексов Postgres, полностью обработанный с дополнительной задержкой в сети 0 миллисекунд.
Альтернатива агрегации JSON
Современные версии Postgres даже позволяют возвращать все сложное соединение, вложенное в формат JSON с json_agg, что позволяет вашему JavaScript получать массив products, включая его подмассив форматированных обзоров, без использования локальных скриптов! (В продвинутых путях и задачах вы изучите предложение Postgres JSONb.)
Ваша очередь
Давайте решим настоящую проблему 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
Если логические группировки не используются внутри базы данных для получения общего количества по строке и категории, Node.js будет бесконечно вызывать БД, превращаясь в N+1.\nНапишите один запрос, который выбирает поле «город» из «клиентов» и стандартное количество Postgres «COUNT(id)», используя ключевое слово терминала, чтобы агрегировать их уникальным образом по имени.
Показать подсказку
Тривиально: ВЫБЕРИТЕ город, COUNT(id) FROM клиентов GROUP BY city;
Решение доступно после 3 попыток