5 мощных оптимизаций для увеличения запросов Mongoose

Привет всем, это Джосс. Сегодня я покажу вам 5 мощных оптимизаций для увеличения запросов Mongoose.

Что такое Мангуст?

Mongoose — это популярная библиотека моделирования объектных данных (ODM) для Node.js и MongoDB. Он упрощает взаимодействие с базой данных, позволяя разработчикам легко определять схемы, создавать модели и выполнять различные операции CRUD. Однако по мере роста приложений эффективная обработка большого количества запросов Mongoose становится критически важной для поддержания оптимальной производительности. В этой статье мы рассмотрим пять мощных оптимизаций, которые могут значительно повысить эффективность запросов Mongoose, обеспечивая более плавное взаимодействие с вашей базой данных MongoDB.

Индексация для скорости

Индексы играют решающую роль в повышении производительности запросов. Без индексов MongoDB должна выполнять сканирование коллекции, что может быть довольно медленным для больших наборов данных. Создавая соответствующие индексы, вы можете сократить время, затрачиваемое на поиск документов, и значительно ускорить операции чтения. Анализируйте часто используемые поля в запросах и при необходимости создавайте составные индексы.

⚠️ Имейте в виду, что создание индексов для каждого поля может привести к увеличению объема памяти и времени записи, поэтому сосредоточьтесь на полях, которые часто запрашиваются.

const userSchema = new mongoose.Schema({
  username: { 
    type: String, 
    unique: true 
  },
  email: { 
    type: String, 
    index: true 
  },
  age: {
    type: Number
  }
});

Ограничение и пропуск запросов с разбивкой на страницы

При работе с большими наборами данных обычно реализуется разбиение на страницы для извлечения данных меньшими управляемыми фрагментами. Mongoose предоставляет методы limit() и skip(), которые можно использовать для получения данных на страницах. Однако очень важно использовать их с осторожностью, так как skip() может привести к снижению производительности при работе с большими наборами данных. Рассмотрите возможность использования разбивки на страницы с помощью курсора, например поля createdAt, чтобы обеспечить стабильную производительность независимо от количества страниц.

const pageNumber = 2;
const pageSize = 10;
const users = await User
  .find()
  .skip((pageNumber - 1) * pageSize)
  .limit(pageSize);

Как упоминалось ранее, разбиение на страницы с помощью курсора является более эффективным подходом к обработке больших наборов данных. Вместо того, чтобы полагаться исключительно на skip(), который может стать медленным по мере увеличения количества пропущенных документов, используйте курсор для разбиения на страницы набора данных. Курсор представляет собой указатель на определенную позицию в наборе данных, что позволяет эффективно выбирать следующую страницу данных.

Пример разбиения на страницы с помощью курсора с использованием поля createdAt:

async function getNextPage(lastDocumentCreationDate, pageSize) {
  const query = lastDocumentCreationDate ? { 
    createdAt: { 
      $lt: lastDocumentCreationDate 
    } 
  } : {};

  const users = await User.find(query)
    .sort({ createdAt: -1 }) // Sort by createdAt in descending order (newest first)
    .limit(pageSize);

  return users;
}

const pageSize = 10;

// will return 10 newest users
const firstPage = await getNextPage(null, pageSize); 

// To get the next page of users
const lastUserCreationDate = firstPage[firstPage.length - 1].createdAt;
const secondPage = await getNextPage(lastUserCreationDate, pageSize); 

Бережливые запросы для снижения накладных расходов

По умолчанию запросы Mongoose возвращают документы Mongoose, что сопряжено с некоторыми накладными расходами из-за предлагаемых ими дополнительных функций. Однако, если вам нужны только необработанные объекты JavaScript, вы можете использовать метод lean(), чтобы сократить время обработки и объем памяти. При использовании lean() результатом запроса является не экземпляр документа Mongoose, а простой объект JavaScript.

const users = await User.find().lean();

⚠️ Недостатком включения lean является то, что в бережливых документах нет:

  • Отслеживание изменений
  • Кастинг и проверка
  • Геттеры и сеттеры
  • Виртуалы
  • save()

Выберите только необходимые поля

Минимизируйте данные, извлекаемые из базы данных, выбирая только необходимые поля в своих запросах. Эта оптимизация особенно важна при работе с большими документами с вложенными структурами. Ограничив данные, отправляемые по сети, можно значительно улучшить время ответа на запрос.

const users = await User.find().select('username email');

Используйте операции массовой записи

При работе с несколькими операциями записи рассмотрите возможность использования операций массовой записи, таких как insertMany(), updateMany() и deleteMany(), вместо отдельных запросов. Операции массовой записи могут быть более эффективными, поскольку они сокращают количество обращений к базе данных.

Пример создания множества пользователей:

const newUsers = [
  { username: 'user1', email: '[email protected]' },
  { username: 'user2', email: '[email protected]' }
];
await User.insertMany(newUsers);

Другим подходящим примером может быть этот:

Предполагая, что у вас есть модель User, определенная, как и раньше, с добавлением поля score:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true },
  email: { type: String, required: true },
  age: { type: Number, required: true },
  createdAt: { type: Date, default: Date.now },
  score: { type: Number, default: 0 } // New 'score' field added
});

const User = mongoose.model('User', userSchema);

Вы можете использовать метод bulkWrite для обновления оценки некоторых пользователей за один раз:

// Function to update the score for multiple users
async function updateScores(scoreUpdates) {
  const bulkOperations = scoreUpdates.map((update) => {
    const { userId, points } = update;
    const updateOperation = points >= 0 ? '$inc' : '$dec'; // Use '$inc' for adding points and '$dec' for removing points
    return {
      updateOne: {
        filter: { 
          _id: userId 
        },
        [updateOperation]: { 
          score: Math.abs(points) // Ensure points are a positive value
        } 
      }
    };
  });

  try {
    const result = await User.bulkWrite(bulkOperations); // call Mongo only 1 time instead of 3
    console.log(`${result.modifiedCount} users' scores updated successfully.`);
  } catch (error) {
    console.error('Error updating scores:', error);
  }
}

// Example usage:
const scoreUpdates = [
  { userId: 'user1Id', points: 5 },   // Add 5 points to user1
  { userId: 'user2Id', points: -3 },  // Remove 3 points from user2
  { userId: 'user3Id', points: 10 }   // Add 10 points to user3
];

updateScores(scoreUpdates);

Заключение

Внедрив эти пять мощных оптимизаций, вы можете значительно повысить производительность своих запросов Mongoose. Надлежащее индексирование, разбиение на страницы, бережливые запросы, выборочное извлечение полей и массовые операции записи — ценные инструменты, обеспечивающие беспрепятственное взаимодействие вашего приложения Node.js с базой данных MongoDB даже по мере роста вашего набора данных. Не забывайте отслеживать и тестировать производительность после применения этих оптимизаций, чтобы точно настроить приложение и обеспечить бесперебойную работу пользователей. Удачного кодирования!