{"id":26093176,"url":"https://github.com/lifebce/redis-single-file","last_synced_at":"2026-02-08T08:33:25.692Z","repository":{"id":275691339,"uuid":"925438020","full_name":"lifeBCE/redis-single-file","owner":"lifeBCE","description":"Redis Single File - Distributed Execution Synchronization","archived":false,"fork":false,"pushed_at":"2025-02-09T03:04:09.000Z","size":40,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T00:03:37.017Z","etag":null,"topics":["distributed-lock","distributed-queue","mutex","mutex-lock","mutex-synchronization","redis","redlock","ruby","semaphore","synchronization"],"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/lifeBCE.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.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}},"created_at":"2025-01-31T22:03:32.000Z","updated_at":"2025-02-09T03:04:12.000Z","dependencies_parsed_at":"2025-02-04T03:37:30.016Z","dependency_job_id":null,"html_url":"https://github.com/lifeBCE/redis-single-file","commit_stats":null,"previous_names":["lifebce/redis-single-file"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeBCE%2Fredis-single-file","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeBCE%2Fredis-single-file/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeBCE%2Fredis-single-file/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeBCE%2Fredis-single-file/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lifeBCE","download_url":"https://codeload.github.com/lifeBCE/redis-single-file/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248540462,"owners_count":21121362,"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-lock","distributed-queue","mutex","mutex-lock","mutex-synchronization","redis","redlock","ruby","semaphore","synchronization"],"created_at":"2025-03-09T11:55:48.763Z","updated_at":"2026-02-08T08:33:25.626Z","avatar_url":"https://github.com/lifeBCE.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://github.com/lifeBCE/redis-single-file/actions/workflows/build.yml/badge.svg)](https://github.com/lifeBCE/redis-single-file/actions/workflows/build.yml)\n[![RSpec Status](https://github.com/lifeBCE/redis-single-file/actions/workflows/rspec.yml/badge.svg)](https://github.com/lifeBCE/redis-single-file/actions/workflows/rspec.yml)\n[![Rubocop Status](https://github.com/lifeBCE/redis-single-file/actions/workflows/rubocop.yml/badge.svg)](https://github.com/lifeBCE/redis-single-file/actions/workflows/rubocop.yml)\n[![CodeQL Status](https://github.com/lifeBCE/redis-single-file/actions/workflows/codeql.yml/badge.svg)](https://github.com/lifeBCE/redis-single-file/actions/workflows/codeql.yml)\n\n# Redis Single File - Distributed Execution Synchronization\n\nRedis single file is a queue-based implementation of a remote/shared semaphore\nfor distributed execution synchronization. A distributed semaphore may be useful\nfor synchronizing execution across numerous instances or between the application\nand background job workers.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'redis-single-file'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install redis-single-file\n\n## Configuration\n\nConfigure redis single file via its configuration object.\n\n```ruby\nRedisSingleFile.configuration do |config|\n  # config.host = 'localhost'\n  # config.port = '6379'\n  # config.name = 'default'\n  # config.expire_in = 300\nend\n```\n\n## Usage Examples\n\n#### Default lock name and infinite blocking\n```ruby\n  semaphore = RedisSingleFile.new\n  semaphore.synchronize do\n    # synchronized logic defined here...\n  end\n```\n\n#### Named locks can provide exclusive synchronization\n```ruby\n   semaphore = RedisSingleFile.new(name: :user_cache_update)\n   semaphore.synchronize do\n      # synchronized logic defined here...\n   end\n```\n\n#### Prevent deadlocks by providing a timeout\n```ruby\n   semaphore = RedisSingleFile.new(name: :s3_file_upload)\n   semaphore.synchronize(timeout: 15) do\n      # synchronized logic defined here...\n   end\n```\n\n#### Use your own redis client instance\n```ruby\n   redis = Redis.new(...)\n   semaphore = RedisSingleFile.new(redis:)\n   semaphore.synchronize do\n      # synchronized logic defined here...\n   end\n```\n\n## Documentation\n\n### Distributed Queue Design\n\n\u003e [!IMPORTANT]\n\u003e The redis `blpop` command will attempt to pop (delete and return) a value from\n\u003e a queue but will block when no values are present in the queue. A timeout can\n\u003e be provided to prevent deadlock situations.\n\u003e\n\u003e To unblock (unlock) an instance, add/push an item to the queue. This is done\n\u003e one at a time to controll the serialization of the distrubuted execution. Redis\n\u003e selects the instance waiting the longest each time a new token is added.\n\n### Auto Expiration\n\n\u003e [!NOTE]\n\u003e All redis keys are expired and automatically removed after a certain period\n\u003e but will be recreated again on the next use. Each new client should face one\n\u003e of two scenarios when entering synchronization.\n\u003e\n\u003e 1. The mutex key is not set causing the client to create the keys and prime\n\u003e    the queue with its first token unlocking it for the first execution.\n\u003e\n\u003e 2. The mutex key is already set so the client will skip the priming and enter\n\u003e    directly into the queue where it should immediately find a token left by\n\u003e    the last client upon completion or block waiting for the current client to\n\u003e    finish execution.\n\n### Considerations over redlock approach\n\n[Redlock](https://github.com/leandromoreira/redlock-rb) is the current standard and the official approach [suggested by redis themselves](https://redis.io/docs/latest/develop/use/patterns/distributed-locks/) but the design does have some complexities/drawbacks that some may wish to avoid. The following is a list of pros and cons of redis single file over redlock.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003ePro:\u003c/code\u003e Multi-master redis node configuration not required\u003c/summary\u003e\n\u003cbr /\u003e\n\u003cblockquote\u003e\nThe redlock design requires a multi-master redis node setup where each node is completely independent of the others (no replication). This would be uncommon in most standard application deployment environments so a seperate redis setup would be required just for the distributed lock management.\n\u003cbr /\u003e\u003cbr /\u003e\nRedis single file will work with your existing redis configuration so no need to maintain a seperate redis setup for the application of distributed semaphores.\n\u003c/blockquote\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003ePro:\u003c/code\u003e No polling or waiting logic needed as redis does all the blocking\u003c/summary\u003e\n\u003cbr /\u003e\n\u003cblockquote\u003e\nThe redlock design requires the client to enter into a polling loop checking for the ability to execute its logic repeatedly. This approach is less efficient and requires quite a bit more logic to accomplish also making it more prone to error.\n\u003cbr /\u003e\u003cbr /\u003e\nRedis single file pushes much of this responsibility off to redis itself with the use of the \u003ccode\u003eblpop\u003c/code\u003e command. Redis will block on that call when no item is present in the queue and will allocate tokens to competing clients waiting their turn on a `first-come, first-served basis`.\n\u003c/blockquote\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003ePro:\u003c/code\u003e Replication lag is not a concern with \u003ccode\u003eblpop\u003c/code\u003e\u003c/summary\u003e\n\u003cbr /\u003e\n\u003cblockquote\u003e\nThe redlock design requires a multi-master setup given it utilizes read operations that could be delegated to a read replica in a standard clustered redis deployement. Redis replication is handled in an async manner so replication lag can hinder distributed synchronization when using read operations against a cluster utlizing replication.\n\u003cbr /\u003e\u003cbr /\u003e\nRedis single file is not susceptible to this limitation given that \u003ccode\u003eblpop\u003c/code\u003e is a write operation meaning it will always be handled by the master node eliminating concerns over replication lag.\n\u003c/blockquote\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ccode\u003eCon:\u003c/code\u003e Redis cluster failover could disrupt currently queued clients\u003c/summary\u003e\n\u003cbr /\u003e\n\u003cblockquote\u003e\nRedis single file does attempt to recognize a connection failure and proceeds in rejoining the queue when detected but there is still a small chance that a cluster failover could cause already queued clients to have issues.\n\u003cbr /\u003e\u003cbr /\u003e\nRedlock is not susceptible to this given the use of the multi-master deployment and absence of read-replicas so cluster failover (and recovery) is not a concern.\n\u003c/blockquote\u003e\n\u003c/details\u003e\n\n## Run Tests\n\n    $ bundle exec rspec\n\n```spec\nFinished in 0.00818 seconds (files took 0.09999 seconds to load)\n22 examples, 0 failures\n```\n\n## Benchmark\n\n    $ bundle exec ruby benchmark.rb\n\n```ruby\nruby 3.2.0 (2022-12-25 revision a528908271) [arm64-darwin22]\nWarming up --------------------------------------\n         synchronize   434.000 i/100ms\n        synchronize!   434.000 i/100ms\n      threaded (10x)    29.000 i/100ms\n        forked (10x)     8.000 i/100ms\nCalculating -------------------------------------\n         synchronize      4.329k (± 1.9%) i/s  (230.98 μs/i) -     21.700k in   5.014460s\n        synchronize!      4.352k (± 0.3%) i/s  (229.79 μs/i) -     22.134k in   5.086272s\n      threaded (10x)    249.794 (±28.4%) i/s    (4.00 ms/i) -      1.073k in   5.058461s\n        forked (10x)     56.588 (± 3.5%) i/s   (17.67 ms/i) -    288.000 in   5.097885s\n\nComparison:\n        synchronize!:     4351.8 i/s\n         synchronize:     4329.4 i/s - same-ish: difference falls within error\n      threaded (10x):      249.8 i/s - 17.42x  slower\n        forked (10x):       56.6 i/s - 76.90x  slower\n```\n\n## Cluster Management\n\nAfter installing redis locally, you can use the provided `bin/cluster` script to manage a local cluster. To customize your local cluster, edit the `bin/cluster` script to provide your own values for the following script variables.\n\n```bash\n#\n# configurable settings\n#\nHOST=127.0.0.1\nPORT=30000\nMASTERS=3  # min 3 for cluster\nREPLICAS=2 # replicas per master\nTIMEOUT=2000\nPROTECTED_MODE=yes\nADDITIONAL_OPTIONS=\"\"\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eStart cluster nodes\u003c/strong\u003e\u003c/summary\u003e\n\n    $ bin/cluster start\n\n```console\nStarting 30001\nStarting 30002\nStarting 30003\nStarting 30004\nStarting 30005\nStarting 30006\nStarting 30007\nStarting 30008\nStarting 30009\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eCreate cluster configuration\u003c/strong\u003e\u003c/summary\u003e\n\n    $ bin/cluster create -f\n\n```console\n\u003e\u003e\u003e Performing hash slots allocation on 9 nodes...\nMaster[0] -\u003e Slots 0 - 5460\nMaster[1] -\u003e Slots 5461 - 10922\nMaster[2] -\u003e Slots 10923 - 16383\nAdding replica 127.0.0.1:30005 to 127.0.0.1:30001\nAdding replica 127.0.0.1:30006 to 127.0.0.1:30001\nAdding replica 127.0.0.1:30007 to 127.0.0.1:30002\nAdding replica 127.0.0.1:30008 to 127.0.0.1:30002\nAdding replica 127.0.0.1:30009 to 127.0.0.1:30003\nAdding replica 127.0.0.1:30004 to 127.0.0.1:30003\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eStop cluster nodes\u003c/strong\u003e\u003c/summary\u003e\n\n    $ bin/cluster stop\n\n```console\nStopping 30001\nStopping 30002\nStopping 30003\nStopping 30004\nStopping 30005\nStopping 30006\nStopping 30007\nStopping 30008\nStopping 30009\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eClean local cluster files\u003c/strong\u003e\u003c/summary\u003e\n\n    $ bin/cluster clean\n\n```console\nCleaning *.log\nCleaning appendonlydir-*\nCleaning dump-*.rdb\nCleaning nodes-*.conf\n```\n\u003c/details\u003e\n\nAfter the cluster is running and configured, you can direct the `test.rb` and `benchmark.rb` scripts at the cluster by setting the port on execution.\n\n    $ REDIS_PORT=30001 bundle exec ruby benchmark.rb\n\n## Disclaimer\n\n\u003e [!WARNING]\n\u003e Make sure you understand the limitations and reliability inherent in this implementation prior to using it in a production environment. No guarantees are made. Use at your own risk!\n\n## Inspiration\n\nInspiration for this gem was taken from a number of existing projects. It would be beneficial for anyone interested to take a look at all 3.\n\n1. [Redlock](https://github.com/leandromoreira/redlock-rb)\n2. [redis-semaphore](https://github.com/dv/redis-semaphore)\n3. [redis-mutex](https://github.com/kenn/redis-mutex)\n\n## Contributing\n\n1. [Fork it](https://github.com/lifeBCE/redis-single-file/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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifebce%2Fredis-single-file","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flifebce%2Fredis-single-file","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifebce%2Fredis-single-file/lists"}