Разогрев кеша дайджестов за ночь

У нас есть сайт Rails 3.2, довольно большой, с тысячами URL. Мы реализовали гем Cache_Digests для кэширования русской куклы. Работает хорошо. Мы хотим еще больше оптимизировать, разогрев кэш-память в одночасье, чтобы пользователь получал лучшие впечатления в течение дня. Я видел ответ на этот вопрос: Rails: Запланированное задание на разогрев кеша?

Может ли он быть изменен для разогрева большого количества URL?

2 ответа

Чтобы вызвать попадания в кэш для многих страниц с дорогим временем загрузки, просто создайте задачу rake, чтобы итеративно отправлять веб-запросы всем комбинациям записей / URL-адресов на вашем сайте. ( Вот одна реализация)

Итеративно Net::HTTP запросить все URL сайта / записи:

Чтобы посетить только каждую страницу, вы можете запустить ночное задание Rake, чтобы убедиться, что у пользователей раннего утра все еще есть мгновенная страница с обновленным содержимым.

lib / tasks / visit_every_page.rake:

namespace :visit_every_page do
  include Net
  include Rails.application.routes.url_helpers

  task :specializations => :environment do
    puts "Visiting specializations..."
    Specialization.all.sort{ |a,b| a.id <=> b.id }.each do |s|
      begin
        puts "Specialization #{s.id}"

        City.all.sort{ |a,b| a.id <=> b.id }.each do |c|
          puts "Specialization City #{c.id}"
          Net::HTTP.get( URI("http://#{APP_CONFIG[:domain]}/specialties/#{s.id}/#{s.token}/refresh_city_cache/#{c.id}.js") )
        end

        Division.all.sort{ |a,b| a.id <=> b.id }.each do |d|
          puts "Specialization Division #{d.id}"
          Net::HTTP.get( URI("http://#{APP_CONFIG[:domain]}/specialties/#{s.id}/#{s.token}/refresh_division_cache/#{d.id}.js") )
        end
      end
    end
  end

  # The following methods are defined to fake out the ActionController
  # requirements of the Rails cache

  def cache_store
    ActionController::Base.cache_store
  end

  def self.benchmark( *params )
    yield
  end

  def cache_configured?
    true
  end
end

(Если вы хотите напрямую включить истечение срока действия / повторное кэширование в эту задачу, ознакомьтесь с этой реализацией.)

с помощью пользовательского действия контроллера:

Если вам нужно обойти ограничения аутентификации пользователей, чтобы попасть на ваши страницы, и / или вы не хотите испортить (слишком плохо) аналитику отслеживания вашего сайта, вы можете создать настраиваемое действие контроллера для попадания в дайджесты кэша, использующие токены для обхода аутентификация:

приложение / контроллеры / specializations.rb:

class SpecializationsController < ApplicationController
...
  before_filter :check_token, :only => [:refresh_cache, :refresh_city_cache, :refresh_division_cache]
  skip_authorization_check :only => [:refresh_cache, :refresh_city_cache, :refresh_division_cache]

...

  def refresh_cache
    @specialization = Specialization.find(params[:id])
    @feedback = FeedbackItem.new
    render :show, :layout => 'ajax'
  end

  def refresh_city_cache
    @specialization = Specialization.find(params[:id])
    @city = City.find(params[:city_id])
    render 'refresh_city.js'
  end

  def refresh_division_cache
    @specialization = Specialization.find(params[:id])
    @division = Division.find(params[:division_id])
    render 'refresh_division.js'
  end

end

Наш пользовательский контроллер выполняет рендеринг других дорогих для загрузки страниц, вызывая попадание в кеш этих страниц. Например refresh_cache отображает ту же страницу просмотра и данные, что и контроллер # show, поэтому запрашивает refresh_cache прогреет те же дайджесты кеша, что и контроллер # show для этих записей.

Примечание по безопасности:

Из соображений безопасности я рекомендую перед предоставлением доступа к любому refresh_cache Контроллер запрашивает, чтобы вы передали токен и проверили его, чтобы убедиться, что он соответствует уникальному токену для этой записи. Сопоставление токенов URL с записями базы данных перед предоставлением доступа (как показано выше) является тривиальным, поскольку ваша задача Rake имеет доступ к уникальным токенам каждой записи - просто передайте токен записи с каждым запросом.

