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



Rails 附带片段缓存和低级缓存。片段缓存的工作原理非常清楚:

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

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

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

我不想缓存视图。我想缓存从活动记录查询生成的哈希集合。我知道 Rails 有 SQL 缓存,当在一个请求中使用时,它会缓存相同查询的结果。但是我需要在多个请求中提供结果,并且仅在其中一个对象的更改或将新对象添加到 Hash 集合时updated_at更新。

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

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

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

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。它不能在每次进入此方法时都触发,否则您将失去缓存的所有兴趣。
  • 钥匙的选择至关重要!它必须:
    • 数据过时后立即更改。否则,您将使用旧数据命中"旧"缓存键,并且不会提供最新数据。
    • 但!确定缓存键的成本必须比检索缓存数据少得多,因为每次传入此方法时,您都会"计算"缓存键是什么。因此,如果确定缓存键比检索实际数据花费更长的时间,那么,也许您将不得不以不同的方式思考。

使用上述方法的示例

让我们通过一个示例来检查我以前的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。它还不存在,因此所有事件都将从数据库请求并使用此缓存键存储到缓存中。

相同的数据,后续调用

如果不更新任何这些事件,则对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过滤事件,缓存将取决于您的参数,因此您需要找到一种方法将参数表示为字符串以将其集成到缓存键中。缓存的事件因一组参数而异。

查找这些参数的最新更新事件,以避免检索过时数据。我们可以在任何 ActiveRecord 对象上使用 ActiveRecordcache_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

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

最新更新