2012-02-03 19 views
5

Próbuję buforować ActiveRecord z powiązaniem. Problem polega na tym, że istnieje zapytanie do bazy danych podczas uzyskiwania dostępu do powiązań w pobranym rekordzie.Buforowanie ograniczone skojarzenia

Zwykle po prostu przechowywałbym w pamięci podręcznej z obciążonym ładowaniem Rails.cache.write('post', Post.includes(:comments).find(99)). To wydaje się działać, ale problem polega tylko na tym, że chcę buforować tylko ograniczony podzbiór skojarzeń, a limity są ignorowane, gdy przykłada się do nich ładowanie (na przykład: here). Tak więc Post.includes(:popular_comments).find(99) zwróci wszystkie komentarze, a nie tylko te popularne.

Więc próbowałem buforowania obiektu po lazy-loading stowarzyszenia, ale niestety pojawia się, gdy kwerenda ciągnięcie obiekty z:

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 :(

Próbowałem buforowanie klon Zamiast tego samego zapytania SQL niechciane . Próbowałem tego zarówno z implementacjami redis, jak i memcached, tym samym wynikiem. Dziwnie, ta sekwencja działa na konsoli afaict, ale proste użycie w kontrolerze lub widoku jak wyżej nie powiedzie się (np. SQL występuje).

Aktualizacja (kwiecień 2017): Chciałbym teraz powiedzieć, że to głupie założenie. Buforowanie całych obiektów jest generalnie marnotrawstwem, ponieważ wykorzystuje dużo pamięci podręcznej i jest wolne od serializacji/deserializacji. Również skojarzenia do buforowania (o które pytano w tym pytaniu) pomnażają te straty przez N. Zwykle bardziej wydajne jest po prostu buforowanie surowych identyfikatorów i fragmentów HTML.

+0

Która wersja Railsa jest uruchomiona? – Brandan

+0

@Bandan To jest na Rails 3.1 – mahemoff

+0

Czy możesz opublikować fragment dziennika pokazujący dokładnie, co SQL jest wykonywane i gdzie w żądaniu to się dzieje? Nie mogłem tego odtworzyć. – Brandan

Odpowiedz

6

Spróbuj post.popular_comments.reload

pierwsze, granice są w rzeczywistości ignorowane kiedy chętny do załadunku. Od the docs:

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

Oznacza to, jak odkrył, trzeba zmuszać stowarzyszenie do rodzica sprzeciw siebie. W moich eksperymentach nie działało post.popular_comments (ma to sens, ponieważ zwraca obiekt proxy), a co ciekawe, nie było też post.popular_comments.all. post.popular_comments(true) robi jednak lewę. Poniżej tego kodu wywołuje przeładowanie, a po prostu robi post.popular_comments.reload również pobiera skojarzenie załadowane do klasy nadrzędnej.

Nie jestem pewien, który z tych dwóch jest bardziej poprawny, post.popular_comments(true) lub post.popular_comments.reload. Oba wydają się trochę kruche, ale drugi jest milszy i lepiej wyraża twoje zamiary.

I potwierdzone, że obie te metody:

  1. przechowywane ograniczoną stowarzyszenie w memcache
  2. nie wywołać zapytanie SQL po wczytaniu z pamięci podręcznej

mój skrypt, aby zapisać wpis:

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" 

I do pobrania:

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 

Jeśli uruchomić jeden po drugim moi pokazy dziennika:

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, ... 

Mój dziennik mySQL potwierdza również, że drugi skrypt nie wywołać zapytanie do stowarzyszenia.

Zrobiono to z Rails 3.1.1

+0

Świetnie, odpowiedź, dzięki. Nie mogę tego teraz przetestować, ale zrozumiałeś problem i brzmi jak najlepsze możliwe rozwiązanie. – mahemoff

0

Nie widzę problemu opisanego w moich testach.

Code że użyłem w kontrolerze

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 

Kiedy uruchomić stronę widzę następujące oświadczenia w dzienniku, który mówi, że modele nie są coraz reloaded enter image description here Powiększ zdjęcie dostępne w https://skitch.com/aroop/g9w5t/untitled

Problem może dotyczyć Twojego pliku widoku. Skomentuj widok i sprawdź, czy problem nadal występuje.

+0

To jest problem, który widzę, gdy ograniczenie has_many jest ograniczone. Podałem więcej informacji w pytaniu. – mahemoff

+1

Spróbuję zasymulować twoją sytuację, gdy dostanę chwilę. –

+0

Wielkie dzięki. To nie powinno być takie skomplikowane. Tylko ma wiele skojarzeń z limitem zdefiniowanym na skojarzeniu. – mahemoff

0

Rails 5 nie obsługuje :limit => 20 opcji w ogóle. dostaniesz poniżej błędu:

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

Musisz jawnie używać post.popular_comments.limit(20).