Проблема N+1 запросов — классическая ловушка для разработчиков на Ruby on Rails, которая может превратить быстрый код в медленный из-за избыточных обращений к PostgreSQL. В этой статье разберём, как находить такие узкие места с помощью гемов вроде bullet, и научимся эффективно устранять их через includes, preload и другие методы ActiveRecord. DevOps-практики мониторинга и профилирования запросов помогут предотвратить подобные проблемы в продакшене.
Всё было хорошо… пока ты не загрузил 100 пользователей с их компаниями — и не получил 101 SQL-запрос. Добро пожаловать в мир N+1 — главной боли ORM.
💥 Что такое N+1?
User.all.each do |user|
puts user.company.name
end
Если не сделать includes(:company), Active Record сначала выполнит SELECT * FROM users,
а затем ещё по одному запросу на каждую user.company — итого N+1 запрос.
🕵️ Как найти N+1?
- В логах
development.log— ищи повторяющиесяSELECT ... WHERE id = ... -
Гем
bulletкричит прямо в браузере:“N+1 detected: User => [:company]”
🛡 Как бороться?
✅ includes
User.includes(:company).each do |user|
puts user.company.name
end
ActiveRecord выполнит 2 запроса, без повторений.
✅ preload / eager_load
| Метод | Как работает |
|---|---|
includes |
сам выбирает preload или eager_load |
preload |
всегда 2 запроса |
eager_load |
делает LEFT OUTER JOIN |
🧠 Полезные советы
- Проверь has_many через
includes(...).references(...)— иначеWHEREпо ассоциации не сработает -
Избегай вложенных N+1:
Post.includes(comments: :author) - Профилируй через
EXPLAINиbullet
🚀 Новички любят .each — профи любят .pluck
Иногда pluck решает всё:
User.where(active: true).pluck(:id, :email)
1 запрос. Никаких моделей. Быстро.
🔚 Вывод: N+1 — не баг ORM, а следствие ленивой загрузки. На собеседовании покажите, что вы умеете его замечать и убирать — это один из самых часто задаваемых performance-вопросов.