Skip to content

Instantly share code, notes, and snippets.

@leshill
Created April 3, 2011 18:39
Show Gist options
  • Save leshill/900658 to your computer and use it in GitHub Desktop.
Save leshill/900658 to your computer and use it in GitHub Desktop.
# Setup the following in your heroku config vars:
# See http://blog.leshill.org/blog/2010/11/02/heroku-environment-variables.html for an idea on how to do it
HEROKU_APP: poh-dev
HEROKU_USER: [email protected]
HEROKU_PASSWORD: replace_me
# Gemfile
gem 'heroku', '>= 1.19.1'
gem 'resque'
# Use our fork until our fix is merged.
# https://github.com/bvandenbos/resque-scheduler/pull/68
gem 'resque-scheduler', require: 'resque_scheduler', git: 'git://github.com/infbio/resque-scheduler', ref: 'strings_with_hooks'
# config/initializers/resque.rb
Resque.schedule = YAML.load_file(File.join(File.dirname(__FILE__), '../resque_schedule.yml'))
Resque.after_fork = Proc.new { ActiveRecord::Base.establish_connection }
# lib/tasks/resque.rake
require 'resque/tasks'
require 'resque_scheduler/tasks'
module ResqueWorker
extend self
def scheduler_or_worker?
redis = Resque.redis
if redis.setnx('scheduler_active', 'active')
begin
yield 'scheduler'
ensure
redis.del('scheduler_active')
end
else
yield 'work'
end
end
end
task "resque:setup" => :environment
task "resque:scheduler_setup" => :environment # load the env so we know about the job classes
desc "Run Resque workers on Heroku"
task "jobs:work" => ["resque:setup", "resque:scheduler_setup"] do
ENV['QUEUE'] = '*'
ResqueWorker.scheduler_or_worker? {|which| Rake::Task["resque:#{which}"].invoke }
end
# NB include this in your Job models i.e.
#
# class NotificationTime < ActiveRecord::Base
# extend HerokuResqueAutoscale
# ...
#
# app/models/heroku_resque_autoscale
require 'heroku'
module HerokuResqueAutoscale
module Scaler
extend self
def heroku
if ENV['HEROKU_USER'] && ENV['HEROKU_PASSWORD'] && ENV['HEROKU_APP']
@heroku ||= Heroku::Client.new(ENV['HEROKU_USER'], ENV['HEROKU_PASSWORD'])
else
false
end
end
def workers
if heroku
heroku.info(ENV['HEROKU_APP'])[:workers].to_i
else
0
end
end
def workers=(qty)
heroku.set_workers(ENV['HEROKU_APP'], qty) if heroku
end
def job_count
Resque.info[:pending].to_i
end
end
def scale_down!
# Nothing fancy, just shut everything but the scheduler down if we have no jobs
Scaler.workers = 1 if Scaler.job_count.zero?
end
def after_perform_scale_down(*args)
scale_down!
end
def after_enqueue_scale_up(*args)
[
{ :workers => 2, # This many workers
:job_count => 1 # For this many jobs or more, until the next level
},
{ :workers => 3,
:job_count => 15
},
{ :workers => 4,
:job_count => 25
},
{ :workers => 5,
:job_count => 40
},
{ :workers => 6,
:job_count => 60
}
].reverse_each do |scale_info|
# Run backwards so it gets set to the highest value first
# Otherwise if there were 70 jobs, it would get set to 1, then 2, then 3, etc
# If we have a job count greater than or equal to the job limit for this scale info
if Scaler.job_count >= scale_info[:job_count]
# Set the number of workers unless they are already set to a level we want. Don't scale down here!
if Scaler.workers <= scale_info[:workers]
Scaler.workers = scale_info[:workers]
end
break # We've set or ensured that the worker count is high enough
end
end
end
def on_failure(e, *args)
Rails.logger.info("Resque Exception for [#{self.to_s}, #{args.join(', ')}] : #{e.to_s}")
scale_down!
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment