Don't schedule ProjectCacheWorker unless needed

This changes ProjectCacheWorker.perform_async so it only schedules a job
when no lease for the given project is present. This ensures we don't
end up scheduling hundreds of jobs when they won't be executed anyway.
This commit is contained in:
Yorick Peterse 2016-10-25 16:01:24 +02:00
parent 9a6770388c
commit 3b4af59a5f
No known key found for this signature in database
GPG key ID: EDD30D2BEB691AC9
6 changed files with 77 additions and 18 deletions

View file

@ -27,6 +27,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix unauthorized users dragging on issue boards - Fix unauthorized users dragging on issue boards
- Better handle when no users were selected for adding to group or project. (Linus Thiel) - Better handle when no users were selected for adding to group or project. (Linus Thiel)
- Only show register tab if signup enabled. - Only show register tab if signup enabled.
- Only schedule ProjectCacheWorker jobs when needed
## 8.13.0 (2016-10-22) ## 8.13.0 (2016-10-22)
- Removes extra line for empty issue description. (!7045) - Removes extra line for empty issue description. (!7045)

View file

@ -9,6 +9,18 @@ class ProjectCacheWorker
LEASE_TIMEOUT = 15.minutes.to_i LEASE_TIMEOUT = 15.minutes.to_i
def self.lease_for(project_id)
Gitlab::ExclusiveLease.
new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT)
end
# Overwrite Sidekiq's implementation so we only schedule when actually needed.
def self.perform_async(project_id)
# If a lease for this project is still being held there's no point in
# scheduling a new job.
super unless lease_for(project_id).exists?
end
def perform(project_id) def perform(project_id)
if try_obtain_lease_for(project_id) if try_obtain_lease_for(project_id)
Rails.logger. Rails.logger.
@ -37,8 +49,6 @@ class ProjectCacheWorker
end end
def try_obtain_lease_for(project_id) def try_obtain_lease_for(project_id)
Gitlab::ExclusiveLease. self.class.lease_for(project_id).try_obtain
new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT).
try_obtain
end end
end end

View file

@ -27,7 +27,7 @@ module Gitlab
# on begin/ensure blocks to cancel a lease, because the 'ensure' does # on begin/ensure blocks to cancel a lease, because the 'ensure' does
# not always run. Think of 'kill -9' from the Unicorn master for # not always run. Think of 'kill -9' from the Unicorn master for
# instance. # instance.
# #
# If you find that leases are getting in your way, ask yourself: would # If you find that leases are getting in your way, ask yourself: would
# it be enough to lower the lease timeout? Another thing that might be # it be enough to lower the lease timeout? Another thing that might be
# appropriate is to only use a lease for bulk/automated operations, and # appropriate is to only use a lease for bulk/automated operations, and
@ -48,6 +48,13 @@ module Gitlab
end end
end end
# Returns true if the key for this lease is set.
def exists?
Gitlab::Redis.with do |redis|
redis.exists(redis_key)
end
end
# No #cancel method. See comments above! # No #cancel method. See comments above!
private private

View file

@ -1,21 +1,36 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::ExclusiveLease do describe Gitlab::ExclusiveLease, type: :redis do
it 'cannot obtain twice before the lease has expired' do let(:unique_key) { SecureRandom.hex(10) }
lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
expect(lease.try_obtain).to eq(true) describe '#try_obtain' do
expect(lease.try_obtain).to eq(false) it 'cannot obtain twice before the lease has expired' do
lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
expect(lease.try_obtain).to eq(true)
expect(lease.try_obtain).to eq(false)
end
it 'can obtain after the lease has expired' do
timeout = 1
lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
lease.try_obtain # start the lease
sleep(2 * timeout) # lease should have expired now
expect(lease.try_obtain).to eq(true)
end
end end
it 'can obtain after the lease has expired' do describe '#exists?' do
timeout = 1 it 'returns true for an existing lease' do
lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout) lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
lease.try_obtain # start the lease lease.try_obtain
sleep(2 * timeout) # lease should have expired now
expect(lease.try_obtain).to eq(true)
end
def unique_key expect(lease.exists?).to eq(true)
SecureRandom.hex(10) end
it 'returns false for a lease that does not exist' do
lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
expect(lease.exists?).to eq(false)
end
end end
end end

View file

@ -50,6 +50,12 @@ RSpec.configure do |config|
example.run example.run
Rails.cache = caching_store Rails.cache = caching_store
end end
config.around(:each, :redis) do |example|
Gitlab::Redis.with(&:flushall)
example.run
Gitlab::Redis.with(&:flushall)
end
end end
FactoryGirl::SyntaxRunner.class_eval do FactoryGirl::SyntaxRunner.class_eval do

View file

@ -5,6 +5,26 @@ describe ProjectCacheWorker do
subject { described_class.new } subject { described_class.new }
describe '.perform_async' do
it 'schedules the job when no lease exists' do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:exists?).
and_return(false)
expect_any_instance_of(described_class).to receive(:perform)
described_class.perform_async(project.id)
end
it 'does not schedule the job when a lease exists' do
allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:exists?).
and_return(true)
expect_any_instance_of(described_class).not_to receive(:perform)
described_class.perform_async(project.id)
end
end
describe '#perform' do describe '#perform' do
context 'when an exclusive lease can be obtained' do context 'when an exclusive lease can be obtained' do
before do before do