{"id":13878662,"url":"https://github.com/veeqo/activejob-uniqueness","last_synced_at":"2025-05-14T15:10:42.756Z","repository":{"id":37796893,"uuid":"276727861","full_name":"veeqo/activejob-uniqueness","owner":"veeqo","description":"Unique jobs for ActiveJob. Ensure the uniqueness of jobs in the queue.","archived":false,"fork":false,"pushed_at":"2024-12-07T14:12:32.000Z","size":142,"stargazers_count":286,"open_issues_count":12,"forks_count":28,"subscribers_count":21,"default_branch":"main","last_synced_at":"2025-04-13T12:41:39.372Z","etag":null,"topics":["activejob","distributed-locks","lock","rails","redis","uniqueness"],"latest_commit_sha":null,"homepage":"https://devs.veeqo.com/job-uniqueness-for-activejob/","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/veeqo.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,"zenodo":null}},"created_at":"2020-07-02T19:18:42.000Z","updated_at":"2025-04-03T13:19:16.000Z","dependencies_parsed_at":"2024-01-13T20:37:09.754Z","dependency_job_id":"6b536004-2160-4f92-bdeb-64faf84dc636","html_url":"https://github.com/veeqo/activejob-uniqueness","commit_stats":{"total_commits":80,"total_committers":12,"mean_commits":6.666666666666667,"dds":0.375,"last_synced_commit":"7a56859976e9e2c3aa3dfab088d5644bc0e23ae5"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veeqo%2Factivejob-uniqueness","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veeqo%2Factivejob-uniqueness/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veeqo%2Factivejob-uniqueness/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/veeqo%2Factivejob-uniqueness/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/veeqo","download_url":"https://codeload.github.com/veeqo/activejob-uniqueness/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254170055,"owners_count":22026219,"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":["activejob","distributed-locks","lock","rails","redis","uniqueness"],"created_at":"2024-08-06T08:01:56.034Z","updated_at":"2025-05-14T15:10:42.736Z","avatar_url":"https://github.com/veeqo.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Job uniqueness for ActiveJob\n[![Build Status](https://github.com/veeqo/activejob-uniqueness/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/veeqo/activejob-uniqueness/actions/workflows/main.yml) [![Gem Version](https://badge.fury.io/rb/activejob-uniqueness.svg)](https://badge.fury.io/rb/activejob-uniqueness)\n\nThe gem allows to protect job uniqueness with next strategies:\n\n| Strategy | The job is locked | The job is unlocked |\n|-|-|-|\n| `until_executing` | when **pushed** to the queue | when **processing starts** |\n| `until_executed` | when **pushed** to the queue | when the job is **processed successfully** |\n| `until_expired` | when **pushed** to the queue | when the lock is **expired** |\n| `until_and_while_executing` | when **pushed** to the queue | when **processing starts**\u003cbr\u003ea runtime lock is acquired to **prevent simultaneous jobs**\u003cbr\u003e*has extra options: `runtime_lock_ttl`, `on_runtime_conflict`* |\n| `while_executing` | when **processing starts** | when the job is **processed**\u003cbr\u003ewith any result including an error |\n\nInspired by [SidekiqUniqueJobs](https://github.com/mhenrixon/sidekiq-unique-jobs), uses [Redlock](https://github.com/leandromoreira/redlock-rb) under the hood.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.veeqo.com/\" title=\"Sponsored by Veeqo\"\u003e\n    \u003cimg src=\"https://static.veeqo.com/assets/sponsored_by_veeqo.png\" width=\"360\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n## Installation\n\nAdd the `activejob-uniqueness` gem to your Gemfile.\n\n```ruby\ngem 'activejob-uniqueness'\n```\n\nIf you want jobs unlocking for Sidekiq Web UI, require the patch explicitly. [**Queues cleanup becomes slower!**](#sidekiq-api-support)\n```ruby\ngem 'activejob-uniqueness', require: 'active_job/uniqueness/sidekiq_patch'\n```\n\nAnd run `bundle install` command.\n\n## Configuration\n\nActiveJob::Uniqueness is ready to work without any configuration. It will use `REDIS_URL` to connect to Redis instance.\nTo override the defaults, create an initializer `config/initializers/active_job_uniqueness.rb` using the following command:\n\n```sh\nrails generate active_job:uniqueness:install\n```\n\nThis gem relies on `redlock` for it's Redis connection, that means **it will not inherit global configuration of `Sidekiq`**. To configure the connection, you can use `config.redlock_servers`, for example to disable SSL verification for Redis/Key-Value cloud providers:\n\n```ruby\nActiveJob::Uniqueness.configure do |config|\n  config.redlock_servers = [\n    RedisClient.new(\n      url: ENV['REDIS_URL'],\n      ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE }\n    )\n  ]\nend\n```\n\n## Usage\n\n\n### Make the job to be unique\n\n```ruby\nclass MyJob \u003c ActiveJob::Base\n  # new jobs with the same args will raise error until existing one is executed\n  unique :until_executed\n\n  def perform(args)\n    # work\n  end\nend\n```\n\n### Tune uniqueness settings per job\n\n```ruby\nclass MyJob \u003c ActiveJob::Base\n  # new jobs with the same args will be logged within 3 hours or until existing one is being executing\n  unique :until_executing, lock_ttl: 3.hours, on_conflict: :log\n\n  def perform(args)\n    # work\n  end\nend\n```\n\nYou can set defaults globally with [the configuration](#configuration)\n\n### Control lock conflicts\n\n```ruby\nclass MyJob \u003c ActiveJob::Base\n  # Proc gets the job instance including its arguments\n  unique :until_executing, on_conflict: -\u003e(job) { job.logger.info \"Oops: #{job.arguments}\" }\n\n  def perform(args)\n    # work\n  end\nend\n```\n\n### Control redis connection errors\n\n```ruby\nclass MyJob \u003c ActiveJob::Base\n  # Proc gets the job instance including its arguments, and as keyword arguments the resource(lock key) `resource` and the original error `error`\n  unique :until_executing, on_redis_connection_error: -\u003e(job, resource: _, error: _) { job.logger.info \"Oops: #{job.arguments}\" }\n\n  def perform(args)\n    # work\n  end\nend\n```\n\n### Control lock key arguments\n\n```ruby\nclass MyJob \u003c ActiveJob::Base\n  unique :until_executed\n\n  def perform(foo, bar, baz)\n    # work\n  end\n\n  def lock_key_arguments\n    arguments.first(2) # baz is ignored\n  end\nend\n```\n\n### Control the lock key\n\n```ruby\nclass MyJob \u003c ActiveJob::Base\n  unique :until_executed\n\n  def perform(foo, bar, baz)\n    # work\n  end\n\n  def lock_key\n    'qux' # completely custom lock key\n  end\n\n  def runtime_lock_key\n    'quux' # completely custom runtime lock key for :until_and_while_executing\n  end\nend\n```\n\n### Unlock jobs manually\n\nThe selected strategy automatically unlocks jobs, but in some cases (e.g. the queue is purged) it is handy to unlock jobs manually.\n\n```ruby\n# Remove the lock for particular arguments:\nMyJob.unlock!(foo: 'bar')\n# or\nActiveJob::Uniqueness.unlock!(job_class_name: 'MyJob', arguments: [{foo: 'bar'}])\n\n# Remove all locks of MyJob\nMyJob.unlock!\n# or\nActiveJob::Uniqueness.unlock!(job_class_name: 'MyJob')\n\n# Remove all locks\nActiveJob::Uniqueness.unlock!\n```\n\n## Test mode\n\nMost probably you don't want jobs to be locked in tests. Add this line to your test suite (`rails_helper.rb`):\n\n```ruby\nActiveJob::Uniqueness.test_mode!\n```\n\n## Logging\n\nActiveJob::Uniqueness instruments `ActiveSupport::Notifications` with next events:\n* `lock.active_job_uniqueness`\n* `runtime_lock.active_job_uniqueness`\n* `unlock.active_job_uniqueness`\n* `runtime_unlock.active_job_uniqueness`\n* `conflict.active_job_uniqueness`\n* `runtime_conflict.active_job_uniqueness`\n\nAnd then writes to `ActiveJob::Base.logger`.\n\n**ActiveJob prior to version `6.1` will always log `Enqueued MyJob (Job ID) ...` even if the callback chain is halted. [Details](https://github.com/rails/rails/pull/37830)**\n\n## Testing\n\nRun redis server (in separate console):\n```\ndocker run --rm -p 6379:6379 redis\n```\n\nRun tests with:\n\n```sh\nbundle\nrake\n```\n\n## Sidekiq API support\n\nActiveJob::Uniqueness supports Sidekiq API to unset job locks on queues cleanup (e.g. via Sidekiq Web UI). Starting Sidekiq 5.1 job death also triggers locks cleanup.\nTake into account that **[big queues cleanup becomes much slower](https://github.com/veeqo/activejob-uniqueness/issues/16)** because each job is being unlocked individually. In order to activate Sidekiq API patch require it explicitly in your Gemfile:\n\n```ruby\ngem 'activejob-uniqueness', require: 'active_job/uniqueness/sidekiq_patch'\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/veeqo/activejob-uniqueness.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## About [Veeqo](https://www.veeqo.com)\n\nAt Veeqo, our team of Engineers is on a mission to create a world-class Inventory and Shipping platform, built to the highest standards in best coding practices. We are a growing team, looking for other passionate developers to [join us](https://veeqo-ltd.breezy.hr/) on our journey. If you're looking for a career working for one of the most exciting tech companies in ecommerce, we want to hear from you.\n\n[Veeqo developers blog](https://devs.veeqo.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fveeqo%2Factivejob-uniqueness","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fveeqo%2Factivejob-uniqueness","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fveeqo%2Factivejob-uniqueness/lists"}