Лучший способ объединить кеширование фрагментов и объектов для memcached и Rails - PullRequest
10 голосов
/ 24 июня 2009

Допустим, у вас есть фрагмент страницы, на котором отображаются самые последние сообщения, и срок его действия истекает через 30 минут. Я использую Rails здесь.

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

Очевидно, что вам не нужно выполнять поиск в базе данных, чтобы получить самые последние сообщения, если фрагмент существует, поэтому вы также сможете избежать этих издержек.

То, что я сейчас делаю, в контроллере выглядит примерно так:

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

Это лучший способ? Это безопасно?

Одна вещь, которую я не понимаю, это то, почему ключ "recent_posts" для фрагмента и "views/recent_posts" при проверке позже, но я придумал это после просмотра memcached -vv, чтобы увидеть, что он использовал , Кроме того, мне не нравится дублирование ввода вручную "recent_posts", было бы лучше сохранить это в одном месте.

Идеи

Ответы [ 5 ]

12 голосов
/ 24 июня 2009

Плагин блокировки Эвана Уивера решает эту проблему.

Вы также можете легко реализовать что-то подобное, если вам нужно другое поведение, например, более точный контроль. Основная идея заключается в том, чтобы обернуть код вашего контроллера в блок, который фактически выполняется, только если представлению нужны эти данные:

# 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

Если вы знакомы с основами мета-программирования ruby, достаточно легко обернуть это в более чистый API на ваш вкус.

Это лучше, чем поместить код поиска прямо в представление:

  • хранит код поиска там, где его ожидают разработчики по соглашению
  • сохраняет представление в неведении относительно названия / метода модели, что позволяет больше повторного использования представления

Я думаю, что cache_fu может иметь аналогичную функциональность в одной из своих версий / вилок, но не может вспомнить конкретно.

Преимущество, которое вы получаете от memcached, напрямую связано с частотой попаданий в кеш. Будьте осторожны, чтобы не тратить емкость кеша и не допустить ненужных пропусков, кэшируя один и тот же контент несколько раз. Например, не кэшируйте одновременно набор объектов записей и их HTML-фрагменты. Обычно фрагментарное кэширование обеспечивает наилучшую производительность, но это действительно зависит от специфики вашего приложения.

3 голосов
/ 24 июня 2009

Что произойдет, если срок действия кэша истечет в промежуток времени между проверкой его в контроллере и время проверки в представлении представления?

Я бы сделал новый метод в модели:

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

затем используйте это в представлении:

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

Для ясности, вы можете также рассмотреть возможность перемещения рендеринга недавнего поста в его собственный фрагмент:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <%= render :partial => "recent_post", :collection => Post.recent(20) %>
<% end %>
1 голос
/ 03 сентября 2010

просто как кусок мысли:

в контроллере приложения определяют

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

тогда в любом действии используйте

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

в поле зрения

cache "cache_key" do
view_code
end
1 голос
/ 10 июля 2009

Ларс делает очень хорошее замечание о малой вероятности неудачи, используя:

unless fragment_exist?("recent_posts")

потому что существует разрыв между проверкой кэша и использованием кэша.

Плагин, который Джейсон упоминает (Interlock), обрабатывает это очень изящно, предполагая, что если вы проверяете наличие фрагмента, то вы, вероятно, также будете использовать фрагмент и, таким образом, кэшировать содержимое локально. Я использую блокировку по этим самым причинам.

1 голос
/ 24 июня 2009

Вы также можете посмотреть на

Документы кэша фрагмента

, которые позволяют вам сделать это:

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

Контроллер

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

Хотя я признаю, что проблема DRY все еще поднимает свою голову, нуждаясь в названии ключа в двух местах. Я обычно делаю это подобно тому, как предложил Ларс, но это действительно зависит от вкуса. Другие разработчики, которых я знаю, придерживаются проверки фрагмента.

Обновление:

Если вы посмотрите на фрагмент документа, вы увидите, как он избавляется от необходимости префикса представления:

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