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

时间:2018-04-06 19:27:28

标签: ruby-on-rails ruby caching

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

  

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

<% @products.each do |product| %>
  <% cache product do %>
   <%= render product %>
  <% end %>
<% end %>

因此视图将被缓存,并且当与视图关联的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

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

1 个答案:

答案 0 :(得分:8)

Rails.cache.fetch是读写的快捷方式

我们尝试用缓存做的是:

value = Rails.cache.read( :some_key )
if value.nil?
  expected_value = ...
  Rails.cache.write( :some_key, expected_value )
end

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

在接下来的调用中,我们将尝试再次访问: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

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

我们如何处理低级缓存?

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

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

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

这里真正重要的是:

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

使用此前一种方法的示例

让我们看看我之前的get_events方法如何用一个例子来表现。假设您的数据库中包含以下Events

| ID | Updated_at       | 
|----|------------------|
|  1 | 2018-01-01 00:00 | 
|  2 | 2018-02-02 00:00 | 
|  3 | 2018-03-03 00:00 |

第一次致电

此时,我们打电话给get_events。事件#3是最近更新的事件,因此Rails将检查缓存键all_events/20180303000000。它尚不存在,因此将从DB请求所有事件并使用此缓存密钥将其存储到缓存中。

相同数据,后续调用

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

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

Event.find(2).touch

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

| ID | Updated_at       | 
|----|------------------|
|  1 | 2018-01-01 00:00 | 
|  2 | 2018-04-07 19:27 | <--- just updated :) 
|  3 | 2018-03-03 00:00 |

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

你的具体问题怎么样?

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

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

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

这应该给你类似的东西:

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

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