Skip to content

Commit 3128f06

Browse files
committed
Improve sidekiq-unique-jobs orphan lock cleanup
Configure reaper with resurrector enabled, add unique jobs middleware, and add lock to SyncIssuesWorker. Add rake tasks for listing and clearing locks manually, with weekly cleanup cron.
1 parent 6202cd9 commit 3128f06

File tree

5 files changed

+123
-6
lines changed

5 files changed

+123
-6
lines changed

app.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
{
2424
"command": "bundle exec rake hosts:sync_all",
2525
"schedule": "0 0 * * *"
26+
},
27+
{
28+
"command": "bundle exec rake sidekiq:unique_jobs:clear",
29+
"schedule": "0 0 * * 0"
2630
}
2731
]
2832
}

app/sidekiq/sync_issues_worker.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class SyncIssuesWorker
22
include Sidekiq::Worker
33
include Sidekiq::Status::Worker
44

5-
sidekiq_options queue: :default
5+
sidekiq_options queue: :default, lock: :until_executed, lock_expiration: 1.day.to_i
66

77
def perform(job_id)
88
Job.find_by_id!(job_id).perform_issue_syncing

config/sidekiq.rb

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
11
require 'sidekiq'
22
require 'sidekiq-status'
33

4+
SidekiqUniqueJobs.configure do |config|
5+
config.reaper = :ruby
6+
config.reaper_count = 2_000
7+
config.reaper_interval = 60
8+
config.reaper_timeout = 15
9+
10+
config.reaper_resurrector_enabled = true
11+
config.reaper_resurrector_interval = 300
12+
end
13+
414
Sidekiq.configure_client do |config|
515
config.logger = Rails.logger if Rails.env.test?
6-
# accepts :expiration (optional)
16+
config.redis = { ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }
17+
718
Sidekiq::Status.configure_client_middleware config, expiration: 60.minutes.to_i
19+
20+
config.client_middleware do |chain|
21+
chain.add SidekiqUniqueJobs::Middleware::Client
22+
end
823
end
924

1025
Sidekiq.configure_server do |config|
11-
# accepts :expiration (optional)
12-
Sidekiq::Status.configure_server_middleware config, expiration: 60.minutes.to_i
26+
config.redis = { ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }
1327

14-
# accepts :expiration (optional)
28+
Sidekiq::Status.configure_server_middleware config, expiration: 60.minutes.to_i
1529
Sidekiq::Status.configure_client_middleware config, expiration: 60.minutes.to_i
16-
end
30+
31+
config.client_middleware do |chain|
32+
chain.add SidekiqUniqueJobs::Middleware::Client
33+
end
34+
35+
config.server_middleware do |chain|
36+
chain.add SidekiqUniqueJobs::Middleware::Server
37+
end
38+
39+
SidekiqUniqueJobs::Server.configure(config)
40+
end

lib/tasks/sidekiq.rake

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace :sidekiq do
2+
namespace :unique_jobs do
3+
desc "List orphaned unique job locks"
4+
task list: :environment do
5+
digests = SidekiqUniqueJobs::Digests.new
6+
entries = digests.entries(pattern: "*", count: 10_000)
7+
if entries.empty?
8+
puts "No unique job locks found."
9+
else
10+
puts "#{entries.size} unique job lock(s):"
11+
entries.each do |digest, score|
12+
time = Time.at(score).utc rescue score
13+
puts " #{digest} (scored at #{time})"
14+
end
15+
end
16+
end
17+
18+
desc "Clear all unique job locks"
19+
task clear: :environment do
20+
digests = SidekiqUniqueJobs::Digests.new
21+
entries = digests.entries(pattern: "*", count: 10_000)
22+
if entries.empty?
23+
puts "No unique job locks to clear."
24+
else
25+
puts "Clearing #{entries.size} unique job lock(s)..."
26+
digests.delete_by_pattern("*", count: 10_000)
27+
puts "Done."
28+
end
29+
30+
expiring = SidekiqUniqueJobs::ExpiringDigests.new
31+
expiring_entries = expiring.entries(pattern: "*", count: 10_000)
32+
unless expiring_entries.empty?
33+
puts "Clearing #{expiring_entries.size} expiring digest(s)..."
34+
expiring.delete_by_pattern("*", count: 10_000)
35+
puts "Done."
36+
end
37+
end
38+
39+
desc "Clear unique job locks matching a pattern (e.g. PATTERN='*SyncIssues*')"
40+
task clear_matching: :environment do
41+
pattern = ENV.fetch("PATTERN") { abort "Usage: rake sidekiq:unique_jobs:clear_matching PATTERN='*SyncIssues*'" }
42+
digests = SidekiqUniqueJobs::Digests.new
43+
entries = digests.entries(pattern: pattern, count: 10_000)
44+
if entries.empty?
45+
puts "No locks matching '#{pattern}'."
46+
else
47+
puts "Clearing #{entries.size} lock(s) matching '#{pattern}'..."
48+
digests.delete_by_pattern(pattern, count: 10_000)
49+
puts "Done."
50+
end
51+
end
52+
end
53+
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require "test_helper"
2+
require "rake"
3+
4+
class SidekiqUniqueJobsRakeTest < ActiveSupport::TestCase
5+
setup do
6+
Rails.application.load_tasks unless Rake::Task.task_defined?("sidekiq:unique_jobs:list")
7+
@digests = SidekiqUniqueJobs::Digests.new
8+
end
9+
10+
test "list task runs without error" do
11+
assert_nothing_raised do
12+
capture_io { Rake::Task["sidekiq:unique_jobs:list"].execute }
13+
end
14+
end
15+
16+
test "clear task runs without error" do
17+
assert_nothing_raised do
18+
capture_io { Rake::Task["sidekiq:unique_jobs:clear"].execute }
19+
end
20+
end
21+
22+
test "clear_matching task aborts without PATTERN" do
23+
assert_raises(SystemExit) do
24+
capture_io { Rake::Task["sidekiq:unique_jobs:clear_matching"].execute }
25+
end
26+
end
27+
28+
test "clear_matching task runs with PATTERN" do
29+
ENV["PATTERN"] = "*"
30+
assert_nothing_raised do
31+
capture_io { Rake::Task["sidekiq:unique_jobs:clear_matching"].execute }
32+
end
33+
ensure
34+
ENV.delete("PATTERN")
35+
end
36+
end

0 commit comments

Comments
 (0)