Как я могу кэшировать вычисленный столбец в рельсах? - PullRequest
13 голосов
/ 08 октября 2008

У меня есть дерево активных объектов записи, что-то вроде:

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

Это слишком дорого каждый раз пересчитывать сложный_счет. Итак, мне нужен способ для кэширования значения. Тем не менее, если какая-либо часть изменяется, она должна сделать недействительным свой кэш и кэш своего родителя, деда и т. Д. И т. Д.

В качестве черновика я создал столбец для хранения кэшированных вычислений в таблице «частей», но это пахнет немного гнилым. Похоже, что должен быть более чистый способ кэширования вычисленных значений, не помещая их вдоль «настоящих» столбцов.

Ответы [ 5 ]

27 голосов
/ 08 октября 2008

Я предлагаю использовать обратные вызовы ассоциации.

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

Красиво и просто =)

6 голосов
/ 08 октября 2008
  1. Вы можете поместить фактически кэшированные значения в кеш Rails (используйте memcached, если вам требуется его распространение).

  2. Сложный бит - это истечение срока действия кеша, но истечение срока действия кеша встречается редко, верно? В этом случае мы можем просто циклически перебирать каждый из родительских объектов и также убирать его кеш. Я добавил магию ActiveRecord в ваш класс, чтобы упростить получение родительских объектов - и вам даже не нужно трогать вашу базу данных. Не забудьте позвонить Part.sweep_complicated_cache(some_part), как это необходимо в вашем коде - вы можете поместить это в обратные вызовы и т. Д., Но я не могу добавить его для вас, потому что не понимаю, когда complicated_calculation меняется.

    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
    
2 голосов
/ 08 октября 2008

Либо использование before_save или ActiveRecord Observer - это способ убедиться, что кэшированное значение обновлено. Я бы использовал before_save, а затем проверил, действительно ли значение, которое вы используете в расчете, изменилось. Таким образом, вам не нужно обновлять кеш, если вам это не нужно.
Сохранение значения в БД позволит вам кэшировать расчеты по нескольким запросам. Другой вариант для этого - сохранить значение в memcache. Вы можете создать специальный метод доступа и установки для этого значения, который может проверять кэш памяти и обновлять его при необходимости.
Еще одна мысль: будут ли случаи, когда вы измените значение в одной из моделей и нуждаетесь в обновлении расчета перед сохранением? В этом случае вам нужно будет очистить значение кэша всякий раз, когда вы обновляете любое из расчетных значений в модели, а не с before_save.

2 голосов
/ 08 октября 2008

Есть поле, похожее на счетчик кэша. Например: order_items_amount и иметь это кешируемое вычисляемое поле.

Используйте фильтр after_save, чтобы пересчитать поле для всего, что может изменить это значение. (Включая саму запись)

Редактировать: Это в основном то, что у вас есть сейчас. Я не знаю ни одного более чистого решения, если вы не хотите хранить кэшированные вычисляемые поля в другой таблице.

1 голос
/ 13 октября 2008

Я обнаружил, что иногда есть веская причина для нормализации информации в вашей базе данных. У меня есть нечто похожее в приложении, над которым я работаю, и я просто пересчитываю это поле каждый раз, когда изменяется коллекция.

Он не использует кеш и хранит самые последние данные в базе данных.

...