Introduction
Caching is one of the effective methods to improve the performance of the website by saving the little-changed components of the website to the cache to reuse each time they re-visit.
Rails provides us with 3 main caching techniques: Page caching , Action caching and Fragment caching . In this article we will focus on fragment caching only .
By default Rails only enables caching for production environments, to use it with development environment, we will have to add it in the config file:
1 2 3 | <span class="token comment"># config/environments/development.rb</span> config <span class="token punctuation">.</span> action_controller <span class="token punctuation">.</span> perform_caching <span class="token operator">=</span> <span class="token keyword">true</span> |
Fragment Caching
We can cache the whole website and return subsequent requests, but this method only applies to pages that are almost completely static, because when the user generates different interactive requests, the content will have to be done. new constantly. When we cache the whole page, the user only sees the old content, so the above method does not work. In this case, we should cache only the least changed parts of the page, and the Fragment Caching born to do that job.
Fragment Caching allows a portion of the view to be encapsulated in a cache and returned when the next request comes.
For example, in case we want to cache each product, we can do the following:
1 2 3 4 5 6 | <span class="token operator"><</span> <span class="token operator">%</span> <span class="token variable">@products</span> <span class="token punctuation">.</span> <span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span> product <span class="token operator">|</span> <span class="token string">%> <% cache product do %></span> <span class="token operator"><</span> <span class="token operator">%</span> <span class="token operator">=</span> render product <span class="token string">%> <% end %></span> <span class="token operator"><</span> <span class="token operator">%</span> <span class="token keyword">end</span> <span class="token operator">%</span> <span class="token operator">></span> |
When our application receives the first request, Rails will create a new cache entry with a unique key of the form:
1 2 | views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901 |
The long string in the middle is product_id
with the timestamp value of the product’s updated_at
field. Rails uses this timestamp value to ensure the data does not age. If the value of the updated_at
field is changed, a new key will be generated, and Rails will create a new cache with the key just created, the old cache will be removed. This process is called key-based expiration .
The fragments cache will also expire if the fragment’s view is changed. The sequence of letters at the end of the cache entry is called the template tree digest . It is a hash digest created based on the content of the view fragment we are trying to cache. When we change the fragment’s view, the digest chain will also be changed accordingly, and the cache will expire.
If we want to cache fragment under different conditions, we can use cache_if
or cache_unless
:
1 2 3 4 | <span class="token operator"><</span> <span class="token operator">%</span> cache_if admin <span class="token operator">?</span> <span class="token punctuation">,</span> product <span class="token keyword">do</span> <span class="token string">%> <%= render product %></span> <span class="token operator"><</span> <span class="token operator">%</span> <span class="token keyword">end</span> <span class="token operator">%</span> <span class="token operator">></span> |
Collection Caching
The render
helper method can also cache the individual templates of a collection by reading the entire cache templates in advance instead of using each
read each cache one after another as above. To do this, we pass cached: true
when rendering a collection:
1 2 | <span class="token operator"><</span> <span class="token operator">%</span> <span class="token operator">=</span> render partial <span class="token punctuation">:</span> <span class="token string">'products/product'</span> <span class="token punctuation">,</span> collection <span class="token punctuation">:</span> <span class="token variable">@products</span> <span class="token punctuation">,</span> cached <span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token operator">%</span> <span class="token operator">></span> |
All templates cached from the previous render will be fetched at once to improve the loading speed much faster. The template that has not been cached will be cached on the next render.
Russian Doll Caching
Russian Doll Caching is a technique that allows nested cached fragments – to cache sub-fragments within another cached fragment.
The advantage of this technique is that if the outer fragment is updated, only this fragment will change and the entire internal fragments will still be cached for reuse.
But in the opposite case, for example:
1 2 3 4 | <span class="token operator"><</span> <span class="token operator">%</span> cache product <span class="token keyword">do</span> <span class="token string">%> <%= render product.games %></span> <span class="token operator"><</span> <span class="token operator">%</span> <span class="token keyword">end</span> <span class="token operator">%</span> <span class="token operator">></span> |
The internal view is rendered:
1 2 3 4 | <span class="token operator"><</span> <span class="token operator">%</span> cache game <span class="token keyword">do</span> <span class="token string">%> <%= render game %></span> <span class="token operator"><</span> <span class="token operator">%</span> <span class="token keyword">end</span> <span class="token operator">%</span> <span class="token operator">></span> |
If any of the game
‘s attribute changes, the updated_at
field will be updated, meaning the cache will expire. However, because the updated_at
value of the product does not change, the product
cache will not expire, the data rendered will remain the same.
To fix this problem, we can use the touch
method on the model:
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">Product</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> has_many <span class="token symbol">:games</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">Game</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> belongs_to <span class="token symbol">:product</span> <span class="token punctuation">,</span> touch <span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token keyword">end</span> |
With the set touch
set to true
, any action that changes the game
‘s updated_at
value will affect the product
, so the cache for both the game
and the product
will expire and be removed.
From above, we can draw the principle of Russian Doll Caching technique as follows:
- When the outer fragment is updated, only the fragment will change, the remaining fragments will remain, and can be reused.
- When the innermost fragment is updated, it begins a chain that causes all its parent fragments to change.
Summary
The article to share about Caching in Rails and the powerful and effective caching technique is Russian Doll Caching . The article is limited, thank you for taking the time to read.
Source and reference: https://guides.rubyonrails.org/caching_with_rails.html