Skip to content

Commit

Permalink
Merge pull request sidekiq#2322 from kickstarter/poll_interval_average
Browse files Browse the repository at this point in the history
make the average global poller delay configurable
  • Loading branch information
mperham committed May 4, 2015
2 parents 2178d66 + a382cb9 commit 30715f7
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 9 deletions.
18 changes: 17 additions & 1 deletion lib/sidekiq.rb
Expand Up @@ -20,6 +20,8 @@ module Sidekiq
require: '.',
environment: nil,
timeout: 8,
poll_interval_average: nil,
average_scheduled_poll_interval: 15,
error_handlers: [],
lifecycle_events: {
startup: [],
Expand Down Expand Up @@ -127,9 +129,23 @@ def self.logger=(log)
Sidekiq::Logging.logger = log
end

# When set, overrides Sidekiq.options[:average_scheduled_poll_interval] and sets
# the average interval that this process will delay before checking for
# scheduled jobs or job retries that are ready to run.
#
# See sidekiq/scheduled.rb for an in-depth explanation of this value
def self.poll_interval=(interval)
self.options[:poll_interval] = interval
$stderr.puts "DEPRECATION: `Sidekiq.poll_interval = #{interval}` will be removed in Sidekiq 4. Please update to `Sidekiq.average_scheduled_poll_interval = #{interval}`."
self.options[:poll_interval_average] = interval
end

# How frequently Redis should be checked by a random Sidekiq process for
# scheduled and retriable jobs. Each individual process will take turns by
# waiting some multiple of this value.
#
# See sidekiq/scheduled.rb for an in-depth explanation of this value
def self.average_scheduled_poll_interval=(interval)
self.options[:average_scheduled_poll_interval] = interval
end

# Register a proc to handle any error which occurs within the Sidekiq process.
Expand Down
20 changes: 12 additions & 8 deletions lib/sidekiq/scheduled.rb
Expand Up @@ -68,7 +68,7 @@ def poll(first_time=false)

# Calculates a random interval that is ±50% the desired average.
def random_poll_interval
poll_interval * rand + poll_interval.to_f / 2
poll_interval_average * rand + poll_interval_average.to_f / 2
end

# We do our best to tune poll_interval to the size of the active Sidekiq
Expand All @@ -84,13 +84,17 @@ def random_poll_interval
# the same time: the thundering herd problem.
#
# We only do this if poll_interval is unset (the default).
def poll_interval
Sidekiq.options[:poll_interval] ||= begin
ps = Sidekiq::ProcessSet.new
pcount = ps.size
pcount = 1 if pcount == 0
pcount * 15
end
def poll_interval_average
Sidekiq.options[:poll_interval_average] ||= scaled_poll_interval
end

# Calculates an average poll interval based on the number of known Sidekiq processes.
# This minimizes a single point of failure by dispersing check-ins but without taxing
# Redis if you run many Sidekiq processes.
def scaled_poll_interval
pcount = Sidekiq::ProcessSet.new.size
pcount = 1 if pcount == 0
pcount * Sidekiq.options[:average_scheduled_poll_interval]
end

def initial_wait
Expand Down
33 changes: 33 additions & 0 deletions test/test_scheduled.rb
Expand Up @@ -82,5 +82,38 @@ def call(worker_class, message, queue, r)
assert_equal 1, @scheduled.size
end
end

def with_sidekiq_option(name, value)
_original, Sidekiq.options[name] = Sidekiq.options[name], value
begin
yield
ensure
Sidekiq.options[name] = _original
end
end

it 'generates random intervals that target a configured average' do
with_sidekiq_option(:poll_interval_average, 10) do
i = 500
intervals = i.times.map{ @poller.send(:random_poll_interval) }

assert intervals.all?{|i| i >= 5}
assert intervals.all?{|i| i <= 15}
assert_in_delta 10, intervals.reduce(&:+).to_f / i, 0.5
end
end

it 'calculates an average poll interval based on the number of known Sidekiq processes' do
with_sidekiq_option(:average_scheduled_poll_interval, 10) do
3.times do |i|
Sidekiq.redis do |conn|
conn.sadd("processes", "process-#{i}")
conn.hset("process-#{i}", "info", nil)
end
end

assert_equal 30, @poller.send(:scaled_poll_interval)
end
end
end
end

0 comments on commit 30715f7

Please sign in to comment.