ТЛ; др:

Чтобы вызвать тысячи дайджестов URL/ кэша сайта, создайте задачу rake, чтобы итеративно запрашивать каждую комбинацию записи / URL на вашем сайте. Вы можете обойти ограничения аутентификации пользователя вашего приложения для этой задачи, создав собственное действие контроллера, которое вместо этого аутентифицирует доступ через токены.

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

Надеюсь, это поможет следующему человеку...

Для моего собственного служебного класса, который можно найти здесь: https://raw.githubusercontent.com/JayTeeSF/cmd_notes/master/automated_action_runner.rb

Вы можете просто запустить это (по методу.help) и предварительно кэшировать свои страницы, не связывая свой собственный веб-сервер, в процессе.

class AutomatedActionRunner  
  class StatusObject
    def initialize(is_valid, error_obj)
      @is_valid = !! is_valid
      @error_obj = error_obj
    end

    def valid?
      @is_valid
    end

    def error
      @error_obj
    end
  end

  def self.help
    puts <<-EOH
      Instead tying-up the frontend of your production site with:
        `curl http://your_production_site.com/some_controller/some_action/1234`
        `curl http://your_production_site.com/some_controller/some_action/4567`
      Try:
        `rails r 'AutomatedActionRunner.run(SomeController, "some_action", [{id: "1234"}, {id: "4567"}])'`
    EOH
  end

  def self.common_env
    {"rack.input"  => "", "SCRIPT_NAME" => "", "HTTP_HOST" => "localhost:3000" }
  end
  REQUEST_ENV = common_env.freeze

  def self.run(controller, controller_action, params_ary=[], user_obj=nil)
    success_objects = []
    error_objects = []
    autorunner = new(controller, controller_action, user_obj)
    Rails.logger.warn %Q|[AutomatedAction Kickoff]: Preheating cache for #{params_ary.size} #{autorunner.controller.name}##{controller_action} pages.|

    params_ary.each do |params_hash|
      status = autorunner.run(params_hash)
      if status.valid?
        success_objects << params_hash
      else
        error_objects << status.error
      end
    end

    return process_results(success_objects, error_objects, user_obj.try(:id), autorunner.controller.name, controller_action)
  end

  def self.process_results(success_objects=[], error_objects=[], user_id, controller_name, controller_action)
    message = %Q|AutomatedAction Summary|
    backtrace = (error_objects.first.try(:backtrace)||[]).join("\n\t").inspect
    num_errors = error_objects.size
    num_successes = success_objects.size

    log_message = %Q|[#{message}]: Generated #{num_successes} #{controller_name}##{controller_action}, pages; Failed #{num_errors} times; 1st Fail: #{backtrace}|
    Rails.logger.warn log_message

    # all the local-variables above, are because I typically call Sentry or something with extra parameters!
  end

  attr_reader :controller
  def initialize(controller, controller_action, user_obj)
    @controller = controller
    @controller = controller.constantize unless controller.respond_to?(:name)
    @controller_instance = @controller.new
    @controller_action = controller_action
    @env_obj = REQUEST_ENV.dup
    @user_obj = user_obj
  end

  def run(params_hash)
    Rails.logger.warn %Q|[AutomatedAction]: #{@controller.name}##{@controller_action}(#{params_hash.inspect})|
    extend_with_autorun unless @controller_instance.respond_to?(:autorun)

    @controller_instance.autorun(@controller_action, params_hash, @env_obj, @user_obj)
  end


  private

  def extend_with_autorun
    def @controller_instance.autorun(action_name, action_params, action_env, current_user_value=nil)
      self.params = action_params # suppress strong parameters exception
      self.request = ActionDispatch::Request.new(action_env)
      self.response = ActionDispatch::Response.new
      define_singleton_method(:current_user, -> { current_user_value })

      send(action_name) # do it
      return StatusObject.new(true, nil)
    rescue Exception => e
      return StatusObject.new(false, e)
    end
  end
end
Другие вопросы по тегам