Rails 3 和 Memcached - 智能缓存,永不过期

发布于 2024-10-30 23:08:58 字数 1944 浏览 0 评论 0原文

我正在通过 Memcached 在我的 Rails 项目中实现缓存,特别是尝试缓存侧列块(最新的照片、博客等),目前我让它们每 15 分钟左右使缓存过期。这是可行的,但如果我可以做到更新,比如每当添加、更新新内容或诸如此类的时候,那就更好了。

我正在观看 Memcached 上的 Scaling Rails 截屏视频 http:// /content.newrelic.com/railslab/videos/08-ScalingRails-Memcached-fixed.mp4,以及 8:27在视频中,Gregg Pollack 谈论了 Memcached 中的智能缓存,其中智能密钥(在本例中为 update_at 时间戳)用于替换以前缓存的项目,而不必使缓存过期。因此,我认为,每当更新时间戳时,缓存就会刷新,因为它会寻求新的时间戳。

在本示例中,我使用“最近的照片”侧栏,这就是它的设置方式...

_side-column.html.erb:

<div id="photos"">
   <p class="header">Photos</p>
   <%= render :partial => 'shared/photos', :collection => @recent_photos %>     
</div>

_photos.html.erb

<% cache(photos) do %>
  <div class="row">
    <%= image_tag photos.thumbnail.url(:thumb) %>
    <h3><%= link_to photos.title, photos %></h3>
    <p><%= photos.photos_count %> Photos</p>
  </div>
</div>

<%结束%>

第一次运行时,Memcached 将该块缓存为views/photos/1-20110308040600,并在页面刷新时重新加载缓存的片段,到目前为止一切顺利。然后,我将额外的照片添加到后端的该特定行并重新加载,但照片计数未更新。日志显示它仍在从views/photos/1-20110308040600加载并且没有获取更新的时间戳。我所做的一切似乎都与视频中所做的相同,我上面做错了什么?

此外,这个问题还有第二部分。正如您在上面的部分中看到的,为集合调用 @recent_photos 查询(在我的 lib 文件夹中的模块之外)。然而,我注意到即使该块被缓存,这个 SELECT 查询仍然被调用。我首先尝试将整个部分包装在一个块中,如 <%cache(@recent_photos) do %>,但显然这不起作用 - 特别是因为整个集合上没有真正的时间戳,只是它是单独的项目当然。如果结果已缓存,如何防止进行此查询?

更新 在参考第二个问题时,我发现除非Rails.cache.exist?可能只是我的票,但棘手的是使用时间戳的通配符性质......

更新2 完全忽略我的第一个问题,我确切地弄清楚了缓存不刷新的原因。那是因为 Updated_at 字段没有被更新。原因是我要添加/删除一个项目,该项目是父级中的嵌套资源,并且我可能需要对其实施“触摸”,以便更新父级中的 Updated_at 字段。

但我的第二个问题仍然存在......即使片段被缓存,主@recent_photos查询仍然被调用......有没有办法使用cache.exists?目标名为 /views/photos/1-2011random 之类的缓存?

I am implementing caching into my Rails project via Memcached and particularly trying to cache side column blocks (most recent photos, blogs, etc), and currently I have them expiring the cache every 15 minutes or so. Which works, but if I can do it more up-to-date like whenever new content is added, updated or whatnot, that would be better.

I was watching the episode of the Scaling Rails screencasts on Memcached http://content.newrelic.com/railslab/videos/08-ScalingRails-Memcached-fixed.mp4, and at 8:27 in the video, Gregg Pollack talks about intelligent caching in Memcached in a way where intelligent keys (in this example, the updated_at timestamp) are used to replace previously cached items without having to expire the cache. So whenever the timestamp is updated, the cache would refresh as it seeks a new timestamp, I would presume.

I am using my "Recent Photos" sideblock for this example, and this is how it's set up...

_side-column.html.erb:

<div id="photos"">
   <p class="header">Photos</p>
   <%= render :partial => 'shared/photos', :collection => @recent_photos %>     
</div>

_photos.html.erb

<% cache(photos) do %>
  <div class="row">
    <%= image_tag photos.thumbnail.url(:thumb) %>
    <h3><%= link_to photos.title, photos %></h3>
    <p><%= photos.photos_count %> Photos</p>
  </div>
</div>

<% end %>

On the first run, Memcached caches the block as views/photos/1-20110308040600 and will reload that cached fragment when the page is refreshed, so far so good. Then I add an additional photo to that particular row in the backend and reload, but the photo count is not updated. The log shows that it's still loading from views/photos/1-20110308040600 and not grabbing an updated timestamp. Everything I'm doing appears to be the same as what the video is doing, what am I doing wrong above?

