Pergunta

Eu tenho uma árvore de objetos do Active Record, algo como:

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

É muito caro para recalcular o complicated_calculation cada vez. Então, eu preciso de uma maneira de armazenar em cache o valor. No entanto, se alguma parte é alterada, ele precisa para invalidar o cache eo cache de seu pai e avô, etc.

Como um rascunho, eu criei uma coluna para conter o cálculo em cache na tabela de "partes", mas isto cheira um pouco podre. Parece que deve haver uma maneira mais limpa para armazenar em cache os valores calculados sem enchê-los ao lado das colunas "reais".

Foi útil?

Solução

  1. Você pode encher os valores realmente colocado na cache do Rails (uso memcached se você exigir que ele seja distribuído).

  2. O pouco difícil é cache de expiração, mas o cache de expiração é incomum, certo? Nesse caso, podemos apenas loop sobre cada um dos objetos pai por sua vez, e zap seu cache também. Eu adicionei um pouco de magia ActiveRecord para sua classe para fazer chegar o pai objetos própria simplicidade - e você não precisa mesmo de tocar o seu banco de dados. Lembre-se de chamar Part.sweep_complicated_cache(some_part) como apropriado no seu código -. Você pode colocar isso em retornos de chamada, etc, mas não pode adicioná-lo para você, porque eu não entendo quando complicated_calculation está mudando

    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
    

Outras dicas

Eu sugiro usar callbacks de associação.

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

agradável e fácil =)

Tenha um campo semelhante a um cache balcão. Por exemplo: order_items_amount e tem que ser um campo calculado em cache.

Use um filtro after_save para recalcular o campo em qualquer coisa que pode modificar esse valor. (Incluindo o registro em si)

Edit: Este é basicamente o que você tem agora. Eu não sei de qualquer solução de limpeza a menos que você queria para armazenar campos calculados em cache em outra tabela.

De qualquer usando um before_save ou um Observer ActiveRecord é o caminho a percorrer para garantir que o valor em cache é up-to-date. Gostaria de usar um before_save e, em seguida, verifique se o valor que você utilizar no cálculo realmente mudou. Dessa forma, você não tem que atualizar o cache se você não precisa.
Armazenar o valor no db lhe permitirá armazenar em cache os cálculos sobre várias solicitações. Outra opção para isso é para armazenar o valor em memcache. Você pode fazer um acessor especial e setter para esse valor que pode verificar o cache de memória e atualizá-lo se necessário.
Outro pensamento: Haverá casos em que você vai mudar um valor em um dos modelos e precisam do cálculo ser atualizado antes de fazer o salvamento? Nesse caso, você terá que suja o valor do cache sempre que você atualizar qualquer um dos valores de cálculo no modelo, não com um before_save.

Descobri que às vezes há uma boa razão para de-normalize informações em seu banco de dados. Eu tenho algo semelhante em um aplicativo que estou trabalhando e eu apenas re-calculam que campo a qualquer hora as mudanças de cobrança.

Ele não usa um cache e armazena os mais modernos figura da data no banco de dados.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top