Il modo migliore per combinare la memorizzazione nella cache di frammenti e oggetti per memcached e Rails

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

Domanda

Supponiamo che tu abbia un frammento della pagina che mostra i post più recenti e lo fai scadere in 30 minuti. Sto usando Rails qui.

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  ...
<% end %>

Ovviamente non è necessario eseguire la ricerca nel database per ottenere i post più recenti se il frammento esiste, quindi dovresti essere in grado di evitare anche questo sovraccarico.

Quello che sto facendo ora è qualcosa di simile nel controller che sembra funzionare:

unless Rails.cache.exist? "views/recent_posts"
  @posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end

È questo il modo migliore? È sicuro?

Una cosa che non capisco è perché la chiave è " recent_posts " per il frammento e " views / recent_posts " quando ho controllato più tardi, ma ho trovato questo dopo aver visto memcached -vv per vedere cosa stava usando. Inoltre, non mi piace la duplicazione dell'inserimento manuale di " recent_posts " ;, sarebbe meglio tenerlo in un posto.

idee?

È stato utile?

Soluzione

Il Interlock Plugin di Evan Weaver risolve questo problema.

Puoi anche implementare qualcosa di simile da solo facilmente se hai bisogno di comportamenti diversi, come un controllo più preciso. L'idea di base è racchiudere il codice del controller in un blocco che viene effettivamente eseguito solo se la vista necessita di tali dati:

# in FooController#show
@foo_finder = lambda{ Foo.find_slow_stuff }

# in foo/show.html.erb
cache 'foo_slow_stuff' do
  @foo_finder.call.each do 
    ...
  end
end

Se hai familiarità con le basi della meta programmazione ruby ??è abbastanza facile racchiuderlo in un'API più pulita di tuo gusto.

Questo è superiore all'inserimento del codice finder direttamente nella vista:

  • mantiene il codice finder dove gli sviluppatori lo prevedono per convenzione
  • mantiene la vista ignorante sul nome / metodo del modello, consentendo un maggiore riutilizzo della vista

Penso che cache_fu potrebbe avere funzionalità simili in una delle sue versioni / fork, ma non è possibile richiamarlo in modo specifico.

Il vantaggio che si ottiene da memcached è direttamente correlato alla percentuale di hit della cache. Fare attenzione a non sprecare la capacità della cache e causare inutili errori memorizzando nella cache più volte lo stesso contenuto. Ad esempio, non memorizzare contemporaneamente nella cache un set di oggetti record e il loro frammento html. La cache in genere dei frammenti offrirà le migliori prestazioni, ma in realtà dipende dalle specifiche dell'applicazione.

Altri suggerimenti

Cosa succede se la cache scade tra il tempo in cui la controlli nel controller e il tempo viene verificato nel rendering della vista?

Farei un nuovo metodo nel modello:

  class Post
    def self.recent(count)
      find(:all, :limit=> count, :order=>"updated_at DESC")
    end
  end

quindi usalo nella vista:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <% Post.recent(20).each do |post| %>
     ...
  <% end %>
<% end %>

Per chiarezza, potresti anche considerare di spostare il rendering di un post recente nel suo parziale:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <%= render :partial => "recent_post", :collection => Post.recent(20) %>
<% end %>

Potresti anche voler esaminare

Fragment Cache Docs

Che ti permettono di fare questo:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  ...
<% end %>

Regolatore

unless fragment_exist?("recent_posts")
  @posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end

Anche se ammetto che il problema di DRY si impenna ancora, ha bisogno del nome della chiave in due punti. Di solito lo faccio in modo simile a come suggeriva Lars, ma dipende molto dal gusto. Esistono altri sviluppatori che conosco attenersi al controllo del frammento.

Aggiornamento:

Se guardi i documenti dei frammenti, puoi vedere come si elimina la necessità del prefisso di visualizzazione:

# File vendor/rails/actionpack/lib/action_controller/caching/fragments.rb, line 33
def fragment_cache_key(key)
  ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end

Lars sottolinea davvero la possibilità che si verifichi un errore utilizzando:

unless fragment_exist?("recent_posts")

perché c'è un divario tra quando si controlla la cache e quando si utilizza la cache.

Il plugin menzionato da Jason (Interlock) lo gestisce in modo molto grazioso, supponendo che se si sta verificando l'esistenza del frammento, probabilmente si utilizzerà anche il frammento e si memorizzerà nella cache il contenuto localmente. Uso Interlock proprio per questi motivi.

proprio come un pezzo di pensiero:

nella definizione del controller dell'applicazione

def when_fragment_expired( name, time_options = nil )
        # idea of avoiding race conditions
        # downside: needs 2 cache lookups
        # in view we actually cache indefinetely 
        # but we expire with a 2nd fragment in the controller which is expired time based
        return if ActionController::Base.cache_store.exist?( 'fragments/' + name ) && ActionController::Base.cache_store.exist?( fragment_cache_key( name ) )

        # the time_fraqgment_cache uses different time options
        time_options = time_options - Time.now if time_options.is_a?( Time )

        # set an artificial fragment which expires after given time
        ActionController::Base.cache_store.write("fragments/" + name, 1, :expires_in => time_options )

        ActionController::Base.cache_store.delete( "views/"+name )        
        yield    
  end

quindi in qualsiasi azione utilizzare

    def index
when_fragment_expired "cache_key", 5.minutes
@object = YourObject.expensive_operations
end
end

in vista

cache "cache_key" do
view_code
end
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top