In addition, there is a part two to this question. As you see in the partial above, @recent_photos query is called for the collection (out of a module in my lib folder). However, I noticed that even when the block is cached, this SELECT query is still being called. I attempted to wrap the entire partial in a block at first as <% cache(@recent_photos) do %>, but obviously this doesn't work - especially as there is no real timestamp on the whole collection, just it's individual items of course. How can I prevent this query from being made if the results are cached already?

UPDATE
In reference to the second question, I found that unless Rails.cache.exist? may just be my ticket, but what's tricky is the wildcard nature of using the timestamp...

UPDATE 2
Disregard my first question entirely, I figured out exactly why the cache wasn't refreshing. That's because the updated_at field wasn't being updated. Reason for that is that I was adding/deleting an item that is a nested resource in a parent, and I probably need to implement a "touch" on that in order to update the updated_at field in the parent.

But my second question still stands...the main @recent_photos query is still being called even if the fragment is cached...is there a way using cache.exists? to target a cache that is named something like /views/photos/1-2011random ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

彩扇题诗 2024-11-06 23:08:58

Rails 缓存的主要缺陷之一是您无法可靠地将缓存组件的控制器和视图分开。我发现的唯一解决方案是直接将查询嵌入到缓存块中,但最好通过辅助方法。

例如,您可能有这样的情况:

class PhotosController < ApplicationController
  def index
    # ...

    @recent_photos = Photos.where(...).all

    # ...
  end
end

第一直觉是仅在视图需要时才运行该查询,例如测试缓存内容是否存在。不幸的是,内容有可能会在您测试缓存和实际渲染页面之间的时间间隔内过期,这会在 nil 值 @recent_photos 时导致模板渲染错误被使用。

这是一种更简单的方法:

<%= render :partial => 'shared/photos', :collection => recent_photos %>

不使用实例变量,而是使用辅助方法。定义您的帮助器方法,就像在控制器内加载一样:

module PhotosHelper
  def recent_photos 
    @recent_photos ||= Photos.where(...).all
  end
end

在这种情况下,会保存该值,以便对同一帮助器方法的多次调用只会触发一次查询。这在您的应用程序中可能不是必需的,可以省略。毕竟,该方法必须做的就是返回“最近照片”列表。

如果 Rails 支持子控制器及其自己的关联视图(这是此处使用的模式的变体),则可以消除很多混乱。

One of the major flaws with Rails caching is that you cannot reliably separate the controller and the view for cached components. The only solution I've found is to embed the query in the cached block directly, but preferably through a helper method.

For instance, you probably have something like this:

class PhotosController < ApplicationController
  def index
    # ...

    @recent_photos = Photos.where(...).all

    # ...
  end
end

The first instinct would be to only run that query if it will be required by the view, such as testing for the presence of the cached content. Unfortunately there is a small chance that the content will expire in the interval between you testing for it being cached and actually rendering the page, something that will lead to a template rendering error when the nil-value @recent_photos is used.

Here's a simpler approach:

<%= render :partial => 'shared/photos', :collection => recent_photos %>

Instead of using an instance variable, use a helper method. Define your helper method as you would've the load inside the controller:

module PhotosHelper
  def recent_photos 
    @recent_photos ||= Photos.where(...).all
  end
end

In this case the value is saved so that multiple calls to the same helper method only triggers the query once. This may not be necessary in your application and can be omitted. All the method is obligated to do is return a list of "recent photos", after all.

A lot of this mess could be eliminated if Rails supported sub-controllers with their own associated views, which is a variation on the pattern employed here.

疏忽 2024-11-06 23:08:58

自从提出这个问题以来,我一直在进一步研究缓存,我想我开始准确地理解这种缓存技术的价值。

例如,我有一篇文章,通过页面所需的各种操作(包括查询其他表),也许我需要为每篇文章执行五到七个不同的查询。但是,以这种方式缓存文章会将所有这些查询减少为一个。

我假设使用这种技术,总是需要至少有“一个”查询,因为需要有“某种”方法来判断时间戳是否已更新。

As I've been working further with caching since asking this question, I think I'm starting to understand exactly the value of this kind of caching technique.

For example, I have an article and through a variety of things I need for the page which include querying other tables, maybe I need to do five-seven different queries per article. However, caching the article in this way reduces all those queries to one.

I am assuming that with this technique, there always needs to have at least "one" query, as there needs to be "some" way to tell whether the timestamp has been updated or not.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文