pixelearth
pixelearth

Reputation: 14630

How to programmatically start/stop heroku workers in rails?

About 6 years ago there was a rails gem caled HireFire that worked with background jobs to start and stop workers as needed, so the worker wasn't always running and always accumulating charges.

HireFire seems to be defunct (as a gem), and I'm wondering how I can autoscale heroku workers like this these days?

I'm not interested in paying for a service for this.

I've looked around and am surprised that the solution doesn't seem obvious.

Has heroku implemented this type of auto-scaling, or is there another rails gem that will do it for me? Thanks

Upvotes: 1

Views: 893

Answers (3)

zawhtut
zawhtut

Reputation: 8561

As of 2019, Heroku gem was sunset. So use the web api instead. In my case, I used rest-client. Authorization credentials can be retrieved from heroku cli using

heroku authorizations:create

          if Rails.env.production?
            response = RestClient::Request.execute(
            method: :delete,
            url: 'https://api.heroku.com/apps/yourappname/dynos/worker.1',
            headers: {Authorization: 'Bearer xxxx-xxx-xxx-xxx-xxxxx',accept:'application/vnd.heroku+json; version=3'}
            )   
          end   

Upvotes: 0

pixelearth
pixelearth

Reputation: 14630

Update: I couldn't find an existing library/gem/plugin to do this for me, so I decided to try to solve it myself.

I decided to post back here after I came up with a solution. Perhaps it will be helpful for others.

DelayedJob has a plugin system (I had to upgrade my gem version to the latest)

This is the Plugin I wrote. I don't think it will win awards for beauty, but it seems to do the trick. Please post comments if I'm doing something stupid.

I also made it so it checks for production/development env and runs the local DelayedJob script when in development.

class HerokuWorkerDelayedJobPlugin < Delayed::Plugin
  callbacks do |lifecycle|
    lifecycle.before(:enqueue) do |job, *args, &block|
      Rails.logger.info "----before enqueue -----"
      self.start
    end

    lifecycle.after(:enqueue) do |job, *args, &block|
      Rails.logger.info "----after enqueue -----"
    end

    lifecycle.after(:invoke_job) do |job, *args, &block|
      Rails.logger.info "----after invoke_job -----"
    end

    lifecycle.after(:perform) do |worker, job, *args, &block|
      Rails.logger.info "----after perform-----"
      self.stop
    end

    def self.start
      if Rails.env.production?
        self.set_heroku_workers 1
      else
        `script/delayed_job start`
      end
    end

    def self.stop
      if Rails.env.production?
        self.set_heroku_workers 0
      else
        `script/delayed_job stop`
      end
    end

    def self.set_heroku_workers(num)
      heroku = PlatformAPI.connect_oauth(ENV['PLATFORM_API_OAUTH_TOKEN'])  #https://github.com/heroku/platform-api
      heroku.formation.update('my-app', 'worker', {quantity:   num}) #https://devcenter.heroku.com/articles/platform-api-reference#formation
    end
  end
end

You'll want to add the plugin to DelayedJob like this in your initializer:

Delayed::Worker.plugins << HerokuWorkerDelayedJobPlugin

Upvotes: 4

David Aldridge
David Aldridge

Reputation: 52376

You can scale Heroku dynos using the CLI, so building your own service to do this can be pretty simple.

Create another app that uses the scheduler to run every hour, using a unix shell script to download and install the CLI, and use credentials stored as ENV variables to send scale commands to other apps.

Here's a codebase as an example of the technique: https://github.com/kbaum/heroku-database-backups

Upvotes: 0

Related Questions