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
但是现在我不认为如果其中一个事件被更新或者新的哈希添加到集合中,缓存就会过期。我怎么能这样做?
答案 0 :(得分:8)
我们尝试用缓存做的是:
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
。它尚不存在,因此将从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
瞧!我希望它有所帮助。祝你的实施细节好运。