Skip to content

Optimum Sidekiq Configuration on Heroku with Puma #12

Open
@winston

Description

@winston

What's the optimum config for Sidekiq on Heroku with Puma?

There are quite a number of answers on the Internet, but nothing definitive, and most of them come with vague numbers and suggestions or are outdated.

Basically, these are the questions that are often asked:

  • What do I exactly put in the config/initializers/sidekiq.rb file?
  • What should I set for client/server size?
  • What should I set for client/server concurrency?
  • How does the Puma workers and threads affect the Sidekiq settings?
  • How does the number of Redis connections affect the Sidekiq settings?
  • How does that number of web/worker dynos affect the Sidekiq settings?

The best (and updated) answers I can find are:

With @bryanrite's post as a reference, this is our Sidekiq config:

config/initializers/sidekiq.rb

require 'sidekiq_calculations'

Sidekiq.configure_client do |config|
  sidekiq_calculations = SidekiqCalculations.new
  sidekiq_calculations.raise_error_for_env!

  config.redis = {
    url: ENV['REDISCLOUD_URL'],
    size: sidekiq_calculations.client_redis_size
  }
end

Sidekiq.configure_server do |config|
  sidekiq_calculations = SidekiqCalculations.new
  sidekiq_calculations.raise_error_for_env!

  config.options[:concurrency] = sidekiq_calculations.server_concurrency_size
  config.redis = {
    url: ENV['REDISCLOUD_URL']
  }
end

lib/sidekiq_calculations.rb

class SidekiqCalculations
  DEFAULT_CLIENT_REDIS_SIZE  = 2
  DEFAULT_SERVER_CONCURRENCY = 25

  def raise_error_for_env!
    return if !Rails.env.production?

    web_dynos
    worker_dynos
    max_redis_connection
  rescue KeyError, TypeError # Integer(nil) raises TypeError
    raise <<-ERROR
Sidekiq Server Configuration failed.
!!!======> Please add ENV:
  - NUMBER_OF_WEB_DYNOS
  - NUMBER_OF_WORKER_DYNOS
  - MAX_REDIS_CONNECTION
    ERROR
  end

  def client_redis_size
    return DEFAULT_CLIENT_REDIS_SIZE if !Rails.env.production?

    puma_workers * (puma_threads/2) * web_dynos
  end

  def server_concurrency_size
    return DEFAULT_SERVER_CONCURRENCY if !Rails.env.production?

    (max_redis_connection - client_redis_size - sidekiq_reserved) / worker_dynos / paranoid_divisor
  end

  private
    def web_dynos
      Integer(ENV.fetch('NUMBER_OF_WEB_DYNOS'))
    end

    def worker_dynos
      Integer(ENV.fetch('NUMBER_OF_WORKER_DYNOS'))
    end

    def max_redis_connection
      Integer(ENV.fetch('MAX_REDIS_CONNECTION'))
    end

    # ENV used in `config/puma.rb` too.
    def puma_workers
      Integer(ENV.fetch("WEB_CONCURRENCY", 2))
    end

    # ENV used in `config/puma.rb` too.
    def puma_threads
      Integer(ENV.fetch("WEB_MAX_THREADS", 5))
    end

    # https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/redis_connection.rb#L12
    def sidekiq_reserved
      5
    end

    # This is added to bring down the value of Concurrency
    # so that there's leeway to grow
    def paranoid_divisor
      2
    end
end

The sidekiq_calculations.rb file is dependent on a number of ENV variables to work, so if you do scale your app (web or workers), do remember to update these ENVs:

  • MAX_REDIS_CONNECTION
  • NUMBER_OF_WEB_DYNOS
  • NUMBER_OF_WORKER_DYNOS

At the same time, WEB_CONCURRENCY and WEB_MAX_THREADS should be the identical ENV variables used to set the number of Puma workers and threads in config/initializers/puma.rb.

Our puma.rb looks exactly like what Heroku has proposed.

The only difference to @bryanrite's calculation is that Sidekiq reserves 5 connections instead of 2 now
according to this line, and I have also added a paranoid_divisor to bring down the concurrency number and keep it below a 80% threshold.

Let me know how this config works for you. Would love to hear your feedback!


Thank you for reading.

@winston ✏️ Jolly Good Code

About Jolly Good Code

Jolly Good Code

We specialise in Agile practices and Ruby, and we love contributing to open source.
Speak to us about your next big idea, or check out our projects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions