Найти в Дзене

Кейс про ии, n+1 и orm

Все опытные бекендеры так или иначе знают, что sql-запросы в цикле это зло. Но даже если их явно не делать, то они все равно будут появляться при использовании ORM. Например мы извлекаем какую-то связь, а затем генерируем json по этому набору данных, попутно вызывая какие-то вложенные связи: user.companies().map((company) => company.creator()). В этом примере сами компании будут извлечены одним запросом, но обращение к creator() породит цепочку запросов, по запросу на каждую компанию. В миру эта штука называется select n + 1 Кто-то скажет что фу, orm зло, потому что там есть такая проблема. Я к этому отношусь не так, orm позволяет быстро двигаться вперед, а n+1 обычно чинится очень просто, за счет механизмов подгрузки связей в orm. Например так: user.companies.includes(:creator).map.... Несмотря на это, у нас на Хекслете действительно десятки мест где такая проблема есть. Чаще всего она не критическая, поэтому мы чиним по мере появления проблем. Хорошая новость заключается в том, чт

Кейс про ии, n+1 и orm

Все опытные бекендеры так или иначе знают, что sql-запросы в цикле это зло. Но даже если их явно не делать, то они все равно будут появляться при использовании ORM. Например мы извлекаем какую-то связь, а затем генерируем json по этому набору данных, попутно вызывая какие-то вложенные связи: user.companies().map((company) => company.creator()). В этом примере сами компании будут извлечены одним запросом, но обращение к creator() породит цепочку запросов, по запросу на каждую компанию. В миру эта штука называется select n + 1

Кто-то скажет что фу, orm зло, потому что там есть такая проблема. Я к этому отношусь не так, orm позволяет быстро двигаться вперед, а n+1 обычно чинится очень просто, за счет механизмов подгрузки связей в orm. Например так: user.companies.includes(:creator).map.... Несмотря на это, у нас на Хекслете действительно десятки мест где такая проблема есть. Чаще всего она не критическая, поэтому мы чиним по мере появления проблем.

Хорошая новость заключается в том, что если у вас ORM, то возможно написать инструмент, который будет находить n + 1 самостоятельно почти для всех ситуаций. Так как мы пишем на Rails, то используем гем Bullet, который добрые люди написали довольно давно как раз под эту задачу. Он не просто показывает где n+1, но конкретно говорит как его убрать:

user: kirillmokevnin

GET /programs/algorithms-extended

USE eager loading detected

Stack::Landing::LearningModule => [:project_cover_attachment]

Add to your query: .includes([:project_cover_attachment])

// тут еще стек вызовов для удобства

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

На днях, когда стало видно, что было много изменений за последний месяц, которые породили немало тяжелых n+1. Думаю дай выделю часик и пройдусь по ним. Открываю код и понимаю что елки палки, так это ведь идеальная задача под ИИ. Написал запрос и запустил на выполнение в режиме если найден n+1 то бросаем исключение:

> Запускай тесты по очереди, исправляй исключение n+1 (bullet) по рекомендации. Если тест после этого снова не проходит, переходи к следующему

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

Ссылки: Телеграм | Youtube | VK