Commit 3353e37f authored by Yorick Peterse's avatar Yorick Peterse

Merge branch '665-avoid-locking-when-updating-mirrors' into 'master'

Distribute RepositoryUpdateMirror jobs with exclusive lease

See merge request !462
parents b594d95d bc582d61
...@@ -5,6 +5,7 @@ v 8.9.0 (unreleased) ...@@ -5,6 +5,7 @@ v 8.9.0 (unreleased)
- Allow LDAP to mark users as external based on their group membership. !432 - Allow LDAP to mark users as external based on their group membership. !432
- Instrument instance methods of Gitlab::InsecureKeyFingerprint class - Instrument instance methods of Gitlab::InsecureKeyFingerprint class
- Add API endpoint for Merge Request Approvals !449 - Add API endpoint for Merge Request Approvals !449
- Distribute RepositoryUpdateMirror jobs in time and add exclusive lease on them by project_id
v 8.8.4 v 8.8.4
- Remove license overusage message - Remove license overusage message
......
...@@ -544,7 +544,7 @@ class Project < ActiveRecord::Base ...@@ -544,7 +544,7 @@ class Project < ActiveRecord::Base
mirror_updated? && self.mirror_last_successful_update_at mirror_updated? && self.mirror_last_successful_update_at
end end
def update_mirror def update_mirror(delay: 0)
return unless mirror? && repository_exists? return unless mirror? && repository_exists?
return if import_in_progress? return if import_in_progress?
...@@ -555,7 +555,7 @@ class Project < ActiveRecord::Base ...@@ -555,7 +555,7 @@ class Project < ActiveRecord::Base
import_start import_start
end end
RepositoryUpdateMirrorWorker.perform_async(self.id) RepositoryUpdateMirrorWorker.perform_in(delay, self.id)
end end
def mark_import_as_failed(error_message) def mark_import_as_failed(error_message)
......
...@@ -2,11 +2,15 @@ class RepositoryUpdateMirrorWorker ...@@ -2,11 +2,15 @@ class RepositoryUpdateMirrorWorker
include Sidekiq::Worker include Sidekiq::Worker
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
LEASE_TIMEOUT = 300
sidekiq_options queue: :gitlab_shell sidekiq_options queue: :gitlab_shell
attr_accessor :project, :repository, :current_user attr_accessor :project, :repository, :current_user
def perform(project_id) def perform(project_id)
return unless try_obtain_lease(project_id)
@project = Project.find(project_id) @project = Project.find(project_id)
@current_user = @project.mirror_user || @project.creator @current_user = @project.mirror_user || @project.creator
...@@ -18,4 +22,12 @@ class RepositoryUpdateMirrorWorker ...@@ -18,4 +22,12 @@ class RepositoryUpdateMirrorWorker
project.import_finish project.import_finish
end end
private
def try_obtain_lease(project_id)
# Using 5 minutes timeout based on the 95th percent of timings (currently max of 25 seconds)
lease = ::Gitlab::ExclusiveLease.new("repository_update_mirror:#{project_id}", timeout: LEASE_TIMEOUT)
lease.try_obtain
end
end end
class UpdateAllMirrorsWorker class UpdateAllMirrorsWorker
include Sidekiq::Worker include Sidekiq::Worker
LEASE_TIMEOUT = 3600
def perform def perform
return unless try_obtain_lease
fail_stuck_mirrors! fail_stuck_mirrors!
Project.mirror.each(&:update_mirror) Project.mirror.find_each(batch_size: 200) do |project|
project.update_mirror(delay: rand(30.minutes))
end
end end
def fail_stuck_mirrors! def fail_stuck_mirrors!
...@@ -16,4 +22,12 @@ class UpdateAllMirrorsWorker ...@@ -16,4 +22,12 @@ class UpdateAllMirrorsWorker
project.mark_import_as_failed('The mirror update took too long to complete.') project.mark_import_as_failed('The mirror update took too long to complete.')
end end
end end
private
def try_obtain_lease
# Using 30 minutes timeout based on the 95th percent of timings (currently max of 10 minutes)
lease = ::Gitlab::ExclusiveLease.new("update_all_mirrors", timeout: LEASE_TIMEOUT)
lease.try_obtain
end
end end
require 'rails_helper'
describe RepositoryUpdateMirrorWorker do
describe '#perform' do
it "just returns if cannot obtain a lease" do
worker = described_class.new
project_id = 15
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(false)
expect(Projects::UpdateMirrorService).not_to receive(:execute)
worker.perform(project_id)
end
context "when obtain the lease" do
before do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
end
it "sets import as finished when update mirror service executes successfully" do
project = create(:empty_project, :mirror)
expect_any_instance_of(Projects::UpdateMirrorService).to receive(:execute).and_return(status: :success)
expect do
described_class.new.perform(project.id)
end.to change { project.reload.import_status }.to("finished")
end
it "sets import as failed when update mirror service executes with errors" do
project = create(:empty_project, :mirror)
expect_any_instance_of(Projects::UpdateMirrorService).to receive(:execute).and_return(status: :error, message: 'fail!')
expect do
described_class.new.perform(project.id)
end.to change { project.reload.import_status }.to("failed")
end
end
end
end
require 'rails_helper' require 'rails_helper'
describe UpdateAllMirrorsWorker do describe UpdateAllMirrorsWorker do
before do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(true)
end
describe '#perform' do describe '#perform' do
it 'fails stuck mirrors' do it 'fails stuck mirrors' do
worker = described_class.new worker = described_class.new
...@@ -11,12 +16,27 @@ describe UpdateAllMirrorsWorker do ...@@ -11,12 +16,27 @@ describe UpdateAllMirrorsWorker do
end end
it 'updates all mirrored Projects' do it 'updates all mirrored Projects' do
worker = described_class.new
create(:empty_project, :mirror) create(:empty_project, :mirror)
create(:empty_project) create(:empty_project)
expect_any_instance_of(Project).to receive(:update_mirror).once expect(worker).to receive(:rand).with(30.minutes).and_return(10)
expect_any_instance_of(Project).to receive(:update_mirror).with(delay: 10).once
worker.perform
end
described_class.new.perform it 'does not execute if cannot get the lease' do
allow_any_instance_of(Gitlab::ExclusiveLease)
.to receive(:try_obtain).and_return(false)
worker = described_class.new
create(:empty_project, :mirror)
expect(worker).not_to receive(:fail_stuck_mirrors!)
worker.perform
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment