Commit 9cffd438 authored by Tiger Watson's avatar Tiger Watson

Merge branch 'mr-compliance-finder-query' into 'master'

Optimize queries in MergeRequestsComplianceFinder

See merge request gitlab-org/gitlab!34482
parents d800c3d3 4489d176
...@@ -16,7 +16,7 @@ class Groups::Security::ComplianceDashboardsController < Groups::ApplicationCont ...@@ -16,7 +16,7 @@ class Groups::Security::ComplianceDashboardsController < Groups::ApplicationCont
def paginated_merge_requests def paginated_merge_requests
@paginated_merge_requests ||= begin @paginated_merge_requests ||= begin
merge_requests = MergeRequestsComplianceFinder.new(current_user, { group_id: @group.id }).execute merge_requests = MergeRequestsComplianceFinder.new(current_user, { group_id: @group.id }).execute
Kaminari.paginate_array(merge_requests).page(params[:page]) merge_requests.page(params[:page])
end end
end end
......
...@@ -11,6 +11,9 @@ ...@@ -11,6 +11,9 @@
class MergeRequestsComplianceFinder < MergeRequestsFinder class MergeRequestsComplianceFinder < MergeRequestsFinder
def execute def execute
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
# This lateral query is used to get the single, latest
# "MR merged" event PER project.
lateral = Event lateral = Event
.select(:created_at, :target_id) .select(:created_at, :target_id)
.where('projects.id = project_id') .where('projects.id = project_id')
...@@ -19,17 +22,36 @@ class MergeRequestsComplianceFinder < MergeRequestsFinder ...@@ -19,17 +22,36 @@ class MergeRequestsComplianceFinder < MergeRequestsFinder
.limit(1) .limit(1)
.to_sql .to_sql
sql = params.find_group_projects.arel.as('projects').to_sql query = projects_in_group
records = Project .joins("JOIN LATERAL (#{lateral}) events ON true")
.select('projects.id, events.target_id as merge_request_id')
.from([Arel.sql("#{sql} JOIN LATERAL (#{lateral}) #{Event.table_name} ON true")])
.order('events.created_at DESC') .order('events.created_at DESC')
select_sorted_mrs(records) .select('events.target_id as target_id') # The `target_id` of the `events` are the MR ids.
ordered_events_cte = Gitlab::SQL::CTE.new(:ordered_events_cte, query)
MergeRequest
.with(ordered_events_cte.to_arel)
.joins(inner_join_ordered_events_table(ordered_events_cte))
.order(Arel.sql('array_position(ARRAY(SELECT target_id FROM ordered_events_cte), merge_requests.id)'))
.preload(preloads)
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
private private
def inner_join_ordered_events_table(ordered_events_cte)
merge_requests_table = MergeRequest.arel_table
merge_requests_table
.join(ordered_events_cte.table, Arel::Nodes::InnerJoin)
.on(merge_requests_table[:id].eq(ordered_events_cte.table[:target_id]))
.join_sources
end
def projects_in_group
params.find_group_projects
end
def params def params
finder_options = { finder_options = {
include_subgroups: true, include_subgroups: true,
...@@ -38,18 +60,13 @@ class MergeRequestsComplianceFinder < MergeRequestsFinder ...@@ -38,18 +60,13 @@ class MergeRequestsComplianceFinder < MergeRequestsFinder
super.merge(finder_options) super.merge(finder_options)
end end
def select_sorted_mrs(records)
hash = {}
records.each { |row| hash[row['merge_request_id']] = nil }
mrs = MergeRequest.where(id: hash.keys).preload(preloads) # rubocop: disable CodeReuse/ActiveRecord
mrs.each { |mr| hash[mr.id] = mr }
hash.compact!
hash.values # sorted MRs
end
def preloads def preloads
[:approved_by_users, :metrics, source_project: :route, target_project: :namespace] [
:approved_by_users,
:metrics,
source_project: :route,
target_project: :namespace,
head_pipeline: [project: :project_feature]
]
end end
end end
---
title: Optimize queries in MergeRequestsComplianceFinder
merge_request: 34482
author:
type: performance
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