2012-02-03 15 views
5

Sto tentando di memorizzare nella cache un ActiveRecord con la sua associazione. Il problema è che c'è una query del database quando si accede alle associazioni su un record recuperato.Associazione limitata di cache

Normalmente, mi limito a memorizzare nella cache con il caricamento urgente Rails.cache.write('post', Post.includes(:comments).find(99)). Questo sembra funzionare, ma il problema è che voglio solo memorizzare nella cache un sottoinsieme limitato dell'associazione, e i limiti vengono ignorati quando si carica con il eager (ad esempio, here). Quindi Post.includes(:popular_comments).find(99) restituirebbe tutti i commenti, non solo quelli più popolari.

Così ho cercato la memorizzazione nella cache l'oggetto dopo il pigro a caricamento l'associazione, ma una query purtroppo si verifica quando si tira fuori gli oggetti:

class Post < ActiveRecord::Base 
    has_many :comments 
    has_many :popular_comments, :class_name > 'Comment', :limit => 20, :order => :votes 

post = Post.find(99) 
post.popular_comments # lazy-load limited associations 
Rails.cache.write('post', post) 
... 
Rails.cache.read('post').popular_comments # Unwanted SQL query :(

ho provato la memorizzazione nella cache di un clone, invece, stessa query SQL indesiderato . E ho provato questo con entrambe le implementazioni redis e memcached, stesso risultato. Stranamente, questa sequenza funziona su console afaict, ma un semplice utilizzo in un controller o una vista come sopra non riesce (ad es. SQL si verifica).

Aggiornamento (aprile 2017): Direi che questa è una premessa stupida. Memorizzare nella cache gli oggetti interi è generalmente uno spreco poiché utilizza molta memoria cache ed è lento serializzarli/deserializzarli. Anche le associazioni di memorizzazione nella cache (come richiesto in questa domanda) moltiplicano lo spreco di N. Solitamente è più efficiente archiviare solo ID grezzi e frammenti HTML.

+0

Quale versione di Rails stai utilizzando? – Brandan

+0

@Brandan Questo è su Rails 3.1 – mahemoff

+0

Puoi pubblicare uno snippet del log che mostra esattamente ciò che SQL viene eseguito e dove si trova nella richiesta? Non potrei riprodurlo – Brandan

risposta

6

Prova post.popular_comments.reload

In primo luogo, i limiti vengono infatti ignorati quando desiderosi di carico. Da the docs:

If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects

Questo significa, come hai scoperto, è necessario forzare l'associazione nella capogruppo opporsi da soli. Nei miei esperimenti, post.popular_comments non ha funzionato (questo ha senso, dal momento che restituisce un oggetto proxy), e curiosamente né lo ha fatto post.popular_comments.all. post.popular_comments(true) fa il trucco, tuttavia. Sotto quel codice si chiama reload, e semplicemente facendo post.popular_comments.reload si ottiene anche l'associazione caricata nella classe genitore.

Non sono sicuro quale di questi due sia più corretto, post.popular_comments(true) o post.popular_comments.reload. Entrambi sembrano un po 'friabili, ma il secondo si legge meglio ed esprime più chiaramente le tue intenzioni.

ho convalidato che entrambi questi metodi:

  1. memorizzati l'associazione limitata in memcache
  2. non ha innescato una query SQL dopo aver caricato dalla cache

mio script per archiviare il messaggio:

require 'pp' 
Rails.cache.clear 
post = Post.first 

#post.popular_comments(true) 
post.popular_comments.reload 

Rails.logger.info "writing to cache" 
s = Rails.cache.write "post", post 
Rails.logger.info "writing to cache done" 

E per recuperare:

require 'pp' 
Rails.logger.info "reading from cache" 
post = Rails.cache.read "post" 
Rails.logger.info "reading from cache done" 
Rails.logger.info post.popular_comments.inspect 

Se corro uno dopo l'altro i miei spettacoli di registro:

Post Load (0.5ms) SELECT `posts`.* FROM `posts` LIMIT 1 
    Comment Load (0.5ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` = 1 ORDER BY votes LIMIT 20 
writing to cache 
writing to cache done 
reading from cache 
reading from cache done 
[#<Comment id: 1, ... 

mio log di MySQL conferma anche che il secondo script non attiva una query per l'associazione.

Questo è stato fatto con Rails 3.1.1

+0

Grande, risposta, grazie. Non posso testarlo adesso, ma hai capito il problema e questa sembra la migliore soluzione possibile. – mahemoff

0

Non vedo il problema che hai descritto nei miei test.

codice che ho usato nel regolatore

def show 
    unless Rails.cache.exist?('faq_category') 
    @faq_category = Faq::Category.first 
    @faq_category.questions 
    Rails.cache.write('faq_category', @faq_category) 
    end 
    @faq_category = Rails.cache.read('faq_category') 
end 

Quando eseguo pagina compaiono i seguenti dichiarazioni nel registro che dice che i modelli non sono sempre ricaricati immagine enter image description here formato sono a disposizione presso https://skitch.com/aroop/g9w5t/untitled

Il problema potrebbe essere con il tuo file di visualizzazione. Commenta la vista e vedi se il problema è ancora lì.

+0

Questo è un problema che sto vedendo dove un'associazione has_many è limitata. Ho messo più informazioni nella domanda. – mahemoff

+1

Proverò a simulare la tua situazione quando avrò un momento –

+0

Molte grazie. Non dovrebbe essere così complicato. Solo un'associazione has_many con un limite definito sull'associazione. – mahemoff

0

Rails 5 non supporta :limit => 20 opzione a tutti. si otterrà sotto l'errore:

ArgumentError: Unknown key: :limit. Valid keys are: :class_name, :anonymous_class, :foreign_key, :validate, :autosave, :table_name, :before_add, :after_add, :before_remove, :after_remove, :extend, :primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors

Devi usare post.popular_comments.limit(20) in modo esplicito.

Problemi correlati