Description
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.
About 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.