Commit f57ea28b authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'move-traversal-id-update-to-before-commit' into 'master'

Move traversal_ids update to the end of transaction

See merge request gitlab-org/gitlab!79964
parents 6b65399b e43bd03d
...@@ -43,14 +43,23 @@ module Namespaces ...@@ -43,14 +43,23 @@ module Namespaces
included do included do
before_update :lock_both_roots, if: -> { sync_traversal_ids? && parent_id_changed? } before_update :lock_both_roots, if: -> { sync_traversal_ids? && parent_id_changed? }
after_create :sync_traversal_ids, if: -> { sync_traversal_ids? }
after_update :sync_traversal_ids, if: -> { sync_traversal_ids? && saved_change_to_parent_id? } after_update :sync_traversal_ids, if: -> { sync_traversal_ids? && saved_change_to_parent_id? }
# sync traversal_ids on namespace create, which can happen quite early within a transaction, thus keeping the lock on root namespace record
# for a relatively long time, e.g. creating the project namespace when a project is being created.
after_create :sync_traversal_ids, if: -> { sync_traversal_ids? && !sync_traversal_ids_before_commit? }
# This uses rails internal before_commit API to sync traversal_ids on namespace create, right before transaction is committed.
# This helps reduce the time during which the root namespace record is locked to ensure updated traversal_ids are valid
before_commit :sync_traversal_ids, on: [:create], if: -> { sync_traversal_ids? && sync_traversal_ids_before_commit? }
end end
def sync_traversal_ids? def sync_traversal_ids?
Feature.enabled?(:sync_traversal_ids, root_ancestor, default_enabled: :yaml) Feature.enabled?(:sync_traversal_ids, root_ancestor, default_enabled: :yaml)
end end
def sync_traversal_ids_before_commit?
Feature.enabled?(:sync_traversal_ids_before_commit, root_ancestor, default_enabled: :yaml)
end
def use_traversal_ids? def use_traversal_ids?
return false unless Feature.enabled?(:use_traversal_ids, default_enabled: :yaml) return false unless Feature.enabled?(:use_traversal_ids, default_enabled: :yaml)
......
---
name: sync_traversal_ids_before_commit
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79964
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352499
group: group::workspace
type: development
default_enabled: false
milestone: '14.8'
...@@ -425,7 +425,7 @@ RSpec.describe Namespace do ...@@ -425,7 +425,7 @@ RSpec.describe Namespace do
end end
context 'traversal_ids on create' do context 'traversal_ids on create' do
context 'default traversal_ids' do shared_examples 'default traversal_ids' do
let(:namespace) { build(:namespace) } let(:namespace) { build(:namespace) }
before do before do
...@@ -435,6 +435,18 @@ RSpec.describe Namespace do ...@@ -435,6 +435,18 @@ RSpec.describe Namespace do
it { expect(namespace.traversal_ids).to eq [namespace.id] } it { expect(namespace.traversal_ids).to eq [namespace.id] }
end end
context 'with before_commit callback' do
it_behaves_like 'default traversal_ids'
end
context 'with after_create callback' do
before do
stub_feature_flags(sync_traversal_ids_before_commit: false)
end
it_behaves_like 'default traversal_ids'
end
end end
describe "after_commit :expire_child_caches" do describe "after_commit :expire_child_caches" do
......
...@@ -236,27 +236,42 @@ RSpec.describe Project, factory_default: :keep do ...@@ -236,27 +236,42 @@ RSpec.describe Project, factory_default: :keep do
end end
context 'with project namespaces' do context 'with project namespaces' do
it 'automatically creates a project namespace' do shared_examples 'creates project namespace' do
project = build(:project, path: 'hopefully-valid-path1') it 'automatically creates a project namespace' do
project.save! project = build(:project, path: 'hopefully-valid-path1')
project.save!
expect(project).to be_persisted expect(project).to be_persisted
expect(project.project_namespace).to be_persisted expect(project.project_namespace).to be_persisted
expect(project.project_namespace).to be_in_sync_with_project(project) expect(project.project_namespace).to be_in_sync_with_project(project)
end expect(project.reload.project_namespace.traversal_ids).to eq([project.namespace.traversal_ids, project.project_namespace.id].flatten.compact)
end
context 'with FF disabled' do context 'with FF disabled' do
before do before do
stub_feature_flags(create_project_namespace_on_project_create: false) stub_feature_flags(create_project_namespace_on_project_create: false)
end
it 'does not create a project namespace' do
project = build(:project, path: 'hopefully-valid-path2')
project.save!
expect(project).to be_persisted
expect(project.project_namespace).to be_nil
end
end end
end
it 'does not create a project namespace' do context 'sync-ing traversal_ids in before_commit callback' do
project = build(:project, path: 'hopefully-valid-path2') it_behaves_like 'creates project namespace'
project.save! end
expect(project).to be_persisted context 'sync-ing traversal_ids in after_create callback' do
expect(project.project_namespace).to be_nil before do
stub_feature_flags(sync_traversal_ids_before_commit: false)
end end
it_behaves_like 'creates project namespace'
end end
end end
end end
......
...@@ -8,6 +8,10 @@ RSpec.describe Groups::CreateService, '#execute' do ...@@ -8,6 +8,10 @@ RSpec.describe Groups::CreateService, '#execute' do
subject { service.execute } subject { service.execute }
shared_examples 'has sync-ed traversal_ids' do
specify { expect(subject.reload.traversal_ids).to eq([subject.parent&.traversal_ids, subject.id].flatten.compact) }
end
describe 'visibility level restrictions' do describe 'visibility level restrictions' do
let!(:service) { described_class.new(user, group_params) } let!(:service) { described_class.new(user, group_params) }
...@@ -77,6 +81,18 @@ RSpec.describe Groups::CreateService, '#execute' do ...@@ -77,6 +81,18 @@ RSpec.describe Groups::CreateService, '#execute' do
it 'adds an onboarding progress record' do it 'adds an onboarding progress record' do
expect { subject }.to change(OnboardingProgress, :count).from(0).to(1) expect { subject }.to change(OnboardingProgress, :count).from(0).to(1)
end end
context 'with before_commit callback' do
it_behaves_like 'has sync-ed traversal_ids'
end
context 'with after_create callback' do
before do
stub_feature_flags(sync_traversal_ids_before_commit: false)
end
it_behaves_like 'has sync-ed traversal_ids'
end
end end
context 'when user can not create a group' do context 'when user can not create a group' do
...@@ -102,6 +118,18 @@ RSpec.describe Groups::CreateService, '#execute' do ...@@ -102,6 +118,18 @@ RSpec.describe Groups::CreateService, '#execute' do
it 'does not add an onboarding progress record' do it 'does not add an onboarding progress record' do
expect { subject }.not_to change(OnboardingProgress, :count).from(0) expect { subject }.not_to change(OnboardingProgress, :count).from(0)
end end
context 'with before_commit callback' do
it_behaves_like 'has sync-ed traversal_ids'
end
context 'with after_create callback' do
before do
stub_feature_flags(sync_traversal_ids_before_commit: false)
end
it_behaves_like 'has sync-ed traversal_ids'
end
end end
context 'as guest' do context 'as guest' do
......
...@@ -190,9 +190,13 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -190,9 +190,13 @@ RSpec.describe Projects::CreateService, '#execute' do
user.refresh_authorized_projects # Ensure cache is warm user.refresh_authorized_projects # Ensure cache is warm
end end
it do subject(:project) { create_project(user, opts.merge!(namespace_id: group.id)) }
project = create_project(user, opts.merge!(namespace_id: group.id))
shared_examples 'has sync-ed traversal_ids' do
specify { expect(project.reload.project_namespace.traversal_ids).to eq([project.namespace.traversal_ids, project.project_namespace.id].flatten.compact) }
end
it 'creates the project' do
expect(project).to be_valid expect(project).to be_valid
expect(project.owner).to eq(group) expect(project.owner).to eq(group)
expect(project.namespace).to eq(group) expect(project.namespace).to eq(group)
...@@ -200,6 +204,18 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -200,6 +204,18 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(user.authorized_projects).to include(project) expect(user.authorized_projects).to include(project)
expect(project.project_namespace).to be_in_sync_with_project(project) expect(project.project_namespace).to be_in_sync_with_project(project)
end end
context 'with before_commit callback' do
it_behaves_like 'has sync-ed traversal_ids'
end
context 'with after_create callback' do
before do
stub_feature_flags(sync_traversal_ids_before_commit: false)
end
it_behaves_like 'has sync-ed traversal_ids'
end
end end
context 'group sharing', :sidekiq_inline do context 'group sharing', :sidekiq_inline do
......
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