Как Acadia решает проблему 1+N, запретив циклы и рекурсию
Solving the 1+N Query Problem
ORM-библиотеки часто приводят к проблеме 1+N: вместо одного JOIN выполняется множество отдельных запросов, что замедляет работу. Автор объясняет, что корень проблемы — в выразительности языка, позволяющей циклы и рекурсию. Вдохновившись языком Datalog, Acadia полностью исключает рекурсию, гарантируя завершение всех запросов за полиномиальное время и невозможность проблемы 1+N. В статье показано, как Acadia использует оператор intersect для эффективного соединения таблиц.
Оказывается, мы можем полностью устранить проблему 1+N, но, что еще интереснее, мы также можем гарантировать, что все запросы завершаются, и даже сильнее — что все запросы завершаются за время, полиномиально зависящее от объема данных!
- WilcoKruijer
Я искренне считаю, что каждый инженер, пишущий запросы (даже SQL), должен прочитать руководство по моделированию данных FoundationDB [0]. Оно действительно помогает понять, как умный выбор первичного ключа может повлиять на эффективность запросов. При некоторой денормализации соединения даже не нужны для производительности.
Postgres уже давно поддерживает конвейерную обработку запросов. На мой взгляд, большинство запросов следует писать так, чтобы последовательные запросы не имели никаких зависимостей от данных предыдущего запроса. Это значительно ускоряет работу приложений.
- red_admiral
Вот что получается, когда позволяете своей ORM работать с базой данных, не понимая JOIN'ов. Особенно та часть, где что-то вроде 'book.author.name', которое выглядит как простое обращение к полю, на самом деле является вызовом метода на прокси-объекте ORM (book) через __getattr__ в Python или что-то подобное, который запускает новый запрос, если нужные данные еще не загружены.
Некоторые ORM позволяют указать объем данных, которые вы хотите получить, например, у Hibernate есть свой язык запросов Hibernate Query Language.
В какой-то момент лучше просто написать SQL самостоятельно. Даже без проблем с JOIN'ами, если вы просите ORM получить человека с идентификатором 123, и вам нужно только его имя, ORM не может этого знать, если вы ему не скажете, и в итоге вы получаете запрос типа 'SELECT *'.
- quibono
Отлично, и я понимаю, почему использование `getAuthorNames` решает проблему N+1 здесь.
Но... не решает ли это проблему, убирая большую часть того, что делает ее проблемой в первую очередь? Я думаю, большинство людей используют ORM для синхронизации данных между SQL и нативными классами. И это предполагает, что вместо этого будет выполняться запрос Acadia.
Честно говоря, я не пытаюсь быть негативным, просто у меня сложилось общее впечатление, что эти N+1 обычно возникают потому, что люди _хотят_ прямого доступа к объектам, _хотят_ писать циклы, _хотят_ обращаться к полям, и пусть ORM разбирается с базовым SQL.
- wood_spirit
Конечно, мейнстримные ORM оставляют много производительности на столе. Например, однажды я пропатчил ORM в struggling php-приложении, которому должен был помочь. Я начал с профилировщика-логгера, но потом у меня возникла безумная идея стать оптимизатором трассировки. Распознавая места вызовов из предыдущих посещений, я мог обнаружить 1+N и select * и фактически перекодировать их в лучший SQL при следующем запуске. Поразительно, но это дало огромную разницу, и я был удивлен, что обычные ORM не делают ничего подобного.
- kstrauser
Кстати: я настоятельно предпочитаю называть это "проблемой 1+N", как это сделал автор. Я не понимал, о чем люди говорят, когда они жаловались на "N+1".
N+1: Вы уже выполняете N запросов. Неужели добавление еще одного — это такая большая проблема?
1+N: Это должен был быть один запрос, но каким-то образом вы раздули его до одного плюс еще N.
Я много раз видел этот антипаттерн запросов и знал, что он плох, но не понимал, что именно это люди имеют в виду под "N+1", которое, как я думал, должно означать что-то другое.