Как я могу кэшировать вычисленный столбец в рельсах?
-
05-07-2019 - |
Вопрос
У меня есть дерево активных объектов записи, что-то вроде:
class Part < ActiveRecord::Base
has_many :sub_parts, :class_name => "Part"
def complicated_calculation
if sub_parts.size > 0
return self.sub_parts.inject(0){ |sum, current| sum + current.complicated_calculation }
else
sleep(1)
return rand(10000)
end
end
end
Каждый раз слишком сложно пересчитывать сложный_счет. Итак, мне нужен способ для кэширования значения. Тем не менее, если какая-либо часть изменяется, она должна сделать недействительным свой кэш и кэш своего родителя, деда и т. Д.
В качестве черновика я создал столбец для хранения кэшированных вычислений в " частях " стол, но это пахнет немного гнилым. Похоже, что должен быть более чистый способ кеширования вычисленных значений, не помещая их рядом с «реальным» столбцы.
Решение
Вы можете поместить фактически кэшированные значения в кеш Rails (используйте memcached, если вам требуется его распространение).
Сложность - истечение срока действия кеша, но истечение срока действия кеша встречается редко, верно? В этом случае мы можем просто циклически перебирать каждый из родительских объектов и также убирать его кеш. Я добавил магию ActiveRecord в ваш класс, чтобы упростить получение родительских объектов - и вам даже не нужно трогать вашу базу данных. Не забывайте вызывать Part.sweep_complicated_cache (some_part)
в своем коде, как это необходимо - вы можете поместить это в обратные вызовы и т. Д., Но я не могу добавить это для вас, потому что я не понимаю, когда ложный_счет
меняется.
class Part < ActiveRecord::Base
has_many :sub_parts, :class_name => "Part"
belongs_to :parent_part, :class_name => "Part", :foreign_key => :part_id
@@MAX_PART_NESTING = 25 #pick any sanity-saving value
def complicated_calculation (...)
if cache.contains? [id, :complicated_calculation]
cache[ [id, :complicated_calculation] ]
else
cache[ [id, :complicated_calculation] ] = complicated_calculation_helper (...)
end
end
def complicated_calculation_helper
#your implementation goes here
end
def Part.sweep_complicated_cache(start_part)
level = 1 # keep track to prevent infinite loop in event there is a cycle in parts
current_part = self
cache[ [current_part.id, :complicated_calculation] ].delete
while ( (level <= 1 < @@MAX_PART_NESTING) && (current_part.parent_part)) {
current_part = current_part.parent_part)
cache[ [current_part.id, :complicated_calculation] ].delete
end
end
end
Другие советы
Я предлагаю использовать обратные вызовы ассоциации.
class Part < ActiveRecord::Base
has_many :sub_parts,
:class_name => "Part",
:after_add => :count_sub_parts,
:after_remove => :count_sub_parts
private
def count_sub_parts
update_attribute(:sub_part_count, calculate_sub_part_count)
end
def calculate_sub_part_count
# perform the actual calculation here
end
end
Красиво и просто =)
Иметь поле, похожее на кеш счетчика. Например: order_items_amount и иметь это кешируемое вычисляемое поле. Р>
Используйте фильтр after_save, чтобы пересчитать поле для всего, что может изменить это значение. (Включая саму запись)
Изменить: это в основном то, что у вас есть сейчас. Я не знаю ни одного более чистого решения, если вы не хотите хранить кэшированные вычисляемые поля в другой таблице.
Либо с помощью before_save, либо с помощью ActiveRecord Observer можно убедиться в актуальности кэшированного значения. Я бы использовал before_save, а затем проверил, действительно ли значение, которое вы используете в расчете, изменилось. Таким образом, вам не нужно обновлять кеш, если вам это не нужно.
Сохранение значения в БД позволит вам кэшировать расчеты по нескольким запросам. Другой вариант для этого - сохранить значение в memcache. Вы можете создать специальный метод доступа и установки для этого значения, который может проверять кэш памяти и обновлять его при необходимости.
Еще одна мысль: будут ли случаи, когда вы измените значение в одной из моделей и нуждаетесь в обновлении расчета перед сохранением? В этом случае вам нужно будет очистить значение кэша всякий раз, когда вы обновляете любое из значений вычисления в модели, а не с before_save.
Я обнаружил, что иногда есть веская причина для нормализации информации в вашей базе данных. У меня есть нечто похожее в приложении, над которым я работаю, и я просто пересчитываю это поле каждый раз, когда изменяется коллекция. Р>
Он не использует кеш и хранит самые последние данные в базе данных. Р>