Commit 8a5e1e3d authored by Adam Hegyi's avatar Adam Hegyi

Merge branch 'use-for-no-key-update-lock' into 'master'

Use the for no key update lock when upadting traversal ids

See merge request gitlab-org/gitlab!81239
parents 3c2505c5 527cf391
...@@ -31,15 +31,21 @@ class Namespace ...@@ -31,15 +31,21 @@ class Namespace
# ActiveRecord. https://github.com/rails/rails/issues/13496 # ActiveRecord. https://github.com/rails/rails/issues/13496
# Ideally it would be: # Ideally it would be:
# `incorrect_traversal_ids.update_all('traversal_ids = cte.traversal_ids')` # `incorrect_traversal_ids.update_all('traversal_ids = cte.traversal_ids')`
sql = """ sql = <<-SQL
UPDATE namespaces UPDATE namespaces
SET traversal_ids = cte.traversal_ids SET traversal_ids = cte.traversal_ids
FROM (#{recursive_traversal_ids}) as cte FROM (#{recursive_traversal_ids}) as cte
WHERE namespaces.id = cte.id WHERE namespaces.id = cte.id
AND namespaces.traversal_ids::bigint[] <> cte.traversal_ids AND namespaces.traversal_ids::bigint[] <> cte.traversal_ids
""" SQL
Namespace.transaction do Namespace.transaction do
if Feature.enabled?(:for_no_key_update_lock, default_enabled: :yaml)
@root.lock!("FOR NO KEY UPDATE")
else
@root.lock! @root.lock!
end
Namespace.connection.exec_query(sql) Namespace.connection.exec_query(sql)
end end
rescue ActiveRecord::Deadlocked rescue ActiveRecord::Deadlocked
......
---
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
...@@ -385,31 +385,56 @@ RSpec.describe Group do ...@@ -385,31 +385,56 @@ RSpec.describe Group do
end end
end end
context 'within the same hierarchy' do
let!(:root) { create(:group).reload }
let!(:old_parent) { create(:group, parent: root) }
let!(:new_parent) { create(:group, parent: root) }
context 'with FOR UPDATE lock' do
before do before do
stub_feature_flags(for_no_key_update_lock: false)
subject subject
reload_models(old_parent, new_parent, group) reload_models(old_parent, new_parent, group)
end end
context 'within the same hierarchy' do it 'updates traversal_ids' do
let!(:root) { create(:group).reload } expect(group.traversal_ids).to eq [root.id, new_parent.id, group.id]
let!(:old_parent) { create(:group, parent: root) } end
let!(:new_parent) { create(:group, parent: root) }
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
before do
stub_feature_flags(for_no_key_update_lock: true)
subject
reload_models(old_parent, new_parent, group)
end
it 'updates traversal_ids' do it 'updates traversal_ids' do
expect(group.traversal_ids).to eq [root.id, new_parent.id, group.id] expect(group.traversal_ids).to eq [root.id, new_parent.id, group.id]
end end
it_behaves_like 'hierarchy with traversal_ids' it_behaves_like 'hierarchy with traversal_ids'
it_behaves_like 'locked row' do it_behaves_like 'locked row', 'FOR NO KEY UPDATE' do
let(:row) { root } let(:row) { root }
end end
end end
end
context 'to another hierarchy' do context 'to another hierarchy' do
let!(:old_parent) { create(:group) } let!(:old_parent) { create(:group) }
let!(:new_parent) { create(:group) } let!(:new_parent) { create(:group) }
let!(:group) { create(:group, parent: old_parent) } let!(:group) { create(:group, parent: old_parent) }
before do
subject
reload_models(old_parent, new_parent, group)
end
it 'updates traversal_ids' do it 'updates traversal_ids' do
expect(group.traversal_ids).to eq [new_parent.id, group.id] expect(group.traversal_ids).to eq [new_parent.id, group.id]
end end
...@@ -435,6 +460,11 @@ RSpec.describe Group do ...@@ -435,6 +460,11 @@ RSpec.describe Group do
let!(:old_parent) { nil } let!(:old_parent) { nil }
let!(:new_parent) { create(:group) } let!(:new_parent) { create(:group) }
before do
subject
reload_models(old_parent, new_parent, group)
end
it 'updates traversal_ids' do it 'updates traversal_ids' do
expect(group.traversal_ids).to eq [new_parent.id, group.id] expect(group.traversal_ids).to eq [new_parent.id, group.id]
end end
...@@ -452,6 +482,11 @@ RSpec.describe Group do ...@@ -452,6 +482,11 @@ RSpec.describe Group do
let!(:old_parent) { create(:group) } let!(:old_parent) { create(:group) }
let!(:new_parent) { nil } let!(:new_parent) { nil }
before do
subject
reload_models(old_parent, new_parent, group)
end
it 'updates traversal_ids' do it 'updates traversal_ids' do
expect(group.traversal_ids).to eq [group.id] expect(group.traversal_ids).to eq [group.id]
end end
......
...@@ -68,11 +68,24 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model do ...@@ -68,11 +68,24 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model do
end end
end end
it_behaves_like 'locked row' do it_behaves_like 'locked row', 'FOR UPDATE' 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
......
...@@ -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 RSpec.shared_examples 'locked row' do |lock_type|
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}.+FOR UPDATE/m ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+#{lock_type}/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