Как оптимизировать запросы к базе данных в Node.js-приложении?

«Как оптимизировать запросы к базе данных в Node.js-приложении?» — вопрос из категории Базы данных, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Оптимизация запросов к БД — это основа производительности Node.js-приложения. Вот мои основные практики:

1. Использование пула соединений. Это предотвращает накладные расходы на установление нового соединения для каждого запроса. Например, с pg (PostgreSQL):

const { Pool } = require('pg');
const pool = new Pool({
  max: 20, // Максимальное число клиентов в пуле
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});
// Использование
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);

2. Эффективное кеширование. Для часто запрашиваемых и редко меняющихся данных я использую Redis.

const getCachedUser = async (userId) => {
  const cacheKey = `user:${userId}`;
  let user = await redisClient.get(cacheKey);

  if (!user) {
    // Кеш-промах: идем в БД
    const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
    user = rows[0];
    // Сохраняем в Redis с TTL (например, 5 минут)
    await redisClient.setEx(cacheKey, 300, JSON.stringify(user));
  } else {
    user = JSON.parse(user);
  }
  return user;
};

3. Оптимизация самих SQL-запросов:

  • Индексы: Анализирую медленные запросы с помощью EXPLAIN ANALYZE и добавляю индексы на поля в условиях WHERE, JOIN и ORDER BY.
  • Селективность: Выбираю только нужные колонки (SELECT id, name вместо SELECT *).
  • Пакетные операции (Batching): Объединяю несколько мелких операций в одну.
    // Вместо N запросов в цикле — один запрос с IN
    const userIds = [1, 2, 3, 4];
    const { rows } = await pool.query(
    'SELECT * FROM users WHERE id = ANY($1::int[])',
    [userIds]
    );
  • Prepared Statements: Использую параметризованные запросы (как в примерах выше) для безопасности от SQL-инъекций и возможности повторного использования плана выполнения СУБД.

4. Пагинация. Для больших наборов данных всегда реализую пагинацию на уровне БД с помощью LIMIT и OFFSET (или лучше WHERE id > lastId для keyset-пагинации).

5. Мониторинг. Подключаю логирование медленных запросов и использую инструменты вроде pg_stat_statements для постоянного анализа и выявления узких мест.