Skip to content

Instantly share code, notes, and snippets.

@timdorr
Created May 31, 2016 22:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timdorr/399dacc392e8ae52b05291af469c6df0 to your computer and use it in GitHub Desktop.
Save timdorr/399dacc392e8ae52b05291af469c6df0 to your computer and use it in GitHub Desktop.
A basic Sidekiq rate limiter

Don't copypasta this!

Note that this is very purpose-built. The job args in particular depend on a unique key being in the first position.

There is also plenty of brittle code all over the place.

Instead, use this as a guide for how you might build your own. A lot of the parts are the same between implementations.

class LimitedFetch < Sidekiq::BasicFetch
attr_reader :job
def initialize(options)
super(options)
@digest = Digest::SHA1.hexdigest(script)
end
def retrieve_work
work = super
return work if work.blank?
@job = JSON.parse(work.job) rescue {}
return work if max_concurrent.blank?
Sidekiq.redis do |conn|
if throttled?
conn.lpush("queue:#{work.queue_name}", work.job)
nil
else
work
end
end
end
def throttled?
1 == eval(["limited-throttle-#{id}"], [max_concurrent, 60, @job['jid']])
end
def eval(*args)
Sidekiq.redis { |conn| conn.evalsha(@digest, *args) }
rescue => e
raise unless e.message.include?('NOSCRIPT')
Sidekiq.redis do |conn|
digest = conn.script(:load, script)
@digest = digest.freeze
conn.evalsha(@digest, *args)
end
end
def script
<<-eos
local key, limit, ttl, jid = KEYS[1], tonumber(ARGV[1]), tonumber(ARGV[2]), ARGV[3]
if limit <= redis.call("SCARD", key) and 0 == redis.call("SISMEMBER", key, jid) then
return 1
end
redis.call("SADD", key, jid)
redis.call("EXPIRE", key, ttl)
return 0
eos
end
def max_concurrent
job['max_concurrent']
end
def id
job['args'].first
end
end
class LimitedMiddleware
def call(_worker, job, _queue)
yield
ensure
Sidekiq.redis { |conn| conn.srem("limited-throttle-#{job['args'].first}", job['jid']) }
end
end
@timdorr
Copy link
Author

timdorr commented May 31, 2016

Oh, duh, you need to install it too:

Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add LimitedMiddleware
  end
  Sidekiq.options[:fetch] = LimitedFetch
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment