Даже великие программисты не застрахованы от простых ошибок. Это история 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 делает.
Последний, но тем не менее важный…

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