Skip to content
Snippets Groups Projects
Unverified Commit 04f5d77f authored by Yorick Peterse's avatar Yorick Peterse
Browse files

Smarter refreshing of authorized projects

Prior to this commit the refreshing of authorized projects was done in
two steps:

1. Remove existing authorizations
2. Insert a new list of all authorizations

This can lead to a high amount of dead tuples as every time all rows are
being replaced. For example, if a user with 100 authorizations is given
access to a new project this would lead to:

* 100 rows being removed
* 101 new rows being inserted

This commit changes the way this system works so it only removes/inserts
what is necessary. Using the above example this would lead to only 1 new
row being inserted, with the initial 100 being left untouched.

Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/25257
parent 18201ace
No related branches found
No related tags found
No related merge requests found
Pipeline #
Loading
Loading
@@ -445,23 +445,7 @@ class User < ActiveRecord::Base
end
 
def refresh_authorized_projects
transaction do
project_authorizations.delete_all
# project_authorizations_union can return multiple records for the same
# project/user with different access_level so we take row with the maximum
# access_level
project_authorizations.connection.execute <<-SQL
INSERT INTO project_authorizations (user_id, project_id, access_level)
SELECT user_id, project_id, MAX(access_level) AS access_level
FROM (#{project_authorizations_union.to_sql}) sub
GROUP BY user_id, project_id
SQL
unless authorized_projects_populated
update_column(:authorized_projects_populated, true)
end
end
Users::RefreshAuthorizedProjectsService.new(self).execute
end
 
def authorized_projects(min_access_level = nil)
Loading
Loading
@@ -907,18 +891,6 @@ class User < ActiveRecord::Base
 
private
 
# Returns a union query of projects that the user is authorized to access
def project_authorizations_union
relations = [
personal_projects.select("#{id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"),
groups_projects.select_for_project_authorization,
projects.select_for_project_authorization,
groups.joins(:shared_projects).select_for_project_authorization
]
Gitlab::SQL::Union.new(relations)
end
def ci_projects_union
scope = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] }
groups = groups_projects.where(members: scope)
Loading
Loading
module Users
class RefreshAuthorizedProjectsService
attr_reader :user
# user - The User for which to refresh the authorized projects.
def initialize(user)
@user = user
end
def execute
current = current_authorizations_per_project
fresh = fresh_access_levels_per_project
to_add = []
to_remove = []
current.each do |project_id, row|
if !fresh[project_id] || fresh[project_id] != row.access_level
to_remove << row.id
end
end
fresh.each do |project_id, access_level|
if !current[project_id] || current[project_id].access_level != access_level
to_add << [user.id, project_id, access_level]
end
end
return if to_remove.empty? && to_add.empty?
User.transaction do
unless to_remove.empty?
user.project_authorizations.where(id: to_remove).delete_all
end
unless to_add.empty?
ProjectAuthorization.connection.execute <<-EOF.strip_heredoc
INSERT INTO project_authorizations (user_id, project_id, access_level)
VALUES #{to_add.map { |tuple| "(#{tuple.join(', ')})" }.join(', ')}
EOF
end
unless user.authorized_projects_populated
user.update_column(:authorized_projects_populated, true)
end
end
end
def fresh_access_levels_per_project
fresh_authorizations.each_with_object({}) do |row, hash|
hash[row.project_id] = row.access_level
end
end
def current_authorizations_per_project
current_authorizations.each_with_object({}) do |row, hash|
hash[row.project_id] = row
end
end
def current_authorizations
user.project_authorizations.select(:id, :project_id, :access_level)
end
def fresh_authorizations
ProjectAuthorization.
from("(#{project_authorizations_union.to_sql}) #{ProjectAuthorization.table_name}").
select(:project_id, :access_level)
end
# Returns a union query of projects that the user is authorized to access
def project_authorizations_union
relations = [
user.personal_projects.select("#{user.id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"),
user.groups_projects.select_for_project_authorization,
user.projects.select_for_project_authorization,
user.groups.joins(:shared_projects).select_for_project_authorization
]
Gitlab::SQL::Union.new(relations)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment