如何使低级缓存与Rails中的关联缓存协作? [英] How to make Low-Level caching collaborate with Association caching in Rails?

查看:51
本文介绍了如何使低级缓存与Rails中的关联缓存协作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用Rails 5进行项目.我想提高性能,所以我决定像下面这样使用低级缓存:

  class用户<应用记录has_one:个人资料def cached_profileRails.cache.fetch(['Users',id,'profile',Updated_at.to_i])做轮廓结尾结尾结尾类配置文件<应用记录属地:用户,触摸:真结尾 

它分别工作正常.但是现在我想使两个缓存相互协作.我想要的是,一旦从缓存存储区中检索了关联的对象,就不需要从数据库中检索它了.

  irb>u = User.takeirb>u.cached_profile#从redis获取.我可以在`cached_profile`中设置关联缓存吗?irb>u.profile#从数据库中获取配置文件加载(1.4ms)在配置文件"中从配置文件"中选择配置文件"."user_id" = $ 1 LIMIT $ 2 [["id",1],["LIMIT",1]]irb>u.profile#从关联缓存中获取 

u.profile 不应该从数据库中获取,因为它已经从redis中获取了,如何实现呢?

更新1:

我发现在 ActiveRecord :: Base 的实例中有一个实例变量 @association_cache ,该变量存储缓存的关联并确定是否应从数据库中检索关联

我想我可以做类似 user.instance_variable_get(:@ association_cache)['profile'] = cached_profile 的事情来使其工作.但是 @association_cache 中的值是 ActiveRecord :: Associations :: HasOneAssociation 的实例,我不知道如何构建 user 当前.

解决方案

ActiveRecord :: Base实例中有一个实例变量@association_cache,用于存储缓存的关联并确定是否应从数据库中检索关联.>

我们可以使用 @association_cache 来实现它,例如:

  class用户<应用记录has_one:个人资料def cached_profile缓存= Rails.cache.fetch(['Users',id,'profile',Updated_at.to_i])做轮廓结尾反射= self.class.reflect_on_association(:profile)如果association_instance_get(name).nil?关联= Reflection.association_class.new(自我,反射)association.target =缓存association_instance_set(:profile,关联)结尾快取结尾结尾类配置文件<应用记录属地:用户,触摸:真结尾 

现在,如果已经从缓存存储中检索到数据,我们可以避免从数据库中重新获取数据.

  irb>u = User.takeirb>u.cached_profile#从数据库中获取配置文件并使用redis对其进行缓存配置文件加载(1.4ms)在配置文件"中从配置文件"中选择配置文件"."user_id" = $ 1 LIMIT $ 2 [["id",1],["LIMIT",1]]irb>u.profile#使用redis的缓存版本irb>u.profile.reload#从数据库重新加载配置文件加载(1.4ms)在配置文件"中从配置文件"中选择配置文件"."user_id" = $ 1 LIMIT $ 2 [["id",1],["LIMIT",1]] 

更新:

为此,我构建了一个 cache_associations .

  class用户<应用记录包括CacheAssociationshas_one:个人资料cache_association:profile结尾 

它在做同样的事情,但是它整洁并且易于使用.

I am currently working on a project with Rails 5. I want to boost the performance, so I decided to use Low-Level caching like the following:

class User < ApplicationRecord
  has_one :profile

  def cached_profile 
    Rails.cache.fetch(['Users', id, 'profile', updated_at.to_i]) do
      profile
    end
  end
end

class Profile < ApplicationRecord
  belongs_to :user, touch: true
end

It works fine respectively. But now I want to make the two caches to collaborate. What I want is that the associated object doesn't need to be retrieved from the database once it is retrieved from the cache store(redis here).

irb> u = User.take
irb> u.cached_profile # fetch from the redis. Can I set the association caching in `cached_profile`?
irb> u.profile # fetch from the database
  Profile Load (1.4ms) SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
irb> u.profile # fetch from the association caching

u.profile shouldn't fetch from the database because of it's already retrived from the redis, how to achieve this?

Update 1:

I found that there's an instance variable @association_cache in the instance of ActiveRecord::Base, which stores cached associations and determines if an association should be retrieved from the database.

I think I could do something like user.instance_variable_get(:@association_cache)['profile'] = cached_profile to make it work. But the value in the @association_cache is an instance of ActiveRecord::Associations::HasOneAssociation and I don't know how to build the user to it currently.

解决方案

There's an instance variable @association_cache in the instance of ActiveRecord::Base, which stores cached associations and determines if an association should be retrieved from the database.

We can achieve it with @association_cache like:

class User < ApplicationRecord
  has_one :profile

  def cached_profile
    cache = Rails.cache.fetch(['Users', id, 'profile', updated_at.to_i]) do
      profile
    end

    reflection = self.class.reflect_on_association(:profile)
    if association_instance_get(name).nil?
      association = reflection.association_class.new(self, reflection)
      association.target = cache
      association_instance_set(:profile, association)
    end

    cache
  end
end

class Profile < ApplicationRecord
  belongs_to :user, touch: true
end

Now we can avoid to refetch data from the database if it has been retrieved from the cache store.

irb> u = User.take
irb> u.cached_profile # fetch the profile from the database and use the redis to cache it
  Profile Load (1.4ms) SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
irb> u.profile # use the cached version from the redis
irb> u.profile.reload # reload from the database
  Profile Load (1.4ms) SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

UPDATE:

I built a gem cache_associations for this.

class User < ApplicationRecord
  include CacheAssociations

  has_one :profile
  cache_association :profile
end

It's doing the same thing, but it's neat and much easier to use.

这篇关于如何使低级缓存与Rails中的关联缓存协作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