Commit baceca19 authored by Alexandru Croitor's avatar Alexandru Croitor

Cleanup development feature flags for locking namespaces fix

Cleanup development feature flags used to introduce before_commit
callback and FOR NO KEY UPDATE lock to fix locking issues on
namespaces table when a creating multiple projects in a concurrent
manner.
parent 49aa0200
...@@ -40,12 +40,7 @@ class Namespace ...@@ -40,12 +40,7 @@ class Namespace
SQL SQL
Namespace.transaction do Namespace.transaction do
if Feature.enabled?(:for_no_key_update_lock, default_enabled: :yaml)
@root.lock!("FOR NO KEY UPDATE") @root.lock!("FOR NO KEY UPDATE")
else
@root.lock!
end
Namespace.connection.exec_query(sql) Namespace.connection.exec_query(sql)
end end
rescue ActiveRecord::Deadlocked rescue ActiveRecord::Deadlocked
......
...@@ -44,22 +44,15 @@ module Namespaces ...@@ -44,22 +44,15 @@ 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_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 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 # 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? } before_commit :sync_traversal_ids, on: [:create], if: -> { sync_traversal_ids? }
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: for_no_key_update_lock
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81239
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353619
milestone: '14.9'
type: development
group: group::workspaces
default_enabled: false
---
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'
...@@ -390,26 +390,8 @@ RSpec.describe Group do ...@@ -390,26 +390,8 @@ RSpec.describe Group do
let!(:old_parent) { create(:group, parent: root) } let!(:old_parent) { create(:group, parent: root) }
let!(:new_parent) { create(:group, parent: root) } let!(:new_parent) { create(:group, parent: root) }
context 'with FOR UPDATE lock' do
before do
stub_feature_flags(for_no_key_update_lock: false)
subject
reload_models(old_parent, new_parent, group)
end
it 'updates traversal_ids' do
expect(group.traversal_ids).to eq [root.id, new_parent.id, group.id]
end
it_behaves_like 'hierarchy with traversal_ids'
it_behaves_like 'locked row', 'FOR UPDATE' do
let(:row) { root }
end
end
context 'with FOR NO KEY UPDATE lock' do context 'with FOR NO KEY UPDATE lock' do
before do before do
stub_feature_flags(for_no_key_update_lock: true)
subject subject
reload_models(old_parent, new_parent, group) reload_models(old_parent, new_parent, group)
end end
...@@ -419,7 +401,7 @@ RSpec.describe Group do ...@@ -419,7 +401,7 @@ RSpec.describe Group do
end end
it_behaves_like 'hierarchy with traversal_ids' it_behaves_like 'hierarchy with traversal_ids'
it_behaves_like 'locked row', 'FOR NO KEY UPDATE' do it_behaves_like 'locked row' do
let(:row) { root } let(:row) { root }
end end
end end
......
...@@ -68,24 +68,11 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model do ...@@ -68,24 +68,11 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model do
end end
end end
it_behaves_like 'locked row', 'FOR UPDATE' do it_behaves_like 'locked row' do
let(:recorded_queries) { ActiveRecord::QueryRecorder.new } let(:recorded_queries) { ActiveRecord::QueryRecorder.new }
let(:row) { root } let(:row) { root }
before do before do
stub_feature_flags(for_no_key_update_lock: false)
recorded_queries.record { subject }
end
end
it_behaves_like 'locked row', 'FOR NO KEY UPDATE' do
let(:recorded_queries) { ActiveRecord::QueryRecorder.new }
let(:row) { root }
before do
stub_feature_flags(for_no_key_update_lock: true)
recorded_queries.record { subject } recorded_queries.record { subject }
end end
end end
......
...@@ -436,19 +436,9 @@ RSpec.describe Namespace do ...@@ -436,19 +436,9 @@ 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' it_behaves_like 'default traversal_ids'
end 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
describe "after_commit :expire_child_caches" do describe "after_commit :expire_child_caches" do
let(:namespace) { create(:group) } let(:namespace) { create(:group) }
......
...@@ -262,18 +262,8 @@ RSpec.describe Project, factory_default: :keep do ...@@ -262,18 +262,8 @@ RSpec.describe Project, factory_default: :keep do
end end
end end
context 'sync-ing traversal_ids in before_commit callback' do
it_behaves_like 'creates project namespace' it_behaves_like 'creates project namespace'
end end
context 'sync-ing traversal_ids in after_create callback' do
before do
stub_feature_flags(sync_traversal_ids_before_commit: false)
end
it_behaves_like 'creates project namespace'
end
end
end end
context 'updating a project' do context 'updating a project' do
......
...@@ -85,14 +85,6 @@ RSpec.describe Groups::CreateService, '#execute' do ...@@ -85,14 +85,6 @@ RSpec.describe Groups::CreateService, '#execute' do
context 'with before_commit callback' do context 'with before_commit callback' do
it_behaves_like 'has sync-ed traversal_ids' it_behaves_like 'has sync-ed traversal_ids'
end 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
...@@ -119,18 +111,8 @@ RSpec.describe Groups::CreateService, '#execute' do ...@@ -119,18 +111,8 @@ RSpec.describe Groups::CreateService, '#execute' 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' it_behaves_like 'has sync-ed traversal_ids'
end end
end
context 'as guest' do context 'as guest' do
it 'does not save group and returns an error' do it 'does not save group and returns an error' do
......
...@@ -225,19 +225,9 @@ RSpec.describe Projects::CreateService, '#execute' do ...@@ -225,19 +225,9 @@ RSpec.describe Projects::CreateService, '#execute' do
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' it_behaves_like 'has sync-ed traversal_ids'
end 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
context 'group sharing', :sidekiq_inline do context 'group sharing', :sidekiq_inline do
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:shared_group) { create(:group) } let_it_be(:shared_group) { create(:group) }
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
# Ensure a transaction also occurred. # Ensure a transaction also occurred.
# Be careful! This form of spec is not foolproof, but better than nothing. # Be careful! This form of spec is not foolproof, but better than nothing.
RSpec.shared_examples 'locked row' do |lock_type| RSpec.shared_examples 'locked row' do
it "has locked row" do it "has locked row" do
table_name = row.class.table_name table_name = row.class.table_name
ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+#{lock_type}/m ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+FOR NO KEY UPDATE/m
expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT' expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
expect(recorded_queries.log).to include a_string_matching ids_regex expect(recorded_queries.log).to include a_string_matching ids_regex
......
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