diff options
| author | Yorick Peterse <yorickpeterse@gmail.com> | 2015-11-16 14:28:02 +0100 |
|---|---|---|
| committer | Yorick Peterse <yorickpeterse@gmail.com> | 2015-11-18 13:05:45 +0100 |
| commit | 5fcd9986b86812786c90a0b8d3461db79ce71051 (patch) | |
| tree | 959c2a1187f5e8d0fabdf2c6e8d1a9c441e4e4d4 /app/models/user.rb | |
| parent | bfd9855a2b2a09e8f5bff89e84891d3ad598fe0d (diff) | |
| download | gitlab-ce-5fcd9986b86812786c90a0b8d3461db79ce71051.tar.gz | |
Refactor getting user groups/projects/contributions
This new setup no longer loads any IDs into memory using "pluck",
instead using SQL UNIONs to merge the various datasets together. This
results in greatly improved query performance as well as a reduction of
memory usage.
The old setup was in particular problematic when requesting the
authorized projects _including_ public/internal projects as this would
result in roughly 65000 project IDs being loaded into memory. These IDs
would in turn be passed to other queries.
Diffstat (limited to 'app/models/user.rb')
| -rw-r--r-- | app/models/user.rb | 64 |
1 files changed, 48 insertions, 16 deletions
diff --git a/app/models/user.rb b/app/models/user.rb index a2258967e27..d523b3f0491 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -389,21 +389,40 @@ class User < ActiveRecord::Base end end - # Groups user has access to - def authorized_groups - @authorized_groups ||= - begin - union = Gitlab::SQL::Union. - new([groups.select(:id), authorized_projects.select(:namespace_id)]) + # Returns the groups a user has access to, optionally including any public + # groups. + # + # public_internal - When set to "true" all public groups and groups of public + # projects are also included. + # + # Returns an ActiveRecord::Relation + def authorized_groups(public_internal = false) + union = Gitlab::SQL::Union. + new([groups.select(:id), authorized_projects(public_internal). + select(:namespace_id)]) - Group.where("namespaces.id IN (#{union.to_sql})") - end + sql = "namespaces.id IN (#{union.to_sql})" + + if public_internal + sql << ' OR public IS TRUE' + end + + Group.where(sql) end - # Projects user has access to - def authorized_projects - @authorized_projects ||= - Project.where("projects.id IN (#{projects_union.to_sql})") + # Returns the groups a user is authorized to access. + # + # public_internal - When set to "true" all public/internal projects will also + # be included. + def authorized_projects(public_internal = false) + base = "projects.id IN (#{projects_union.to_sql})" + + if public_internal + Project.where("#{base} OR projects.visibility_level IN (?)", + Project.public_and_internal_levels) + else + Project.where(base) + end end def owned_projects @@ -726,12 +745,25 @@ class User < ActiveRecord::Base Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil) end - def contributed_projects_ids - Event.contributions.where(author_id: self). + # Returns the projects a user contributed to in the last year. + # + # This method relies on a subquery as this performs significantly better + # compared to a JOIN when coupled with, for example, + # `Project.visible_to_user`. That is, consider the following code: + # + # some_user.contributed_projects.visible_to_user(other_user) + # + # If this method were to use a JOIN the resulting query would take roughly 200 + # ms on a database with a similar size to gitlab.com's database. On the other + # hand, using a subquery means we can get the exact same data in about 40 ms. + def contributed_projects + events = Event.select(:project_id). + contributions.where(author_id: self). where("created_at > ?", Time.now - 1.year). - reorder(project_id: :desc). uniq. - pluck(:project_id) + reorder(nil) + + Project.where(id: events) end def restricted_signup_domains |
