Статьи

Ускорьте процесс, узнав о кэшировании в Rails

Как разработчики, мы часто слышим слово «кеш». На самом деле это означает «прятаться» по-французски («cache-cache» — игра в прятки). Кэширование в основном означает, что мы храним некоторые данные, чтобы последующие запросы выполнялись быстрее, без необходимости повторного создания этих данных.

Кэширование действительно может принести огромные преимущества за счет ускорения работы вашего веб-приложения и улучшения его взаимодействия с пользователем. Сегодня мы обсудим различные методы кэширования, доступные в Rails: кэширование страниц, действий, фрагментов, моделей и HTTP. Затем вы можете выбрать один из них или даже использовать несколько методов в сочетании.

Исходный код можно найти на GitHub .

Включение кеширования

Для этой демонстрации я буду использовать Rails 5 beta 3, но большая часть информации применима и к Rails 4. Идем дальше и создаем новое приложение:

$ rails new BraveCacher -T 

Как вы, наверное, знаете, кеширование по умолчанию отключено в среде разработки, поэтому вам нужно его включить. В Rails 5, однако, это делается по-другому. Просто запустите следующую команду:

 $ rake dev:cache 

В основном эта команда создает пустой файл caching-dev.txt в каталоге tmp . Внутри файла development.rb теперь есть следующий код:

конфигурации / среда / development.rb

 if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.action_mailer.perform_caching = false config.cache_store = :memory_store config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=172800' } else config.action_controller.perform_caching = false config.action_mailer.perform_caching = false config.cache_store = :null_store end 

Этот файл сигнализирует, включать или отключать кэширование — вам больше не нужно настраивать параметр config.action_controller.perform_caching . Другие различия между Rails 4 и 5 можно найти в одной из моих предыдущих статей .

Обратите внимание, что если вы используете бета-версию Rails 5, при загрузке сервера с помощью rails s возникает ошибка . Проще говоря, файл caching-dev.txt удаляется каждый раз, поэтому на данный момент вместо этого используйте следующую команду:

 $ bin\rails server -C 

Эта ошибка уже исправлена ​​в основной ветке.

Хорошо, теперь кеширование включено, и мы можем перейти к следующему разделу. Давайте сначала обсудим кеширование страниц.

Кэширование страниц

Как кто-то однажды сказал, слава Богу, если вы можете использовать кэширование страниц, потому что это значительно повышает производительность вашего приложения. Идея этого метода проста: вся HTML-страница сохраняется в файле внутри публичного каталога. При последующих запросах этот файл отправляется непосредственно пользователю без необходимости повторной визуализации представления и макета.

К сожалению, такое мощное решение имеет очень ограниченное использование. Если у вас много страниц, которые выглядят по-разному для разных пользователей, кэширование страниц — не лучший вариант. Кроме того, его нельзя использовать в тех случаях, когда к вашему веб-сайту могут обращаться только авторизованные пользователи. Тем не менее, он светит для полустатических страниц.

Начиная с Rails 4, кэширование страниц было извлечено в отдельный гем , поэтому добавьте его сейчас:

Gemfile

 [...] gem 'actionpack-page_caching' [...] 

Бегать

 $ bundle install 

Теперь настройте свой файл конфигурации:

конфиг / application.rb

 [...] config.action_controller.page_cache_directory = "#{Rails.root.to_s}/public/deploy" [...] 

Этот параметр необходим для указания места хранения ваших кэшированных страниц.

Давайте представим очень простой контроллер и представление для проверки нашей новой техники кэширования.

pages_controller.rb

 class PagesController < ApplicationController def index end end 

конфиг / routes.rb

 [...] root 'pages#index' [...] 

просмотров / страниц / index.html.erb

 <h1>Welcome to my Cached Site!</h1> 

Кэширование страниц включается для каждого действия с помощью метода caches_page . Давайте кешируем нашу главную страницу:

pages_controller.rb

 class PagesController < ApplicationController caches_page :index end 

Загрузите сервер и перейдите к корневому пути. Вы должны увидеть следующий вывод в консоли:

 Write page f:/rails/my/sitepoint/sitepoint-source/BraveCacher/public/deploy/index.html (1.0ms) 

