سؤال

I know dalli (caching) is pretty powerful plugin to enhance performance for static pages.
But what about dynamic pages with pagination, which are updated quite often?
What is the correct way to set up dalli?

One problem I've encountered for example: dalli recognizes different params[:page] as the same page when using pagination:(

How would you guys design the system when using dalli for both

  • the page that gets updated so often
  • the page that won't be updated so often

For example. I have 4 models such as User, Community, Topic, and Comment(defined Polymorphic to both Community, and Topic. They can be created or shown in #show pages)

It's like

  • Community > (has_many) > Topics > (has_many) > Comments
  • Community > (has_many) > Comments
  • All of them belongs to user(creator)

My current codes are just like this(This is quite bit long. Sorry about that)
I'm facing the pagination problem when using caching...

-------------------------------------Settings----------------------------------------

config/environments/development.rb

config.consider_all_requests_local       = true
config.action_controller.perform_caching = true
config.cache_store = :dalli_store

routes.rb

resources :communities do
    resources :topics do
        resources :comments do
    end
end

-------------------------------------Community----------------------------------------

controllers/communities_controller.rb#index&show

caches_page :show
caches_action :edit, :new

def index
    ....
    if params[:sort] == 'new'
        @communities = Community.scoped.page(params[:page]).order("created_at DESC")
    elsif params[:sort] = 'popular'
        @communities = Community.scoped.page(params[:page]).order("follow_count DESC")
    elsif params[:sort] = 'reputation'
        @communities = Community.scoped.page(params[:page]).order("reputation_count DESC")
    else
        @communities = Community.scoped.page(params[:page]).order("created_at DESC")
    end
    ....
end

def show
    @community = Community.find(params[:community_id])
    @comments @community.comments.page(params[:page]
    @topics = @community.topics.limit(10)
end

views/communities/index.html.erb note: However, the content will be the same even if I move to next params[:page]:( It seems caching recognize different page as the same contents...

#here, the url will could be something like this example.com/communities?utf8=✓&location=14&genre=3&search=strawberry
 But this is going to create gigantic petterns of the caches:(  
 So I want to make it work only when params[:sort] was not empty. Because, no other parameters come with when params[:sort] is not empty. 
 It could be like, example.com/communities?sort=new, example.com/communities?sort=popular, example.com/communities?sort=reputation


...
<% if params[:sort] %>
    <% @key = "community_index_" + params[:sort] + params[:page] %>

    <% cache(:controller => "communities", :action => "index", :action_suffix => @key) do %>
        <%= page_entries_info(@communities, :entry_name => 'community').html_safe %>
        <%= paginate @communities, :window => 4 %>

        <% @communities.each do |community| %>  
            <%= render 'communities/community', :community => community %>
        <% end %>
    <% end %>
<% end %>
...

views/communities/show.html.erb

...
<% @key = params[:community_name] + "_community_show_information" %>
<% cache(:controller => "communities", :action => "show", :action_suffix => @key) do %>
    <h2>Information</h2>
    <div class="CommunityInformation">
        <%= render 'communities/information'%>
    </div>
<% end %>


<% @key = params[:community_name] + "_community_show_wall_" + params[:page] + %>
<% cache(:controller => "communities", :action => "show", :action_suffix => @key) do %>
<%= paginate @comments, :window => 4 %>
    <h2>Topic</h2>
    <div class="WallInformation">
        <%  @comments.eager.recent.each do |comment| %>
            <%= render 'communities/comment', :comment => comment %>
        <% end %>
    </div>
<% end %>

<% @key = params[:community_name] + "_community_show_topics" %>
<% cache(:controller => "communities", :action => "show", :action_suffix => @key) do %>
    <h2>Topic</h2>
    <div class="TopicInformation">
        <%  @topics.eager.recent.each do |topic| %>
            <%= render 'communities/topic', :topic => topic %>
        <% end %>
    </div>
<% end %>
...

models/community_sweeper.rb

class CommunitySweeper < ActionController::Caching::Sweeper
  observe Community


  def after_save(record)
    expire_fragment(url_for(:action => 'index', :only_path => true) + '?????(all the caches related to the community#index)')
    expire_fragment(url_for(:action => 'show', :only_path => true) + '?????(the cache related to the particular community#show)')
  end
end

end

-------------------------------------Topic----------------------------------------

controllers/topics_controller.rb#index&show

caches_page :show
caches_action :edit, :new

def index
    ....
    @community = Community.find(params[:community_id])
    @topics = @community.topics.page(params[:page]
    ....
end

def show
    @topic = Topic.find(params[:id])
    @comments = @topic.comments.page(params[:page]
end

views/topics/index.html.erb

...
<% @key = params[:community_id] + "_topic_index_"  + params[:page] %>
    <% cache(:controller => "topics", :action => "index", :action_suffix => @key) do %>
        <%= page_entries_info(@communities, :entry_name => 'community').html_safe %>
        <%= paginate @communities, :window => 4 %>

        <% @communities.each do |community| %>  
            <%= render 'communities/community', :community => community %>
        <% end %>
    <% end %>
<% end %>
...

views/topics/show.html.erb

...    
<% @key = params[:community_name] + "_topic_show_" + params[:id] + "_comments" + params[:page] %>
    <% cache(:controller => "topics", :action => "show", :action_suffix => @key) do %>
         <%= paginate @comments, :window => 4 %>
         <%= page_entries_info(@comments, :entry_name => 'comment').html_safe %>

         <h2>Comment</h2>
         <div class="CommentInformation">
         <% @comments.each do |comment| %>
             <%= render 'topics/comment', :comment => comment %>
         <% end %>
    </div>
<% end %>
...

models/topic_sweeper.rb

class TopicSweeper < ActionController::Caching::Sweeper
  observe Topic


  def after_save(record)
    expire_fragment(url_for(:action => 'index', :only_path => true) + '?????(all the caches related to the topic#index)')
    expire_fragment(url_for(:action => 'show', :only_path => true) + '?????(the cache related to the particular topic#show)')
  end
end

-------------------------------------Comment----------------------------------------

models/comment_sweeper.rb

class CommentSweeper < ActionController::Caching::Sweeper
  observe Comment


  def after_save(record)
    expire_fragment(url_for(:action => 'index', :only_path => true) + '?????(all the caches related to the topic#index)')
    expire_fragment(url_for(:action => 'show', :only_path => true) + '?????(the cache related to the particular topic#show)')
  end
end
هل كانت مفيدة؟

المحلول

I am following on on from my answer to

How should I set up dalli for a dynamic page with lots of content updates?

Please note that I am not an expert but I did try your approach and abandoned it as it just kept getting more and more complex as my application grew and I went for the cache key fragment based approach mentioned in the last question. There are a few things to bear in mind.

  1. The cache sweeper approach requires you to make sure that the sweeper is up to date as you make changes to your application which means extra maintenance and testing work.

  2. In a dynamic application you probably wont be able to easily cache whole pages especially if the views show information from many models.

  3. You wont be able to deal with the paging issue unless the page parameter becomes part of your cache keys.

  4. When your cache gets full, memcached will simply remove the least used / oldest cache items when putting in a new cache fragment.

  5. So there is no issue with you just pushing in a new cache fragments when your models change and letting memcached clear out the old out of date cache items.

Therefore fragment caching in your views will probably be the easiest solution as it will deal with paging and you wont have to deal with manual cache invalidation

So looking at one of your views, what I might do is

<% if params[:sort] %>

        <%= page_entries_info(@communities, :entry_name => 'community').html_safe %>
        <%= paginate @communities, :window => 4 %>

        <% @communities.each do |community| %>  
            <% cache(community, :suffix => "community_index") do 
                <%= render 'communities/community', :community => community %>
            <% end %>
        <% end %>
<% end %>

What I have done is cache the rendering of each community record and paging becomes irrelavent

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top