From f3da423a2107836fadbff7957679652f289c92ad Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 15 Aug 2017 08:06:37 +0000 Subject: Merge branch '34533-speed-up-group-project-authorizations' into 'master' Speed up Group#user_ids_for_project_authorizations Closes #36182 See merge request !13508 --- app/mailers/emails/members.rb | 4 +-- app/models/group.rb | 30 +++++++++++++++++----- app/models/member.rb | 15 +++++++++-- app/models/namespace.rb | 14 ++++++++++ ...34533-speed-up-group-project-authorizations.yml | 5 ++++ spec/models/namespace_spec.rb | 30 ++++++++++++++++++++++ 6 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/34533-speed-up-group-project-authorizations.yml diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 7b617b359ea..d76c61c369f 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -11,11 +11,11 @@ module Emails @member_source_type = member_source_type @member_id = member_id - admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email) + admins = member_source.members.owners_and_masters.pluck(:notification_email) # A project in a group can have no explicit owners/masters, in that case # we fallbacks to the group's owners/masters. if admins.empty? && member_source.respond_to?(:group) && member_source.group - admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email) + admins = member_source.group.members.owners_and_masters.pluck(:notification_email) end mail(to: admins, diff --git a/app/models/group.rb b/app/models/group.rb index bd5735ed82e..2816a68257c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -212,21 +212,39 @@ class Group < Namespace end def user_ids_for_project_authorizations - users_with_parents.pluck(:id) + members_with_parents.pluck(:user_id) end def members_with_parents - GroupMember.active.where(source_id: ancestors.pluck(:id).push(id)).where.not(user_id: nil) + # Avoids an unnecessary SELECT when the group has no parents + source_ids = + if parent_id + self_and_ancestors.reorder(nil).select(:id) + else + id + end + + GroupMember + .active_without_invites + .where(source_id: source_ids) + end + + def members_with_descendants + GroupMember + .active_without_invites + .where(source_id: self_and_descendants.reorder(nil).select(:id)) end def users_with_parents - User.where(id: members_with_parents.select(:user_id)) + User + .where(id: members_with_parents.select(:user_id)) + .reorder(nil) end def users_with_descendants - members_with_descendants = GroupMember.non_request.where(source_id: descendants.pluck(:id).push(id)) - - User.where(id: members_with_descendants.select(:user_id)) + User + .where(id: members_with_descendants.select(:user_id)) + .reorder(nil) end def max_member_access_for_user(user) diff --git a/app/models/member.rb b/app/models/member.rb index dc9247bc9a0..17e343c84d8 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -41,9 +41,20 @@ class Member < ActiveRecord::Base is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil)) user_is_active = User.arel_table[:state].eq(:active) - includes(:user).references(:users) - .where(is_external_invite.or(user_is_active)) + user_ok = Arel::Nodes::Grouping.new(is_external_invite).or(user_is_active) + + left_join_users + .where(user_ok) + .where(requested_at: nil) + .reorder(nil) + end + + # Like active, but without invites. For when a User is required. + scope :active_without_invites, -> do + left_join_users + .where(users: { state: 'active' }) .where(requested_at: nil) + .reorder(nil) end scope :invite, -> { where.not(invite_token: nil) } diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 6073fb94a3f..e7bc1d1b080 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -156,6 +156,14 @@ class Namespace < ActiveRecord::Base .base_and_ancestors end + def self_and_ancestors + return self.class.where(id: id) unless parent_id + + Gitlab::GroupHierarchy + .new(self.class.where(id: id)) + .base_and_ancestors + end + # Returns all the descendants of the current namespace. def descendants Gitlab::GroupHierarchy @@ -163,6 +171,12 @@ class Namespace < ActiveRecord::Base .base_and_descendants end + def self_and_descendants + Gitlab::GroupHierarchy + .new(self.class.where(id: id)) + .base_and_descendants + end + def user_ids_for_project_authorizations [owner_id] end diff --git a/changelogs/unreleased/34533-speed-up-group-project-authorizations.yml b/changelogs/unreleased/34533-speed-up-group-project-authorizations.yml new file mode 100644 index 00000000000..ddaaf4a2507 --- /dev/null +++ b/changelogs/unreleased/34533-speed-up-group-project-authorizations.yml @@ -0,0 +1,5 @@ +--- +title: Fix timeouts when creating projects in groups with many members +merge_request: 13508 +author: +type: fixed diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 1a00c50690c..69286eff984 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -315,6 +315,20 @@ describe Namespace do end end + describe '#self_and_ancestors', :nested_groups do + let(:group) { create(:group) } + let(:nested_group) { create(:group, parent: group) } + let(:deep_nested_group) { create(:group, parent: nested_group) } + let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } + + it 'returns the correct ancestors' do + expect(very_deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group) + expect(deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group) + expect(nested_group.self_and_ancestors).to contain_exactly(group, nested_group) + expect(group.self_and_ancestors).to contain_exactly(group) + end + end + describe '#descendants', :nested_groups do let!(:group) { create(:group, path: 'git_lab') } let!(:nested_group) { create(:group, parent: group) } @@ -331,6 +345,22 @@ describe Namespace do end end + describe '#self_and_descendants', :nested_groups do + let!(:group) { create(:group, path: 'git_lab') } + let!(:nested_group) { create(:group, parent: group) } + let!(:deep_nested_group) { create(:group, parent: nested_group) } + let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } + let!(:another_group) { create(:group, path: 'gitllab') } + let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) } + + it 'returns the correct descendants' do + expect(very_deep_nested_group.self_and_descendants).to contain_exactly(very_deep_nested_group) + expect(deep_nested_group.self_and_descendants).to contain_exactly(deep_nested_group, very_deep_nested_group) + expect(nested_group.self_and_descendants).to contain_exactly(nested_group, deep_nested_group, very_deep_nested_group) + expect(group.self_and_descendants).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group) + end + end + describe '#users_with_descendants', :nested_groups do let(:user_a) { create(:user) } let(:user_b) { create(:user) } -- cgit v1.2.1