Даже великие программисты не застрахованы от простых ошибок. Это история djangoproject.com, где веб-сайт Django работал медленно и плохо работал из-за банальной ошибки в коде. Я написал об этом подробнее здесь, а в Блоге Sentry это обсуждается более подробно.

Они столкнулись с проблемой запроса N + 1, это обычная проблема в базе данных.

Что такое проблема запроса N + 1? 📝

Проблема довольно проста и понятна, поэтому мне не понадобятся никакие объясните это, как будто я пять аналогий.

Таким образом, проблема запроса N + 1 возникает, когда вы запускаете запрос к базе данных несколько раз, когда вы могли бы просто запустить его постоянное количество раз (например, один или два раза).

Решение также довольно простое: GIT GUD.

Помимо шуток, давайте сначала посмотрим на проблему, и вы увидите, что решение тоже довольно простое. Трудная часть — определить его при написании кода. Я включил примеры NodeJS и Django, так что все это понимают.

Допустим, у вас есть таблица/документ базы данных с именем «Сообщения», который имеет отношение «один ко многим» с другой таблицей/документом базы данных с именем «Комментарии». Мы увидим, как извлечение комментариев из всех сообщений может привести к проблеме запроса N + 1. Я включил код NodeJS и Django для облегчения понимания.

// NodeJS example

Post.findAll().then(posts => {
  for (let post of posts) {
    console.log(post.title);
    // The next line suffers from N + 1 query problem
    post.getComments().then(comments => {
      for (let comment of comments) {
        console.log(comment.content);
      }
    });
  }
});
# Python - Django Example

posts = Post.objects.all()
for post in posts:
    print(post.title)
    # The next line suffers from N + 1 query problem
    comments = Comment.objects.filter(post=post) 
    for comment in comments:
        print(comment.content)

В обоих приведенных выше примерах кода код, извлекающий комментарии, страдает от проблемы с запросом N + 1. Для каждого объекта записи создается отдельный запрос для получения связанных с ним комментариев.

Если в вашей базе данных 10 сообщений, этот код выполнит 11 запросов: один для получения всех сообщений и еще 10 для получения их комментариев. Это неэффективно и не нужно, так как вы можете получить все необходимые данные с помощью одного запроса, который соединяет две таблицы.

Решение ❤️

Прежде всего, вам нужно понять, что запросы к базе данных медленные, поэтому вам определенно не следует совершать N циклов обращения к базе данных. Итак, что вам нужно сделать, это загрузить данные в память и выполнять операции локально. Вот так…

// NodeJS Example

Post.findAll({ include: Comment }).then(posts => {
  for (let post of posts) {
    console.log(post.title);
    for (let comment of post.comments) {
      console.log(comment.content);
    }
  }
});
# Python - Django example

posts = Post.objects.all()
comments = Comment.objects.select_related('post')
for post in posts:
    print(post.title)
    post_comments = comments.filter(post=post)
    for comment in post_comments:
        print(comment.content)

Здесь мы просто получаем как сообщения, так и комментарии в одном запросе, используя соединения в базе данных, и фильтруем комментарии во время выполнения языка. Это намного быстрее, чем выполнение N запросов к базе данных.

Да, решение довольно простое, я никогда не говорил, что это сложно. Трудная часть — это надлежащий просмотр и анализ кода, чтобы подобные ошибки не попадали в производство. Его очень трудно не заметить с помощью ORM, поскольку он скрывает сложность и стоимость запросов, из-за чего его трудно заметить.

Итак, вы можете настроить инструмент, который проверяет производительность вашего приложения, например, Sentry делает.

Последний, но тем не менее важный…

Заключение

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

Надеюсь, вам понравилась статья, если вы считаете, что она будет полезна другим, поделитесь ею, если у вас есть какие-либо отзывы, буду рад вашим комментариям. 🌞