Не даем heroku спать

Heroku (что это и почему засыпают воркеры)

heroku.com — замечательный сервис, который позволяет писать на ruby не заботясь о том, как выкатывается новая версия, как делается дамп бд, где собрать статистику. Но, как во всех бочках меда, тут есть ложка дегтя, даже две:

Первая — при использовании его как полноценного хостинга под большие проекты — стоит жутко дорого;

Вторая — при использовании в качестве “бесплатной площадки для игр” предоставляет соответствующие размеры ресурсов (1 веб-воркер).

Первое, на что я наткнулся — это невозможность запуска в одном проекте веб-воркера и обработчика фоновых задач. Это можно обойти, используя в качестве сервера unicorn, но сразу же возникает новая проблема.

Например: Один воркер, бесплатный. Если использовать sidekiq в качестве менеджера очереди сообщений и добавлять задачи в очередь (redis) через rake задачи + scheduller (для них heroku выделяет отдельный воркер), то рано или поздно, а точнее через час, мастер-процесс unicorn’а заснет…

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

Встречаем WakeUpNeo: https://github.com/england/wake_up_neo

Дальше я опишу то, что использовалось при разработке.

Bundler

С помощью одной команды можно создать базовую структуру файлов/папок гема (так же эта команда инициализирует git репозиторий):

bundle gem wake_up_neo

Bundler — инструмент для управления гемами и их зависимостями (dependencies) в ruby-приложении.

Файл wake_up_neo/wake_up_neo.gemspec как раз и хранит зависимости, в нашем случае это:

# -*- encoding: utf-8 -*-
require File.expand_path('../lib/wake_up_neo/version', __FILE__)
Gem::Specification.new do |gem|
  gem.authors       = ["england"]
  gem.email         = ["englandpost@gmail.com"]
  gem.summary       = gem.summary = %q{Simple trick to prevent...
  gem.homepage      = "https://github.com/england/wake_up_neo"
  gem.add_dependency('rails', '>= 3.0.0')
  gem.files         = `git ls-files`.split($)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.name          = "wake_up_neo"
  gem.require_paths = ["lib"]
  gem.version       = WakeUpNeo::VERSION
end

Гемы, необходимые только для разработки и тестирования, стоит добавить через add_development_dependency, чтобы не они не попали в production окружение. В этом же файле хранится информация об авторе и краткое описание гема.

Generator

Генераторы в Rails основаны на Thor, который предоставляет шикарный api для манипуляций с файлами.

Нам нужно только создать простой initializer для назначения url’а, за который будет дергаться приложение на heroku.

Для этого достаточно одной команды create_file, где первый параметр — это путь к файлу, а второй — текст.

module WakeUpNeo
  module Generators
    class InstallGenerator < Rails::Generators::Base
      desc "Creates initializer file"
      def create_initializer_file
        create_file 'config/initializers/wake_up_neo.rb',
        <<-CONTENT
WakeUpNeo.configure do |config|
 # Url what will touch
 # For example 'http://myapp.heroku.com'
 config.knock_knock_url = ''
end
       CONTENT
      end
    end
  end
end

Повинуясь autoload’у, команда rails g wake_up_neo:install будет доступна из консоли и создаст файл config/initializers/wake_up_neo.rb.
(Посмотреть, что еще умеет Thor можно на http://rdoc.info/github/wycats/thor/master/Thor/Actions.html)

Initializer

“Здесь нужен блок.”
Галустян.

И хотя в модуле пока не планируется никаких сложных настроек, все же передать блок методу configure очень хочется. Блок позволяет выполнить код в контексте исполняемого метода. В нашем случае устанавливается значение instance переменной @knock_knock_url.

module WakeUpNeo
  module Config
    attr_accessor :knock_knock_url
    def configure
      yield self
    end
    def knock_knock_url
      if @knock_knock_url && !@knock_knock_url.blank?
        @knock_knock_url
      else
        raise "You must specify knock_knock_url in config/initializers/wake_up_neo.rb"
      end
    end
  end
end

Дальше остается только “расширить” модуль на пару методов.

module WakeUpNeo
  extend Config
  def self.knock_knock
    open(knock_knock_url)
  end
end

Rake Task

Итак, мы приближаемся к цели — rake-задаче, которая будет выполняться heroku scheduler’ом и вызывать метод, дергающий проект за nginx и не дающий заснуть.

namespace :wake_up_neo do
  desc 'Touch knock_knock_url'
  task knock_knock: :environment do
    WakeUpNeo.knock_knock
  end
end

Выглядит она просто. В принципе, от нее другого и не требуется. Есть только необходимость, чтобы она была в списке задач rails-приложения. Здесь нужен контрудар.

Railtie

Railtie — ядро rails, и его расширение позволяет взаимодействовать с фреймворком во время загрузки. Унаследовав класс от Rails::Railtie, мы получаем возможность добавлять инициализаторы, изменять config приложения, добавлять генераторы (то, что я описывал выше, только лучше) и, конечно, добавлять rake-задачи.

module WakeUpNeo
  class Railtie < Rails::Railtie
    rake_tasks do
      load "tasks/wake_up_neo.rake"
    end
  end
end

Теперь в списке rake -T есть

rake wake_up_neo:knock_knock

— можно добавлять ее в sheduller на выполнение с интервалом раз в 10 минут, и наше приложение точно никогда не уснет.

У нас есть похожие новости по этим темам:
Наверх