Skip to content

Commit

Permalink
Merge pull request #82 from veeqo/optimize-bulk-unlock
Browse files Browse the repository at this point in the history
Optimize bulk unlocking
  • Loading branch information
sharshenov authored Dec 6, 2024
2 parents c70fc9e + 125e5cf commit 1ea6985
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

- [#86](https://github.com/veeqo/activejob-uniqueness/pull/86) Add Rails 8.0 rc1 support by[@sharshenov](https://github.com/sharshenov)

### Changed
- [#82](https://github.com/veeqo/activejob-uniqueness/pull/82) Optimize bulk unlocking [@sharshenov](https://github.com/sharshenov)

## [0.3.2](https://github.com/veeqo/activejob-uniqueness/compare/v0.3.1...v0.3.2) - 2024-08-16

### Added
Expand Down
8 changes: 7 additions & 1 deletion lib/active_job/uniqueness/lock_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ def delete_lock(resource)
true
end

DELETE_LOCKS_SCAN_COUNT = 1000

# Unlocks multiple resources by key wildcard.
def delete_locks(wildcard)
@servers.each do |server|
synced_redis_connection(server) do |conn|
conn.scan('MATCH', wildcard).each { |key| conn.call('DEL', key) }
cursor = 0
while cursor != '0'
cursor, keys = conn.call('SCAN', cursor, 'MATCH', wildcard, 'COUNT', DELETE_LOCKS_SCAN_COUNT)
conn.call('UNLINK', *keys) unless keys.empty?
end
end
end

Expand Down
22 changes: 22 additions & 0 deletions spec/active_job/uniqueness/unlock_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,26 @@
end
end
end

describe 'bulk deletion' do
subject(:unlock!) { described_class.unlock! }

let(:expected_initial_number_of_locks) { 1_103 } # 1_100 + 2 + 1
let(:expected_number_of_unlink_commands) { 2 } # 1103 / 1000 (ActiveJob::Uniqueness::LockManager::DELETE_LOCKS_SCAN_COUNT)

before { 1_100.times.each { |i| job_class.perform_later(3, i) } }

it 'removes locks efficiently' do
expect { unlock! }.to change { locks_count }.from(expected_initial_number_of_locks).to(0)
.and change { unlink_commands_calls }.by(expected_number_of_unlink_commands)
end

def unlink_commands_calls
info = redis.call('INFO', 'commandstats')
unlink_stats = info.split("\n").find { |line| line.start_with?('cmdstat_unlink:') }
return 0 unless unlink_stats

unlink_stats.match(/cmdstat_unlink:calls=(\d+)/)[1].to_i
end
end
end

0 comments on commit 1ea6985

Please sign in to comment.