Memcached — система для кэширования объектов в памяти, которая работает очень быстро. Использование Memcached может значительно увеличить скорость работы Rails-приложения с минимальными затратами.
Предварительные условия
Предполагается, что в вашей системе уже установлены Ruby on Rails и Memcached. Если это не так, то вам помогут ссылки, приведенные ниже:
- Как установить Ruby on Rails в Ubuntu 12.04 LTS (Precise Pangolin) с RVM (прим. переводчика: на русском можно почитать здесь)
- Как установить и использовать Memcache в Ubuntu 12.04 (прим. переводчика: на русском можно почитать здесь)
Также предполагается, что у вас есть запущенное Rails-приложение, которое вы планируете оптимизировать с помощью Memcached.
Установка гема «Dalli»
Первое, что мы должны сделать — это установить гем Dalli от Mike Perham:
gem install dalli
Если вы используете Bundler, то добавьте строку gem 'dalli' в Gemfile и выполните установку bundle install.
Далее мы проделаем очень короткий путь по настройке взаимодействия Rails и Memcached.
Конфигурирование Rails
В первую очередь, для настройки взаимодействия Rails и Memcached необходимо отредактировать файл config/environments/production.rb. Добавим следующую строку, которая указывает Rails использовать для кэширования Dalli:
config.cache_store = :dalli_store
В этот же файл добавим еще одну строку, которая разрешает ActionController выполнять кэширование:
config.action_controller.perform_caching = true
Сейчас самое время выполнить перезагрузку вашего Rails-приложения для нормальной дальнейшей работы.
Настройка Rails-приложения
Нам необходимо выполнить настройку Rails-приложения для дальнейшей работы с Memcached. Есть два основных способа настройки и о них мы поговорим ниже.
Добавление заголовков (Cache-Control) для управления кэшем
Самый простой способ заключается в добавлении заголовков (Cache-Control) для управления кэшем к одному из ваших экшенов. Это позволит Rack::Cache сохранять результат работы экшена в Memcached. В этом случае экшен в app/controllers/slow_controller.rb должен содержать следующее:
def slow_action
sleep 15
# todo - print something here
end
Вы также можете добавить следующую строку, чтобы разрешить Rack::Cache хранить результат в течении пяти минут:
def slow_action
expires_in 5.minutes
sleep 15
# todo - print something here
end
Теперь, когда вы выполните этот экшен повторно, то должны заметить, что скорость работы значительно возросла. Rails будет выполнять код экшена только один раз в пять минут для обновления Rack::Cache.
Обратите внимание на то, что Cache-Control заголовки будут общедоступными. Если у вас есть экшены, которые должны быть видны лишь определенным пользователям, то используйте expires_in 5.minutes, :public => false. Вы также должны определить наиболее оптимальное время кэширования для вашего приложения. Для каждого из приложений это оптимальное время может быть разным и должно быть определено опытным путем.
Подробнее об HTTP-кэшировании можно прочитать в статье Mark Nottingham Руководство по кэшированию для веб-авторов и вебмастеров.
Хранение объектов в Memcached
Если в ваших экшенах имеются очень ресурсоемкие операции или вы оперируете объектами, которые по каким-то причинам необходимо часто создавать, то рационально будет хранить промежуточный результат в Memcached. Допустим, что ваш экшен содержит следующий код:
def slow_action
slow_object = create_slow_object
end
Вы можете сохранить результат в Memcached следующим образом:
def slow_action
slow_object = Rails.cache.fetch(:slow_object) do
create_slow_object
end
end
В этом случае Rails будет запрашивать у Memcached объект с ключом 'slow_object'. Если объект уже существует (был ранее сохранен в память), он будет возвращен. В противном случае, выполнится содержимое блока и результат запишется в 'slow_object'.
Кэширование фрагментов
Кэширование фрагментов — это одна из особенностей Rails, позволяющая кэшировать самые динамичные (часто меняющиеся) части вашего приложения. Вы можете кэшировать любые части ваших видов, заключая их в блок cache:
<% # app/views/managers/index.html.erb %>
<% cache manager do %>
Manager s Direct Reports:
<%= render manager.employees %>
<% end %>
<% # app/views/employees/_employee.html.erb %>
<% cache employee do %>
Employee Name: <%= employee.name %>
<%= render employee.incomplete_tasks %>
<% end %>
<% # app/views/tasks/_incomplete_tasks.html.erb %>
<% cache task do %>
Task: <%= task.title %>
Due Date: <%= task.due_date %>
<% end %>
Данная техника кэширования именуется, как Русские матрешки. Rails будет кэшировать все эти фрагменты в Memcached. С тех пор, как мы добавили имя модели в качестве параметра вызова метода cache, ключи этих моделей в кэше будут меняться лишь тогда, когда модель будет изменяться. Эта проблема будет особенно актуальной, если нам нужно обновить модель «task»:
@task.completed!
@task.save!
Итак, мы имеем вложенный кэш объектов и Rails ничего не знает о сроке жизни кэша фрагментов, привязанных к моделям. В этом случае нам поможет ключ ActiveRecord touch:
class Employee < ActiveRecord::Base
belongs_to :manager, touch: true
end
class Task < ActiveRecord::Base
belongs_to :employee, touch: true
end
Теперь, когда модель Task будет обновлена, кэш станет недействительным для фрагментов и модели Employee будет сообщено, что она также должна обновить свои фрагменты. Далее, модель Employee сообщит модели Manager, что она тоже должна обновить свой кэш. После этого процесс обновления кэша можно считать благополучно завершенным.
Существует еще одна дополнительная проблема кэширования по типу «Русских матрешек»: при развертывании нового приложения Rails не знает, когда нужно проверять обновление вида. Допустим, вы обновили «task» следующим образом:
<% # app/views/tasks/_incomplete_tasks.html.erb %>
<% cache task do %>
Task: <%= task.title %>
Due Date: <%= task.due_date %>
<p><%= task.notes %></p>
<% end %>
Rails не умеет обновлять кэш фрагментов при использовании частичных шаблонов. Ранее, чтобы обойти эту проблему, необходимо было добавлять номер версии в метод cache. Сейчас эту проблему можно решить с использованием гема cache_digests, который автоматически добавляет MD5-хэш к ключу кэша. После обновления частичного шаблона и перезапуска приложения ключ кэша также обновится и Rails будет вынужден выполнить новую генерацию шаблона вида. Он также умеет обрабатывать зависимости между файлами шаблонов таким образом, что если, допустим, был изменен частичный шаблон _incomplete_tasks.html.erb, то будут обновлены все зависимые шаблоны по цепочке вверх.
Эта особенность была учтена в Rails 4.0. Если вы используете более раннюю версию, то необходимо установить гем, используя команду:
gem install cache_digests
Если вы используете Bundler, то добавьте следующую строку в Gemfile:
gem 'cache_digests'
Расширенная настройка Rails и Memcached
Гем «Dalli» является очень мощным решением и предоставляет возможность работы с ключами в кластере Memcached серверов. Он умеет распределять нагрузку, тем самым увеличивая потенциал Memcached. Если у вас есть несколько серверов, то вы можете установить на каждом из них Memcached, а затем добавить соответствующую настройку в файл конфигурации config/environments/production.rb:
config.cache_store = :dalli_store, 'web1.example.com', 'web2.example.com', 'web3.example.com'
Данная настройка позволит использовать consistent hashing для распределения ключей между доступными Memcached серверами.
Автор: modjolees