Rails cache_key在model.reload之前和之后返回不同的值

时间:2018-01-09 16:05:37

标签: ruby-on-rails

我在Rails 4.2.8上。

我们说我的模型Contactcontact = Contact.new

调用contact.cache_key会返回带时间戳的密钥,如下所示:

"触点/ 2615608-20180109154442000000000"

其中2615608是ID,20180109154442000000000是时间戳。

我在控制器中看到了一种奇怪的行为。

contact.update(contact_params)之后,如果我拨打contact.cache_key我有不同的时间戳,那么我打电话给contact.reload.cache_key

奇怪的是,我100%确定没有其他更新发生在模型updated_at上(通过检查控制台的SQL更新语句,只有一个正在改变updated_at

的电话

如果我在控制器中执行类似的操作,那就太奇怪了:

contact.udpate(contact_params)
Rails.logger.info "Updated_at before reload is #{@contact.updated_at} and it's cache_key is #{@contact.cache_key}"
Rails.logger.info "Updated_at after reload is #{@contact.reload.updated_at} and it's cache_key is #{@contact.reload.cache_key}"

输出显示IDENTICAL updated_at值,但不同的cache_key值:

Updated_at before reload is 2018-01-09 14:01:58 -0200 and it's cache_key is contacts/2615608-20180109160158143423000
Updated_at after reload  is 2018-01-09 14:01:58 -0200 and it's cache_key is contacts/2615608-20180109160158000000000

如您所见,相同的updated_at,但不同的时间戳。

这让我疯了。我讨厌必须手动。重新加载模型。为什么会这样?尝试查看cache_key的源代码,但无法在那里找到答案,因为它显然只取决于updated_at值(相同)。

1 个答案:

答案 0 :(得分:0)

发现了这个问题。 这是一个Rails错误,已在Rails 5.0中修复。

可以找到Rails 4.2 .cache_key的源代码here。如您所见,它使用cache_timestamp_format = :nsec,这太精确了。

我能理解的原因是,在重新加载之前,模型的updated_at仍然在内存中,因此它具有足够的分辨率(在内存中)以返回非常精确的纳秒键。但是在model.reload之后,updated_at来自数据库,它没有那么高的分辨率,所以这就是为什么cache_key时间戳产生不同的数字,最后有很多零(在我的例子,20180109160158 000000000 )。

This issue使用过于精确(纳秒)的时间戳详细说明问题,合并this pull request以解决问题,将精度从:nsec更改为:usec。

the issue discussion上方(上面的相同链接)@tarzan通过使用以下代码创建ActiveSupport::Concern来提出修复建议:

  included do
    self.cache_timestamp_format = :usec
  end

并将:usec定义为初始化程序中的格式化程序:

Time::DATE_FORMATS[:usec] = "%Y%m%d%H%M%S%6N"

然后手动将您的关注点插入到您想要手动使用的模型中.cache_key(顺便说一句,我使用的是根据oficial Rails指南推荐的用于低级缓存的方法,使用{{1} },在主题1.6低级缓存中检查http://guides.rubyonrails.org/caching_with_rails.html

问题是,至少在我的测试中(MySQL on Mac Os High Sierra),联系一个脚手架模型,其精度:datetime列(如updated_at)仅在秒级;这就是为什么他的修复不能解决它的原因,因为从纳秒变为微秒精度仍然会导致两个不同的cache_keys,并且添加的零更少。举个例子:

Rails.cache.fetch(self.cache_key)

所以,至少现在,我很遗憾地坚持.reload。

最后,但至少,我只是意识到了这个错误,因为我们有一个20个这样的联系人页面,可以通过ajax进行操作。每个ajax调用都会调用模型上的[4] pry(#<ContactsController>)> ::Time::DATE_FORMATS[:usec] = "%Y%m%d%H%M%S%6N" => "%Y%m%d%H%M%S%6N" [5] pry(#<ContactsController>)> @contact.updated_at.utc.to_s(:usec) => "20180109234014062142" [6] pry(#<ContactsController>)> @contact.reload.updated_at.utc.to_s(:usec) Contact Load (2.2ms) SELECT `contacts`.* FROM `contacts` WHERE `contacts`.`id` = 2615608 LIMIT 1 /*application:Temporadalivre,controller:contacts,action:toggle_status*/ => "20180109234014000000" ,并将模型.update返回给视图。自调用.as_cached_json以来,我们希望通过重新加载页面,所有内容都已经被缓存,但事实并非如此,只有通过检查缓存键才能发现这个错误,这给我们带来了非常好的性能提升,失去了。