Почему области Rails предпочтительнее, если грязные контроллеры работают быстрее?
Я пытался связать запросы Arel с помощью областей, а не просто с использованием какой-то длинной логики, которую я написал в контроллере. Но области работают медленнее, чем просто получить все записи и затем проанализировать их с некоторой логикой. Мне интересно, почему прицелы лучше.
Вот что я делаю:
- вопрос имеет много ответов
- ответ принадлежит одному вопросу
- вопрос имеет столбец "question_type", который я использую для его сортировки
Во-первых, способ...
в вопросе
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")
в questions_controller.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 OK за 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 мс)
Завершено 200 OK за 577 мс (Просмотров: 186,7 мс | ActiveRecord: 46,9 мс)
Теперь, грязный способ управления...
@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 OK за 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 мс)
Так...
Помимо читабельности, что можно выиграть, оттачивая запрос с областью? Становится ли это быстрее, когда появляется больше записей?
2 ответа
Во-первых, я не уверен, что понимаю, как вопрос может отличаться от уникального, поэтому я хотел бы попытаться удалить его. Я не знаю логики ваших данных, так что это может быть неприменимо, но это дополнительный шаг, который вы могли бы избежать.
Вот как я бы подошел к этому:
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')
и затем вы можете использовать Question.cats.unique.recent и получить все это в одном быстром запросе, который использует реляционную алгебру, для которой предназначены системы баз данных.
Я думаю, причина в том, что области в этом случае медленнее, потому что они приводят к 3 отдельным запросам к базе данных, тогда как другой подход использует знание того, что все три результата могут быть удовлетворены одним используемым запросом.
Если предположить, что это так, то неудивительно, что области делают 3 отдельных запроса, поскольку система не знает, когда вы вызываете первый, который вы собираетесь вызывать сразу после этого. Может быть, есть стратегия оптимизации, которая была бы целесообразна для этого сценария, но я не знаю, что ActiveRecord реализует ее.
Во всяком случае, это один недостаток объема в данном конкретном случае. Мне нравятся области видимости, потому что они чистые / понятные, гибкие и содержат именованную абстракцию для запроса. AFAICT, во многих случаях они не заметно медленнее, чем эквивалентный прямой запрос.