Rails caching has always had an expires_in
method,
which allows us to set cache expiry in relative time.
Before : Caching Expiry with expires_in
Let’s say in our e-commerce Rails application we are running a sale that ends in 2 days. We store the product ids which are part of sale in cache as below:
product_ids = ["4c383b29-c2d4-4579-be56-bb5fa51b43b5", "8ed1274b-e0ca-44f8-9ef2-9892c1816ba1", ...]
Rails.cache.write("new_year_sale_products", product_ids, expires_in: 2.days)
# Time passed to expires_in will be converted to epochs
# 2.days is stored as 172800 seconds
Rails.cache.send(:read_entry, "new_year_sale_products").instance_variable_get(:@expires_in)
# => 172800.0
Rails.cache.read("new_year_sale_products")
# => ["4c383b29-c2d4-4579-be56-bb5fa51b43b5", "8ed1274b-e0ca-44f8-9ef2-9892c1816ba1", ...]
# returns nil when called after 2 days.
The expires_in
is not an absolute timestamp.
After : Caching Expiry with expires_at
With this merge, it is now possible to store cache expiration in absolute time.
As mentioned in the PR itself, there are several use cases like:
- To expire an API token with a precise expiry time
- Cache until a precise cutoff time
To end the sale after 2 days we can make the below changes to our previous example.
product_ids = ["4c383b29-c2d4-4579-be56-bb5fa51b43b5", "8ed1274b-e0ca-44f8-9ef2-9892c1816ba1", ...]
Rails.cache.write("new_year_sale_products", product_ids, expires_at: (Time.now + 2.days).end_of_day)
# (Time.now + 2.days).end_of_day will return 2021-07-30 23:59:59.999999999
Rails.cache.send(:read_entry, 'new_year_sale_products', {})&.expires_at
# Returns 1627669800.0 ( = Time.parse("2021-07-30 23:59:60").to_i)
We need to be careful when passing values to the expires_at
and expires_in
keys.
# What happens if you pass relative time to expires_at
Rails.cache.write("new_year_sale_products", product_ids, expires_at: 5.minutes)
# 5.minutes will set expires_at to 300 ( Thursday, 1 January 1970 00:05:00 )
Rails.cache.read("new_year_sale_products")
# nil
Differences: expires_in vs expires_at
- The biggest difference is storing time in Relative vs Absolute epochs.
expires_in
can be set in the constructor,expires_at
is set at individual writes.config.cache_store = :redis_cache_store, { url: "redis://localhost:6379", expires_in: 1.day } # For all keys the default expires_in is 1.day # We can override default expires_in as below Rails.cache.write "key", "value", expires_in: 3.hours
- If both the
expires_in
andexpires_at
are set,expires_at
gets priority.