Почему Rails Scopes предпочтительнее, если грязные контроллеры быстрее?
-
29-09-2019 - |
Вопрос
Я пытался цепорить запросы AREL, используя прицелы, вместо того, чтобы просто использовать какую-то многословную логику, которую я написал в контроллере. Но прицелы медленнее, чем просто получать все записи, а затем просеивать их с некоторой логикой. Мне интересно, почему прицелы лучше.
Вот что я делаю:
- Вопрос имеет много ответов
- Ответ принадлежит одному вопросу
- Вопрос имеет столбец «Вопрос_тип», который я использую для его сортировки
Во -первых, The Scopes Way ...
в вопросе. RB:
scope :answered, joins(:answers).order('answers.created_at desc')
scope :dogs, where(:question_type => "dogs")
scope :cats, where(:question_type => "cats")
scope :mermaids, where(:question_type => "mermaids")
в вопросах_контроллере.rb:
@dogs_recently_answered = Question.answered.dogs.uniq[0..9]
@cats_recently_answered = Question.answered.cats.uniq[0..9]
@mermaids_recently_answered = Question.answered.mermaids.uniq[0..9]
Затем, в представлении, я прокатится через эти переменные экземпляра (которые теперь являются массивами, содержащими не более 10 элементов) и отображают результаты.
Вот время, необходимое для загрузки страницы (пять раз в разные времена):
Завершено 200 ОК за 535 мс (просмотры: 189,6 мс | Activerecord: 46,2 мс)
Завершено 200 OK за 573 мс (просмотры: 186,0 мс | Activerecord: 46,3 мс)
Завершено 200 OK за 577 мс (просмотры: 189,0 мс | Activerecord: 45,6 мс)
Завершено 200 OK за 532 мс (просмотры: 182,9 мс | Activerecord: 46,1 мс)
Завершено 200 ОК в 577 мс (просмотры: 186,7 мс | Activerecord: 46.9ms)
Теперь грязный контроллер ...
@answers = Answer.order("created_at desc")
@all_answered = []
@answers.each {|answer| @all_answered << answer.question}
@recently_answered = @all_answered.uniq
@dogs_all_answered = []
@cats_all_answered = []
@mermaids_all_answered = []
@recently_answered.each do |q|
if q.question_type == "dogs"
@dogs_all_answered << q
@dogs_recently_answered = @dogs_all_answered[0..9]
elsif q.question_type == "cats"
@cats_all_answered << q
@cats_recently_answered = @cats_all_answered[0..9]
elsif q.question_type == "mermaids"
@mermaids_all_answered << q
@mermaids_recently_answered = @mermaids_all_answered[0..9]
end
end
А вот времена, которые нужно загрузить страницу сейчас (пять разных раз):
Завершено 200 OK за 475 мс (просмотры: 196,5 мс | Activerecord: 34,5 мс)
Завершено 200 ОК за 480 мс (просмотры: 200,4 мс | Activerecord: 36,4 мс)
Завершено 200 OK за 434 мс (просмотры: 198,2 мс | Activerecord: 35,8 мс)
Завершено 200 OK за 475 мс (просмотры: 194,2 мс | Activerecord: 36,4 мс)
Завершено 200 OK за 475 мс (просмотры: 195,0 мс | Activerecord: 35,4 мс)
Так...
Помимо читаемости, что выиграть, оттачивая запрос с помощью масштаба? В конечном итоге это становится быстрее, когда есть больше записей?
Решение
Во -первых, я не уверен, что понимаю, как вопрос может быть иным, чем уникальным, поэтому я бы посмотрел на попытку удалить это. Я не знаю логику ваших данных, так что это может быть не применимо, но это дополнительный шаг, которого вы могли бы избежать.
Вот как я бы подошел к нему:
scope :answered, joins(:answers).order('answers.created_at desc')
scope :recent, take(10)
scope :dogs, where(:question_type => "dogs")
scope :cats, where(:question_type => "cats")
scope :mermaids, where(:question_type => "mermaids")
@dogs_recently_answered = Question.answered.dogs.recent
@cats_recently_answered = Question.answered.dogs.recent
@mermaids_recently_answered = Question.answered.dogs.recent
Это меняет TOP
часть запроса в базу данных, где он принадлежит, а не получает все рядов, а затем отбрасывание всех, кроме 10. В зависимости от ваших единоцельных критериев вы также можете использовать область
scope :unique, select('DISTINCT column_name')
И тогда вы можете использовать вопрос .Cats.unique.recent и получите все в одном быстром запросе, который использует реляционную алгебру, для которой предназначены системы баз данных.
Другие советы
Я думаю, что причина, по которой прицелы медленнее в этом случае, заключается в том, что они приводят к 3 отдельным запросам базы данных, тогда как в другом подходе используются знания о том, что все три результата могут быть удовлетворены одним запросом, который вы используете.
Предполагая, что это так, неудивительно, что область применения выполняют 3 отдельных запроса, поскольку система не знает, когда вы называете первую, которую вы собираетесь позвонить другим сразу после этого. Возможно, есть стратегия оптимизации, которая была бы разумной для этого сценария, но я не знаю, что Activerecord это реализует.
Во всяком случае, это один недостаток в сфере масштаба в этом конкретном случае. Мне нравятся прицелы, потому что они чистые/чистые, гибкие и инкапсулировали именованную абстракцию для запроса. Подобно, во многих сценариях они не заметно медленнее, чем эквивалентный прямой запрос.