{"id":13879996,"url":"https://github.com/lipanski/slow-down","last_synced_at":"2025-04-14T14:23:37.310Z","repository":{"id":28227335,"uuid":"31731857","full_name":"lipanski/slow-down","owner":"lipanski","description":"A centralized Redis-based lock to help you wait on throttled resources","archived":false,"fork":false,"pushed_at":"2022-01-09T14:44:05.000Z","size":65,"stargazers_count":20,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T03:23:27.089Z","etag":null,"topics":["distributed","gem","lock","locks","redis-lock","ruby"],"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/lipanski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-05T19:27:03.000Z","updated_at":"2023-03-20T13:27:35.000Z","dependencies_parsed_at":"2022-08-21T01:50:35.381Z","dependency_job_id":null,"html_url":"https://github.com/lipanski/slow-down","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lipanski%2Fslow-down","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lipanski%2Fslow-down/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lipanski%2Fslow-down/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lipanski%2Fslow-down/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lipanski","download_url":"https://codeload.github.com/lipanski/slow-down/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248681493,"owners_count":21144700,"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":["distributed","gem","lock","locks","redis-lock","ruby"],"created_at":"2024-08-06T08:02:42.781Z","updated_at":"2025-04-14T14:23:37.225Z","avatar_url":"https://github.com/lipanski.png","language":"Ruby","readme":"# SlowDown\n\n[![Gem Version](https://badge.fury.io/rb/slow_down.svg)](http://badge.fury.io/rb/slow_down)\n[![Build Status](https://travis-ci.com/lipanski/slow-down.svg?branch=master)](https://travis-ci.com/lipanski/slow-down)\n\n## Why would you want to slow down your requests?!\n\nSome external APIs might be throttling your requests (or web scraping attempts) or your own infrastructure is not able to bear the load.\nIt sometimes pays off to be patient...\n\n**SlowDown** delays a call up until the point where you can afford to trigger it.\nIt relies on a Redis lock so it should be able to handle a cluster of servers all going for the same resource.\nIt's based on the `PX` and `NX` options of the Redis `SET` command, which should make it thread-safe.\nNote that these options were introduced with Redis version 2.6.12.\n\n## Installation\n\nAdd it to your *Gemfile*:\n\n```ruby\ngem \"slow_down\"\n```\n\n...and call `bundle install`.\n\n## Usage\n\n### Basic\n\n```ruby\nrequire \"slow_down\"\n\nSlowDown.config do |c|\n  c.requests_per_second = 10\n  c.retries = 50 # times\n  c.timeout = 5 # seconds\n  c.raise_on_timeout = true # will raise SlowDown::Timeout\n  c.redis_url = \"redis://localhost:6379/0\" # or set the REDIS_URL environment variable\nend\n\nSlowDown.run do\n  some_throttled_api_call # accepting only 10 req/sec\nend\n```\n\n### Groups\n\n**SlowDown** can be configured for individual groups, which can be run in isolation:\n\n```ruby\nSlowDown.config(:github) do |c|\n  c.requests_per_second = 50\n  c.timeout = 10\nend\n\nSlowDown.config(:twitter) do |c|\n  c.requests_per_second = 10\n  c.timeout = 1\nend\n\n# Acquire a lock for the :github group\nSlowDown.run(:github) { ... }\n\n# Acquire a lock for the :twitter group\nSlowDown.run(:twitter) { ... }\n```\n\n### Retrieve configuration\n\nWhen called without a block, `SlowDown.config` will return the configuration of the *default* group.\nIn order to fetch the configuration of a different group use `SlowDown.config(:group_name)`.\n\n### Inline configuration\n\n**SlowDown** may also be configured directly within the `SlowDown.run` call:\n\n```ruby\n# Configure the :default group and run a call\nSlowDown.run(requests_per_second: 5, timeout: 15, raise_on_timeout: true) do\n  # ...\nend\n\n# Configure a different group and run a call within that group\nSlowDown.run(:my_group, requests_per_second: 2, timeout: 1) do\n  # ...\nend\n```\n\n### Defaults \u0026 available options\n\n```ruby\nSlowDown.config do |c|\n  # The allowed number of calls per second.\n  c.requests_per_second = 10\n\n  # The number of seconds during which SlowDown will try and acquire the resource\n  # for a given call.\n  c.timeout = 5\n\n  # Whether to raise an error when the timeout was reached and the resource could\n  # not be acquired.\n  # Raises SlowDown::Timeout.\n  c.raise_on_timeout = false\n\n  # How many retries should be performed til the timeout is reached.\n  c.retries = 30\n\n  # The algorithm used to schedule the amount of time to wait between retries.\n  # Available strategies: :linear, :inverse_exponential, :fibonacci or a class\n  # extending SlowDown::Strategy::Base.\n  c.retry_strategy = :linear\n\n  # Redis can be configured either directly, by setting a Redis instance to this\n  # variable, or via the REDIS_URL environment variable or via the redis_url\n  # setting.\n  c.redis = nil\n\n  # Configure Redis via the instance URL.\n  c.redis_url = nil\n\n  # The Redis namespace to apply to all locks.\n  c.redis_namespace = :slow_down\n\n  # The namespace to apply to the default group.\n  # Individual groups will overwrite this with the group name.\n  c.lock_namespace = :default\n\n  # Set this to a path or file descriptor in order to log to file.\n  c.log_path = STDOUT\n\n  # By default, the SlowDown logger is disabled.\n  # Set this to Logger::DEBUG, Logger::INFO or Logger::ERROR for logging various\n  # runtime information.\n  c.log_level = Logger::UNKNOWN\nend\n```\n\n### Non-blocking checks\n\nA call to `.run` will halt until the resource is either acquired or the timeout kicks in.\nIn order to make a non-blocking check, you can use the `SlowDown.free?` method.\n\n```ruby\nSlowDown.config do |c|\n  c.requests_per_second = 2\nend\n\nSlowDown.free? # true\nSlowDown.free? # true\nSlowDown.free? # false (won't wait)\nsleep(1)\nSlowDown.free? # true\n```\n\nThe `SlowDown.free?` method also works with **groups** and **inline configuration**:\n\n```ruby\ndef register_user(name, address, phone)\n  user.name = name\n\n  # Optional: geocode address, if we didn't exceed the request limit\n  if SlowDown.free?(:geocoding, requests_per_second: 5)\n    user.coordinates = geocode(address)\n  end\n\n  # Optional: send SMS, if we didn't exceed the request limit\n  if SlowDown.free?(:sms, requests_per_second: 10)\n    send_sms(phone)\n  end\n\n  user.save\nend\n```\n\n### Resetting the locks\n\nIf you ever need to reset the locks, you can do that for any group by calling:\n\n```ruby\nSlowDown.reset(:group_name)\n```\n\n### Polling strategies\n\nWhen a request is placed that can't access the lock right away, **SlowDown** puts it to sleep and schedules it to wake up \u0026 try again for the amount of retries configured by the user (defaulting to 30 retries).\n\nThe spread of these *retry sessions* can be linear (default behaviour) or non-linear - in case you want to simulate different strategies:\n\n1. **FIFO**: Inverse exponential series - set `SlowDown.config { |c| c.retry_strategy = :inverse_exponential }`\n2. **LIFO**: Fibonacci series - set `SlowDown.config { |c| c.retry_strategy = :fibonacci }`\n\nThese polling strategies are just a proof of concept and their behaviour relies more on probabilities.\n\n## Inspiration\n\n- [Distributed locks using Redis](https://engineering.gosquared.com/distributed-locks-using-redis)\n- [Redis SET Documentation](http://redis.io/commands/set)\n- [mario-redis-lock](https://github.com/marioizquierdo/mario-redis-lock)\n- [redlock-rb](https://github.com/antirez/redlock-rb)\n\n## Quotes\n\n- *It's like sleep but more classy*\n- *It's like sleep but over-engineered*\n- *SlowHand*\n\n## Development\n\nRun tests:\n\n```\nbundle exec rake\n```\n\n## Contributing\n\n1. Fork it ( https://github.com/lipanski/slow_down/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flipanski%2Fslow-down","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flipanski%2Fslow-down","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flipanski%2Fslow-down/lists"}