Как кэшировать вычисленный столбец в рельсах?

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

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

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

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

Ответы

Ответ 1

  • В кеш-память Rails можно хранить фактические кешированные значения (используйте memcached, если требуется, чтобы он был распределен).

  • Жесткий бит - это срок действия кеша, но срок действия кеша является необычным, не так ли? В этом случае мы можем просто перевернуть каждый из родительских объектов по очереди и также заблокировать его кеш. Я добавил магию 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

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

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

Хороший и простой =)

Ответ 3

Введите поле, похожее на кеш-счетчик. Например: order_items_amount и это будет кэшированное вычисленное поле.

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

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

Ответ 4

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

Ответ 5

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

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