Кеширование страниц с динамическими элементами средствами Ruby on Rails

July 3, 2010 cacheable_flash CarGid gem nginx Ruby on Rails кеширование

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

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

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

Перед тем, как заняться ее внедрением, нужно посмотреть по сторонам и составить список динамических элементов, нужных незалогиненному пользователю.

Flash, в смысле уведомления

Наиболее очевидный динамический элемент страницы – флеш. Чтобы выводить флеш на закешированных страницах, Pivotal Labs когда-то делали плагин CacheableFlash, но он рассчитан на Prototype. Потому-то я его и портировал в плагин CacheableFlash for jQuery.

Принцип работы плагина прост: в after_filter флеш заносится в куку, а в яваскрипте забирается оттуда. Надо заметить, что это не работает с flash.now, что, по-моему, вполне логично, поскольку flash.now нужен только на динамических страницах типа форм. Выводить его придется отдельно. Вообще у меня это таким хелпером делается:

def render_flash
  messages = {:error => [], :notice => []}
  flash.each do |type, message|
    messages[type] ||= []
    messages[type] << message
  end
  messages.to_a.map do |m|
    content_tag('div', m[1].join('<br/>'), :class => ['flash',m[0]].join(' '))
  end.join
end

Он и flash.now выводит, и место под динамический флеш подготавливает.

Другие элементы

Остальные динамические элементы – у меня на CarGid это был счетчик объявлений, отобранных пользователем «в блокнот» – выводятся таким же способом. Разумеется, нужно помнить, что объем кук не резиновый и что особо ценную информацию туда лучше не писать (хотя какая ценная информация у незалогиненного пользователя?)

Большие динамические элементы можно подгружать отдельным запросом. Сам понимаешь, что такое решение гораздо хуже.

Обновление кеша в результате действий незалогиненных пользователей

У меня незалогиненные пользователи могут оставлять комментарии. Разумеется, после добавления комментария его нужно показать пользователю – поэтому страницу с комментариями нужно убрать из кеша.

Настройка кеширования в контроллерах

По умолчанию рельсы кладут кеш прямо в public. Разумеется, это нам не подходит, потому кеш нужно перенести в public/cache:

# config/environments/production.rb
config.action_controller.page_cache_directory = File.join(RAILS_ROOT, :public, :cache)

Чтобы кеширование происходило только без наличия залогиненного пользователя, замечательно подходит условно-стандартный метод Authlogic current_user:

class MainController < ActionController::Base
  caches_page :index, :unless => :current_user
end

Остается растыкать caches_page по контроллерам, чтобы Rails начали складывать в кеш правильные страницы.

Настройка nginx

С недавних пор я использую в качестве сервера nginx, поэтому буду рассказывать о нем. Ну или точнее, показывать его документированные настройки.

# убираем слеш из конца URL - его любят навешивать некоторые обозреватели
rewrite ^(.+)/$ $1 permanent;
 
# запросы POST можно смело передавать прямо в Rails
if ($request_method !~ ^(GET|HEAD)$) {
  break;
}
 
# эта директива запрещает прямой доступ к каталогу cache извне
location ^~ /cache {
  internal;
}
 
# есть куки Authlogic - отключаем кеширование
if ($cookie_user_credentials) {
  break;
}
 
# наконец, проверяем наличие страницы в кеше и отдаем ее оттуда
if (-f $document_root/cache/index.html) {
  rewrite ^/$ /cache/index.html last;
}
if (-f $document_root/cache/$request_uri) {
  rewrite .* /cache/$request_uri last;
}
if (-f $document_root/cache/$request_uri.html) {
  rewrite .* /cache/$request_uri.html last;
}

Это все, что касается nginx. Наверно, надо заметить, что переместить папку с кешем из-под document_root не получится. Можно сделать симлинк, если очень хочется.

Buy Me a Coffee at ko-fi.com