{"id":13879641,"url":"https://github.com/ixti/sidekiq-throttled","last_synced_at":"2025-05-13T21:04:22.402Z","repository":{"id":40633425,"uuid":"45144378","full_name":"ixti/sidekiq-throttled","owner":"ixti","description":"Concurrency and rate-limit throttling for Sidekiq","archived":false,"fork":false,"pushed_at":"2025-05-05T15:40:39.000Z","size":585,"stargazers_count":743,"open_issues_count":21,"forks_count":82,"subscribers_count":31,"default_branch":"main","last_synced_at":"2025-05-05T16:50:51.541Z","etag":null,"topics":["concurrency","sidekiq","threshold","throttling"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ixti.png","metadata":{"files":{"readme":"README.adoc","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-10-28T21:58:59.000Z","updated_at":"2025-05-05T15:40:44.000Z","dependencies_parsed_at":"2022-07-16T10:30:34.157Z","dependency_job_id":"a3559854-e63e-467b-99ed-e80befa9312f","html_url":"https://github.com/ixti/sidekiq-throttled","commit_stats":{"total_commits":324,"total_committers":45,"mean_commits":7.2,"dds":0.2222222222222222,"last_synced_commit":"a15db1bb4ef2eb871b45c1ba520691adf7c8382d"},"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixti%2Fsidekiq-throttled","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixti%2Fsidekiq-throttled/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixti%2Fsidekiq-throttled/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixti%2Fsidekiq-throttled/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ixti","download_url":"https://codeload.github.com/ixti/sidekiq-throttled/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254028368,"owners_count":22002240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","sidekiq","threshold","throttling"],"created_at":"2024-08-06T08:02:27.531Z","updated_at":"2025-05-13T21:04:22.357Z","avatar_url":"https://github.com/ixti.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"= Sidekiq::Throttled\n:ci-link: https://github.com/ixti/sidekiq-throttled/actions/workflows/ci.yml\n:ci-badge: https://img.shields.io/github/actions/workflow/status/ixti/sidekiq-throttled/ci.yml?branch=main\u0026style=for-the-badge\n:gem-link: http://rubygems.org/gems/sidekiq-throttled\n:gem-badge: https://img.shields.io/gem/v/sidekiq-throttled?style=for-the-badge\n:doc-link: http://www.rubydoc.info/gems/sidekiq-throttled\n:doc-badge: https://img.shields.io/badge/Documentation-API-blue?style=for-the-badge\n\n****\n{ci-link}[image:{ci-badge}[CI Status]]\n{gem-link}[image:{gem-badge}[Latest Version]]\n{doc-link}[image:{doc-badge}[API Documentation]]\n****\n\nConcurrency and threshold throttling for https://github.com/sidekiq/sidekiq[Sidekiq].\n\n== Installation\n\nAdd this line to your application's Gemfile:\n\n[source,ruby]\n----\ngem \"sidekiq-throttled\"\n----\n\nAnd then execute:\n\n  $ bundle\n\nOr install it yourself as:\n\n  $ gem install sidekiq-throttled\n\n== Usage\n\nAdd somewhere in your app's bootstrap (e.g. `config/initializers/sidekiq.rb` if\nyou are using Rails):\n\n[source,ruby]\n----\nrequire \"sidekiq/throttled\"\n----\n\nOnce you've done that you can include `Sidekiq::Throttled::Job` to your\njob classes and configure throttling:\n\n[source,ruby]\n----\nclass MyJob\n  include Sidekiq::Job\n  include Sidekiq::Throttled::Job\n\n  sidekiq_options :queue =\u003e :my_queue\n\n  sidekiq_throttle(\n    # Allow maximum 10 concurrent jobs of this class at a time.\n    concurrency: { limit: 10 },\n    # Allow maximum 1K jobs being processed within one hour window.\n    threshold: { limit: 1_000, period: 1.hour }\n  )\n\n  def perform\n    # ...\n  end\nend\n----\n\nTIP: `Sidekiq::Throttled::Job` is aliased as `Sidekiq::Throttled::Worker`,\n  thus if you're using `Sidekiq::Worker` naming convention, you can use the\n  alias for consistency:\n\n[source,ruby]\n----\nclass MyWorker\n  include Sidekiq::Worker\n  include Sidekiq::Throttled::Worker\n\n  # ...\nend\n----\n\n\n=== Web UI\n\nTo add a Throttled tab to your sidekiq web dashboard, require it durring your\napplication initialization.\n\n[source,ruby]\n----\nrequire \"sidekiq/throttled/web\"\n----\n\n\n=== Configuration\n\n[source,ruby]\n----\nSidekiq::Throttled.configure do |config|\n  # Period in seconds to exclude queue from polling in case it returned\n  # {config.cooldown_threshold} amount of throttled jobs in a row. Set\n  # this value to `nil` to disable cooldown manager completely.\n  # Default: 1.0\n  config.cooldown_period = 1.0\n\n  # Exclude queue from polling after it returned given amount of throttled\n  # jobs in a row.\n  # Default: 100 (cooldown after hundredth throttled job in a row)\n  config.cooldown_threshold = 100\nend\n----\n\n[WARNING]\n.Cooldown Settings\n====\nIf a queue contains a thousand jobs in a row that will be throttled,\nthe cooldown will kick-in 10 times in a row, meaning it will take 10 seconds\nbefore all those jobs are put back at the end of the queue and you actually\nstart processing other jobs.\n\nYou may want to adjust the cooldown_threshold and cooldown_period,\nkeeping in mind that this will also impact the load on your Redis server.\n====\n\n==== Middleware(s)\n\n`Sidekiq::Throttled` relies on following bundled middlewares:\n\n* `Sidekiq::Throttled::Middlewares::Server`\n\nThe middleware is automatically injected when you require `sidekiq/throttled`.\nIn rare cases, when this causes an issue, you can change middleware order manually:\n\n[source,ruby]\n----\nSidekiq.configure_server do |config|\n  # ...\n\n  config.server_middleware do |chain|\n    chain.prepend(Sidekiq::Throttled::Middlewares::Server)\n  end\nend\n----\n\nSee: https://github.com/sidekiq/sidekiq/blob/main/lib/sidekiq/middleware/chain.rb\n\n\n=== Observer\n\nYou can specify an observer that will be called on throttling. To do so pass an\n`:observer` option with callable object:\n\n[source,ruby]\n----\nclass MyJob\n  include Sidekiq::Job\n  include Sidekiq::Throttled::Job\n\n  MY_OBSERVER = lambda do |strategy, *args|\n    # do something\n  end\n\n  sidekiq_options queue: :my_queue\n\n  sidekiq_throttle(\n    concurrency: { limit: 10 },\n    threshold:   { limit: 100, period: 1.hour },\n    observer:    MY_OBSERVER\n  )\n\n  def perform(*args)\n    # ...\n  end\nend\n----\n\nObserver will receive `strategy, *args` arguments, where `strategy` is a Symbol\n`:concurrency` or `:threshold`, and `*args` are the arguments that were passed\nto the job.\n\n\n=== Dynamic throttling\n\nYou can throttle jobs dynamically with `:key_suffix` option:\n\n[source,ruby]\n----\nclass MyJob\n  include Sidekiq::Job\n  include Sidekiq::Throttled::Job\n\n  sidekiq_options queue: :my_queue\n\n  sidekiq_throttle(\n    # Allow maximum 10 concurrent jobs per user at a time.\n    concurrency: { limit: 10, key_suffix: -\u003e (user_id) { user_id } }\n  )\n\n  def perform(user_id)\n    # ...\n  end\nend\n----\n\nYou can also supply dynamic values for limits and periods by supplying a proc\nfor these values. The proc will be evaluated at the time the job is fetched\nand will receive the same arguments that are passed to the job.\n\n[source,ruby]\n----\nclass MyJob\n  include Sidekiq::Job\n  include Sidekiq::Throttled::Job\n\n  sidekiq_options queue: :my_queue\n\n  sidekiq_throttle(\n    # Allow maximum 1000 concurrent jobs of this class at a time for VIPs and 10 for all other users.\n    concurrency: {\n      limit:      -\u003e(user_id) { User.vip?(user_id) ? 1_000 : 10 },\n      key_suffix: -\u003e(user_id) { User.vip?(user_id) ? \"vip\" : \"std\" }\n    },\n    # Allow 1000 jobs/hour to be processed for VIPs and 10/day for all others\n    threshold: {\n      limit:      -\u003e(user_id) { User.vip?(user_id) ? 1_000 : 10 },\n      period:     -\u003e(user_id) { User.vip?(user_id) ? 1.hour : 1.day },\n      key_suffix: -\u003e(user_id) { User.vip?(user_id) ? \"vip\" : \"std\" }\n    }\n  )\n\n  def perform(user_id)\n    # ...\n  end\nend\n----\n\nYou also can use several different keys to throttle one worker.\n\n[source,ruby]\n----\nclass MyJob\n  include Sidekiq::Job\n  include Sidekiq::Throttled::Job\n\n  sidekiq_options queue: :my_queue\n\n  sidekiq_throttle(\n    # Allow maximum 10 concurrent jobs per project at a time and maximum 2 jobs per user\n    concurrency: [\n      { limit: 10, key_suffix: -\u003e (project_id, user_id) { project_id } },\n      { limit: 2, key_suffix: -\u003e (project_id, user_id) { user_id } }\n    ]\n    # For :threshold it works the same\n  )\n\n  def perform(project_id, user_id)\n    # ...\n  end\nend\n----\n\nIMPORTANT: Don't forget to specify `:key_suffix` and make it return different\n  values if you are using dynamic limit/period options. Otherwise, you risk\n  getting into some trouble.\n\n[source,ruby]\n----\nclass MyJob\n  include Sidekiq::Job\n  include Sidekiq::Throttled::Job\n\n  sidekiq_options queue: :my_queue\n\n  sidekiq_throttle(\n    concurrency: { limit: 10 },\n    # Allow 500 jobs per minute, 5,000 per hour, and 50,000 per day:\n    threshold: [\n      { limit: 500, period: 1.minute, key_suffix: \"minutely\" },\n      { limit: 5_000, period: 1.hour, key_suffix: \"hourly\" },\n      { limit: 50_000, period: 1.day, key_suffix: \"daily\" },\n    ]\n  )\n\n  def perform(project_id, user_id)\n    # ...\n  end\nend\n----\n\nNOTE: `key_suffix` does not have to be a proc/lambda, it can just be a\n  string value. This can come in handy to set throttle limits for different\n  ranges of time\n\n=== Concurrency throttling fine-tuning\n\nConcurrency throttling is based on distributed locks. Those locks have default\ntime to live (TTL) set to 15 minutes. If your job takes more than 15 minutes\nto finish, lock will be released and you might end up with more jobs running\nconcurrently than you expect.\n\nThis is done to avoid deadlocks - when by any reason (e.g. Sidekiq process was\nOOM-killed) cleanup middleware wasn't executed and locks were not released.\n\nIf your job takes more than 15 minutes to complete, you can tune concurrency\nlock TTL to fit your needs:\n\n[source,ruby]\n----\n# Set concurrency strategy lock TTL to 1 hour.\nsidekiq_throttle(concurrency: { limit: 20, ttl: 1.hour.to_i })\n----\n\n\n== Supported Ruby Versions\n\nThis library aims to support and is tested against the following Ruby versions:\n\n* Ruby 2.7.x\n* Ruby 3.0.x\n* Ruby 3.1.x\n* Ruby 3.2.x\n* Ruby 3.3.x\n\nIf something doesn't work on one of these versions, it's a bug.\n\nThis library may inadvertently work (or seem to work) on other Ruby versions,\nhowever support will only be provided for the versions listed above.\n\nIf you would like this library to support another Ruby version or\nimplementation, you may volunteer to be a maintainer. Being a maintainer\nentails making sure all tests run and pass on that implementation. When\nsomething breaks on your implementation, you will be responsible for providing\npatches in a timely fashion. If critical issues for a particular implementation\nexist at the time of a major release, support for that Ruby version may be\ndropped.\n\n\n== Supported Sidekiq Versions\n\nThis library aims to support and work with following Sidekiq versions:\n\n* Sidekiq 7.0.x\n* Sidekiq 7.1.x\n* Sidekiq 7.2.x\n\nAnd the following Sidekiq Pro versions:\n\n* Sidekiq Pro 7.0.x\n* Sidekiq Pro 7.1.x\n* Sidekiq Pro 7.2.x\n\n== Development\n\n  bundle install\n  bundle exec appraisal generate\n  bundle exec appraisal install\n  bundle exec rake\n\n=== Sidekiq-Pro\n\nIf you're working on Sidekiq-Pro support make sure that you have Sidekiq-Pro\nlicense set either in the global config, or in `BUNDLE_GEMS\\__CONTRIBSYS__COM`\nenvironment variable.\n\n== Contributing\n\n* Fork sidekiq-throttled on GitHub\n* Make your changes\n* Ensure all tests pass (`bundle exec rake`)\n* Send a pull request\n* If we like them we'll merge them\n* If we've accepted a patch, feel free to ask for commit access!\n\n\n== Endorsement\n\nhttps://github.com/sensortower[image:sensortower.svg[SensorTower]]\n\nThe initial work on the project was initiated to address the needs of\nhttps://github.com/sensortower[SensorTower].\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fixti%2Fsidekiq-throttled","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fixti%2Fsidekiq-throttled","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fixti%2Fsidekiq-throttled/lists"}