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

StackOverflow https://stackoverflow.com/questions/181090

Вопрос

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

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.

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top