Rails低级缓存:当ActiveRecord对象updated_at更改或将新对象添加到集合时更新缓存

Don*_*ato 4 ruby caching ruby-on-rails

Rails附带了Fragment Caching和Low-Level Caching.片段缓存的工作方式非常清楚:

Rails将使用唯一键编写新的缓存条目.如果updated_at的值已更改,则将生成新密钥.然后Rails会为该密钥写一个新的缓存,并且永远不会再次使用写入旧密钥的旧缓存.这称为基于密钥的过期.当视图片段改变时(例如,视图中的HTML改变),高速缓存片段也将过期.像Memcached这样的缓存存储将自动删除旧的缓存文件.

<% @products.each do |product| %>
  <% cache product do %>
   <%= render product %>
  <% end %>
<% end %>
Run Code Online (Sandbox Code Playgroud)

因此,视图将被缓存,当与视图关联的ActiveRecord对象的updated_at发生更改或html发生更改时,将创建新缓存.可以理解的.

我不想缓存视图.我想缓存从ActiveRecord查询构建的Hash集合.我知道Rails有SQL缓存,它在一个请求中使用时缓存同一个查询的结果.但是我需要在多个请求中提供结果,并且只有在对象之一更新update_at或将新对象添加到Hash集合时才会更新.

低级缓存缓存特定值或查询结果,而不是缓存视图片段.

def get_events
  @events = Event.search(params)

  time = Benchmark.measure {
    event_data = Rails.cache.fetch 'event_data' do          
        # A TON OF EVENTS TO LOAD ON CALENDAR
        @events.collect do |event|
           {
                    title: event.title,
                    description: event.description || '',
                    start: event.starttime.iso8601,
                    end: event.endtime.iso8601,
                    allDay: event.all_day,
                    recurring: (event.event_series_id) ? true : false,
                    backgroundColor: (event.event_category.color || "red"),
                    borderColor: (event.event_category.color || "red")  
           }
        end
    end

  }
  Rails.logger.info("CALENDAR EVENT LOAD TIME: #{time.real}")

  render json: event_data.to_json
end
Run Code Online (Sandbox Code Playgroud)

但是现在我不认为如果其中一个事件被更新或者新的哈希被添加到集合中,缓存就会过期.我怎样才能做到这一点?

Pie*_*son 13

Rails.cache.fetch是一个读写快捷方式

我们试图用缓存做的是这样的:

value = Rails.cache.read( :some_key )
if value.nil?
  expected_value = ...
  Rails.cache.write( :some_key, expected_value )
end
Run Code Online (Sandbox Code Playgroud)

我们首先尝试从缓存中读取,如果没有值,那么我们从任何地方检索数据,将其添加到缓存中并执行您需要执行的任何操作.

在下一次调用时,我们将尝试再次访问:some_key缓存键,但这次它将存在,我们不需要再次检索值,我们只需要获取已经缓存的值.

这就是fetch所有的一切:

value = Rails.cache.fetch( :some_key ) do
  # if :some_key doesn't exist in cache, the result of this block
  # is stored into :some_key and gets returned
end
Run Code Online (Sandbox Code Playgroud)

它只不过是一个非常方便的捷径,但了解它的行为很重要.

我们如何处理低级缓存?

这里的关键(双关语)是选择一个缓存密钥,当缓存的数据与底层数据不是最新时,缓存密钥会发生变化.它通常比更新现有缓存值更容易:相反,您确保不重用已存储旧数据的缓存键.

例如,要将所有事件数据存储在缓存中,我们会执行以下操作:

def get_events
  # Get the most recent event, this should be a pretty fast query
  last_modified = Event.order(:updated_at).last
  # Turns a 2018-01-01 01:23:45 datetime into 20180101220000
  # We could use to_i or anything else but examples would be less readable
  last_modified_str = last_modified.updated_at.utc.to_s(:number) 
  # And our cache key would be
  cache_key = "all_events/#{last_modified_str}"

  # Let's check this cache key: if it doesn't exist in our cache store, 
  # the block will store all events at this cache key, and return the value
  all_events = Rails.cache.fetch(cache_key) do 
    Event.all
  end

  # do whatever we need to with all_events variable