Внутри каталога public / deploy будет файл index.html , содержащий всю разметку страницы. При последующих запросах оно будет отправлено без необходимости проходить через ActionPack.

Конечно, вам также нужно будет использовать логику истечения срока действия кэша. Чтобы истечь ваш кеш, используйте метод expire_page в вашем контроллере:

 expire_page action: 'index' 

Например, если ваша индексная страница содержит список продуктов, вы можете добавить expire_page в метод create . В качестве альтернативы вы можете использовать уборочную машину, как описано здесь .

Кеширование действий

Кэширование действий работает почти так же, как кэширование страниц, однако вместо немедленной отправки страницы, хранящейся в публичном каталоге, оно попадает в стек Rails. При этом он запускается перед действиями, которые могут, например, обрабатывать логику аутентификации.

Кэширование действий также было извлечено в отдельный гем , поэтому добавьте его:

Gemfile

 [...] gem 'actionpack-action_caching' [...] 

Бегать

 $ bundle install 

Давайте добавим новую запрещенную страницу с очень наивной логикой авторизации. Мы будем использовать Action Caching для кэширования действия, вызвав метод caches_action :

 class PagesController < ApplicationController before_action :authenticate!, only: [:restricted] caches_page :index caches_action :restricted def index end def restricted end private def authenticate! params[:admin] == 'true' end end 

просмотров / страниц / restricted.html.erb

 <h1>Restricted Page</h1> 

конфиг / routes.rb

 get '/restricted', to: 'pages#restricted' 

В этой технике используется кэширование фрагментов, поэтому при доступе к закрытой странице вы увидите следующий вывод:

 Write fragment views/localhost:3000/restricted 

Истечение срока действия выполняется аналогично кэшированию страницы — просто expire_action метод expire_action и передайте ему параметры controller и action . caches_action также принимает различные варианты:

  • if или unless указывать, следует ли кэшировать действие
  • expires_in — время истечения срока действия кэша автоматически
  • cache_path — путь для хранения кэша. Полезно для действий с несколькими возможными маршрутами, которые должны кэшироваться по-разному
  • layout — если задано значение false , будет кэшироваться только содержимое действия
  • format — полезно, когда на ваши действия отвечают разные форматы

Кэширование фрагментов

Кэширование фрагментов, как следует из названия, кэширует только часть вашей страницы. Эта функциональность существует в ядре Rails, поэтому вам не нужно добавлять ее вручную.

Давайте представим новую модель под названием Product , соответствующий контроллер, представление и маршрут:

 $ rails g model Product title:string $ rake db:migrate 

products_controller.rb

 class ProductsController < ApplicationController def index @products = Product.all end end 

конфиг / routes.rb

 resources :products, only: [:index] 

просмотров / продукция / index.html.erb

 <h1>Products</h1> <% @products.each do |product| %> <%= product.title %> <% end %> 

Заполните таблицу products образцами данных:

дБ / seeds.rb

 20.times {|i| Product.create!({title: "Product #{i + 1}"})} 

И беги

 $ rake db:seed 

Теперь предположим, что мы хотим кэшировать каждый продукт, указанный на странице. Это делается с помощью метода cache который принимает имя кеша. Это может быть строка или объект:

просмотров / продукция / index.html.erb

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

Или

просмотров / продукция / index.html.erb

 <% @products.each do |product| %> <% cache "product-#{product.id}" do %> <%= product.title %> <% end %> <% end %> 

Если вы передадите объект методу cache , он автоматически получит его идентификатор, добавит метку времени и сгенерирует правильный ключ кеша (который является хешем MD5). Срок действия кэша автоматически истекает, если продукт был обновлен.

В консоли вы должны увидеть вывод, похожий на этот:

 Write fragment views/products/12-20160413131556164995/0b057ac0a9b2a20d07f312c2f31bde45 

Этот код можно упростить, используя метод render с опцией cached :

просмотров / продукция / index.html.erb

 <%= render @products, cached: true %> 

Вы можете пойти дальше и применить так называемое русское кеширование кукол :

просмотров / продукция / index.html.erb

 <% cache "products" do %> <%= render @products, cached: true %> <% end %> 

Есть также cache_if и cache_unless , которые довольно cache_unless .

