23

在研究了 DHH 和其他关于基于键的缓存过期和俄罗斯娃娃缓存的博客文章之后,我仍然不确定如何处理一种关系类型。具体来说,是一种has_many关系。

我将在示例应用程序上分享我的研究结果。这是一个小故事,所以请稍等。假设我们有以下 ActiveRecord 模型。我们只关心模型的适当更改cache_key,对吗?

class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  belongs_to :author
end

class Comment < ActiveRecord::Base
  attr_accessible :article_id, :author_id, :body
  belongs_to :author
  belongs_to :article, touch: true
end

class Author < ActiveRecord::Base
 attr_accessible :name
  has_many :articles
  has_many :comments
end

我们已经有一篇文章,有一条评论。两者都出自不同的作者。目标是cache_key在以下情况下对文章进行更改:

  1. 文章的正文或标题更改
  2. 其评论的正文发生了变化
  3. 文章作者姓名变更
  4. 文章评论作者姓名变更

所以默认情况下,我们适用于案例 1 和 2。

1.9.3-p194 :034 > article.cache_key
 => "articles/1-20130412185804"
1.9.3-p194 :035 > article.comments.first.update_attribute('body', 'First Post!')
1.9.3-p194 :038 > article.cache_key
 => "articles/1-20130412185913"

但不适用于案例 3。

1.9.3-p194 :040 > article.author.update_attribute('name', 'Adam A.')
1.9.3-p194 :041 > article.cache_key
 => "articles/1-20130412185913"

让我们cache_keyArticle.

class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  belongs_to :author

  def cache_key
    [super, author.cache_key].join('/')
  end
end

1.9.3-p194 :007 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190438"
1.9.3-p194 :008 > article.author.update_attribute('name', 'Adam B.')
1.9.3-p194 :009 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190849"

赢!但这当然不适用于案例 4。

1.9.3-p194 :012 > article.comments.first.author.update_attribute('name', 'Bernard A.')
1.9.3-p194 :013 > article.cache_key
 => "articles/1-20130412185913/authors/1-20130412190849"

那么还有哪些选择呢?我们可以对 上的has_many关联做一些事情Author,但has_many没有{touch: true}选择,可能是有原因的。我想它可以按照以下方式实现。

class Author < ActiveRecord::Base
  attr_accessible :name
  has_many :articles
  has_many :comments

  before_save do
    articles.each { |record| record.touch }
    comments.each { |record| record.touch }
  end
end

article.comments.first.author.update_attribute('name', 'Bernard B.')
article.cache_key
  => "articles/1-20130412192036"

虽然这确实有效。它通过加载、实例化和更新每篇文章和另一篇文章的评论,对性能产生巨大影响。我不相信这是一个正确的解决方案,但什么是?

当然 37signals 用例/示例可能会有所不同:project -> todolist -> todo. 但我想象一个单一的待办事项也属于用户。

如何解决这个缓存问题?

4

2 回答 2

8

我偶然发现的一种方法是通过缓存键来处理这个问题。为文章的评论者添加has_many_through关系:

class Article < ActiveRecord::Base
  attr_accessible :author_id, :body, :title
  has_many :comments
  has_many :commenters, through: :comments, source: :author
  belongs_to :author
end

然后在文章/节目中,我们将像这样构造缓存键:

<% cache [@article, @article.commenters, @article.author] do %>
  <h2><%= @article.title %></h2>
  <p>Posted By: <%= @article.author.name %></p>
  <p><%= @article.body %></p>
  <ul><%= render @article.comments %></ul>
<% end %>

诀窍在于,commenters无论何时添加、删除或更新评论,关联生成的缓存键都会发生变化。虽然这确实需要额外的 SQL 查询来生成缓存键,但它可以很好地与 Rails 的低级缓存配合使用,并且添加诸如identity_cache gem之类的东西可以很容易地帮助解决这个问题。

我想看看其他人是否有更清洁的解决方案。

于 2013-06-06T04:31:48.097 回答
0

正如这里建议的https://rails.lighthouseapp.com/projects/8994/tickets/4392-add-touch-option-to-has_many-associations,在我的例子中,我刚刚创建了一个 after_save 回调来更新相关对象的时间戳。

  def touch_line_items_and_tactics
    self.line_item_advertisements.all.map(&:touch)
  end

顺便说一句,我们在一个遗留数据库上构建了我们的 rails 应用程序,该数据库具有last_modified_time列名,它的语义是“用户上次修改它的时间”。因此,由于语义不同,我们无法使用:touch开箱即用的选项。我不得不像https://gist.github.com/tispratik/9276110这样对 cache_key 和 touch 方法进行猴子补丁,以便将更新的时间戳存储在 memcached 而不是数据库的 updated_at 列中。

另请注意,我不能使用cache_timestamp_formatRails 的默认设置,因为它提供的时间戳最多只有几秒钟。我觉得需要一个更细粒度的时间戳,所以我选择了:nsec(纳秒)。

带有 cache_timestamp_format 的时间戳:20140227181414
带有 nsec 的时间戳:20140227181414671756000

于 2014-02-28T17:59:30.433 回答