end
Run Code Online (Sandbox Code Playgroud)

这里有什么重要的:

  • 主数据加载发生内部fetch块.每次进入此方法时都不能触发它,否则您将失去缓存的所有兴趣.
  • 钥匙的选择至关重要!它必须 :
    • 数据变得陈旧后立即更改.否则,您将使用旧数据点击"旧"缓存密钥,并且不会提供最新数据.
    • 但!确定缓存密钥的成本必须比检索缓存数据少很多,因为每次进入此方法时,您都将"计算"缓存密钥的内容.因此,如果确定缓存密钥比检索实际数据花费的时间更长,那么,也许你必须以不同的方式思考问题.

这个先前方法的一个例子

让我们get_events通过一个例子来检查我之前的方法是如何表现的.假设我们Events在您的数据库中有以下内容:

| ID | Updated_at       | 
|----|------------------|
|  1 | 2018-01-01 00:00 | 
|  2 | 2018-02-02 00:00 | 
|  3 | 2018-03-03 00:00 |
Run Code Online (Sandbox Code Playgroud)

第一次打电话

在这一点上,让我们来电话get_events.事件#3是最近更新的事件,因此Rails将检查缓存密钥all_events/20180303000000.它尚不存在,因此将从DB请求所有事件并使用此缓存密钥将其存储到缓存中.

相同数据,后续调用

如果您不更新任何这些事件,则所有下一次调用都get_events将访问all_events/20180303000000现在存在的缓存键并包含所有事件.因此,您不会点击数据库,只需使用缓存中的值.

如果我们修改一个事件怎么办?

Event.find(2).touch
Run Code Online (Sandbox Code Playgroud)

我们已经修改了事件#2,因此预先存储在缓存中的内容不再是最新的.我们现在有以下事件列表:

| ID | Updated_at       | 
|----|------------------|
|  1 | 2018-01-01 00:00 | 
|  2 | 2018-04-07 19:27 | <--- just updated :) 
|  3 | 2018-03-03 00:00 |
Run Code Online (Sandbox Code Playgroud)

下一次调用get_events将采用最近的事件(现在是#2),因此尝试访问all_events/20180407192700尚不存在的缓存键... Rails.cache.fetch将评估该块,并将当前状态中的所有当前事件放入此新密钥中all_events/20180407192700.并且您不会收到过时的数据.


你的具体问题怎么样?

您必须找到正确的缓存键,并使其在fetch块内完成事件数据加载.

由于您使用过滤事件params,缓存将取决于您的参数,因此您需要找到一种方法将params表示为字符串,以将其集成到您的缓存键.缓存事件将从一组参数到另一组参数不同.

查找这些参数的最近更新事件,以避免检索过时数据.我们可以cache_key在任何ActiveRecord对象上使用ActiveRecord 方法作为其缓存键,这很方便,并且像我们之前那样避免繁琐的时间戳格式化.

这应该给你这样的东西:

def get_events
  latest_event = Event.search(params).order(:updated_at).last
  # text representation of the given params

  # Check https://apidock.com/rails/ActiveRecord/Base/cache_key
  # for an easy way to define cache_key for an ActiveRecord model instance
  cache_key = "filtered_events/#{text_rep_of_params}/#{latest_event.cache_key}"

  event_data = Rails.cache.fetch(cache_key) do
    events = Event.search(params)
    # A TON OF EVENTS TO LOAD ON CALENDAR
    events.collect do |event|
      {
        title: event.title,
        description: event.description || '',
        start: event.starttime.iso8601,
        end: event.endtime.iso8601,
        allDay: event.all_day,
        recurring: (event.event_series_id) ? true : false,
        backgroundColor: (event.event_category.color || "red"),
        borderColor: (event.event_category.color || "red")  
      }
    end
  end

  render json: event_data.to_json
end
Run Code Online (Sandbox Code Playgroud)

瞧!我希望它有所帮助.祝你的实施细节好运.