N+1 в Rails: как его найти, за что он отвечает и как побороть

Проблема 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?

  1. В логах development.log — ищи повторяющиеся SELECT ... WHERE id = ...
  2. Гем 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-вопросов.

🗓 Дата публикации: 03.09.2024, но это не точно...

Ruby on Rails N+1 запросы ActiveRecord оптимизация SQL PostgreSQL bullet