Если вы хотите завершить кеш вручную, используйте метод expire_fragment и передайте ему ключ кеша:

 @product = Product.find(params[:id]) # do something... expire_fragment(@product) 

Кеширование модели

Кеширование модели (или низкоуровневое кеширование) часто используется для кэширования определенного запроса, однако это решение может использоваться для хранения любых данных. Эта функциональность также является частью ядра Rails.

Предположим, мы хотим кэшировать список наших продуктов, извлеченных внутри действия index . Введите новый метод класса для Product :

модели / product.rb

 [...] class << self def all_cached Rails.cache.fetch("products") { Product.all } end end [...] 

Метод fetch может как читать, так и записывать кеш Rails (первый аргумент — имя хранилища). Если запрошенное хранилище пусто, оно будет заполнено содержимым, указанным внутри блока. Если оно содержит что-то, результат будет просто возвращен. Также доступны методы write и read .

В этом примере мы создаем новое хранилище под названием «products», которое будет содержать список наших продуктов. Теперь используйте этот новый метод внутри действия контроллера:

products_controller.rb

 [...] def index @products = Product.all_cached end [...] 

Конечно, вам нужно реализовать логику истечения срока действия кэша. Для простоты давайте сделаем это внутри обратного вызова after_commit :

модели / product.rb

 [...] after_commit :flush_cache [...] private def flush_cache Rails.cache.delete('products') end [...] 

После обновления какого-либо продукта мы аннулируем кеш «Продукты».

Кэширование модели довольно просто реализовать и может значительно ускорить ваши сложные запросы.

HTTP-кеширование

Последний тип кэширования, который мы собираемся обсудить сегодня, — это HTTP-кэширование, которое опирается на заголовки HTTP_IF_NONE_MATCH и HTTP_IF_MODIFIED_SINCE . По сути, эти заголовки отправляются клиентом, чтобы проверить, когда содержимое страницы последний раз изменялось и был ли изменен ее уникальный идентификатор. Этот уникальный идентификатор называется ETag и генерируется сервером.

Клиент получает ETag и отправляет его внутри заголовка HTTP_IF_NONE_MATCH при последующих запросах. Если ETag, отправленный клиентом, не соответствует сгенерированному на сервере, это означает, что страница была изменена и должна быть загружена снова. В противном случае возвращается код состояния 304 («не изменен»), и браузер использует кэшированную копию страницы. Чтобы узнать больше, я настоятельно рекомендую прочитать эту статью Google, которая дает очень простые объяснения.

Для реализации HTTP-кэширования можно использовать два метода: stale? и fresh_when Предположим, мы хотим кэшировать страницу show :

products_controller.rb

 [...] def show @product = Product.find(params[:id]) end [...] 

просмотров / продукция / show.html.erb

 <h1>Product <%= @product.title %></h1> 

конфиг / routes.rb

 [...] resources :products, only: [:index, :show] [...] 

Использовать stale? метод и установите параметры :last_modified и :etag :

products_controller.rb

 [...] def show @product = Product.find(params[:id]) if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key) respond_to do |format| format.html end end end [...] 

Идея проста — если продукт был недавно обновлен, кеш будет признан недействительным, и клиенту придется снова загрузить страницу. В противном случае Rails отправит код состояния 304. cache_key — это специальный метод, который генерирует правильный уникальный идентификатор для записи.

Вы можете еще больше упростить код

products_controller.rb

 [...] def show @product = Product.find(params[:id]) if stale?(@product) respond_to do |format| format.html end end end [...] 

stale? автоматически получит метку времени продукта и ключ кеша. fresh_when — более простой метод, который можно использовать, если вы не хотите использовать respond_to :

products_controller.rb

 [...] def show @product = Product.find(params[:id]) fresh_when @product end [...] 

Он также принимает параметры :last_modified и :etag , как и stale? ,

Кэширование HTTP может быть сложно реализовать, особенно для сложных страниц, но его использование может реально повысить производительность веб-сайта.

Вывод

В этой статье мы обсудили различные методы кэширования, которые вы можете использовать в своих приложениях на Rails. Однако помните, что предварительная оптимизация — это зло, поэтому вы должны оценить, какое решение для кэширования вам подходит, прежде чем его внедрять.

Какие методы кэширования вы используете в своих приложениях? Поделитесь своим опытом в комментариях!