Skip to content
Snippets Groups Projects
Commit f82d82fd authored by Shinya Maeda's avatar Shinya Maeda
Browse files

Optimize environment serializer

This commit optimizes environment serializer.
parent 7d443c22
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -377,11 +377,11 @@ def detailed_status(current_user)
end
 
def other_manual_actions
pipeline.manual_actions.where.not(name: name)
pipeline.manual_actions.reject { |action| action.name == self.name }
end
 
def other_scheduled_actions
pipeline.scheduled_actions.where.not(name: name)
pipeline.scheduled_actions.reject { |action| action.name == self.name }
end
 
def pages_generator?
Loading
Loading
Loading
Loading
@@ -171,7 +171,7 @@ def latest_for_sha(sha)
end
 
def commit
project.commit(sha)
@commit ||= project.commit(sha)
end
 
def commit_title
Loading
Loading
@@ -250,7 +250,7 @@ def stop_action
return unless on_stop.present?
return unless manual_actions
 
@stop_action ||= manual_actions.find_by(name: on_stop)
@stop_action ||= manual_actions.find { |action| action.name == self.on_stop }
end
 
def finished_at
Loading
Loading
Loading
Loading
@@ -24,13 +24,13 @@ class Environment < ApplicationRecord
has_many :self_managed_prometheus_alert_events, inverse_of: :environment
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :environment
 
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment', inverse_of: :environment
has_one :last_deployable, through: :last_deployment, source: 'deployable', source_type: 'CommitStatus'
has_one :last_pipeline, through: :last_deployable, source: 'pipeline'
has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus'
has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline'
has_one :upcoming_deployment, -> { running.order('deployments.id DESC') }, class_name: 'Deployment'
has_one :upcoming_deployment, -> { running.order('deployments.id DESC') }, class_name: 'Deployment', inverse_of: :environment
has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
 
before_validation :nullify_external_url
Loading
Loading
Loading
Loading
@@ -23,7 +23,7 @@ def represent(resource, opts = {})
latest: super(item.latest, opts) }
end
else
super(resource, opts)
super(batch_load(resource), opts)
end
end
 
Loading
Loading
@@ -41,11 +41,59 @@ def itemize(resource)
# immediately.
items = @paginator.paginate(items) if paginated?
 
environments = resource.where(id: items.map(&:last_id)).index_by(&:id)
environments = batch_load(resource.where(id: items.map(&:last_id)))
environments_by_id = environments.index_by(&:id)
 
items.map do |item|
Item.new(item.folder, item.size, environments[item.last_id])
Item.new(item.folder, item.size, environments_by_id[item.last_id])
end
end
def batch_load(resource)
resource = resource.preload(environment_associations)
resource.all.tap do |environments|
environments.each do |environment|
# Batch loading the commits of the deployments
environment.last_deployment&.commit&.try(:lazy_author)
environment.upcoming_deployment&.commit&.try(:lazy_author)
end
end
end
def environment_associations
{
last_deployment: deployment_associations,
upcoming_deployment: deployment_associations,
project: project_associations
}
end
def deployment_associations
{
user: [],
cluster: [],
project: [],
deployable: {
user: [],
metadata: [],
pipeline: {
manual_actions: [],
scheduled_actions: []
},
project: project_associations
}
}
end
def project_associations
{
project_feature: [],
route: [],
namespace: :route
}
end
# rubocop: enable CodeReuse/ActiveRecord
end
EnvironmentSerializer.prepend_if_ee('EE::EnvironmentSerializer')
---
title: Optimize environment serializer to reduce N+1 problems
merge_request: 58748
author:
type: performance
Loading
Loading
@@ -672,7 +672,7 @@ def protected_environment_by_name(environment_name)
key = "protected_environment_by_name:#{id}:#{environment_name}"
 
::Gitlab::SafeRequestStore.fetch(key) do
protected_environments.find_by(name: environment_name)
protected_environments.find { |pe| pe.name == environment_name }
end
end
 
Loading
Loading
# frozen_string_literal: true
module EE
module EnvironmentSerializer
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
override :environment_associations
def environment_associations
super.deep_merge(latest_opened_most_severe_alert: [])
end
override :project_associations
def project_associations
super.deep_merge(protected_environments: [])
end
end
end
Loading
Loading
@@ -1840,7 +1840,7 @@
end
 
describe '#protected_environment_by_name' do
let_it_be(:project) { create(:project) }
let_it_be(:project, reload: true) { create(:project) }
 
subject { project.protected_environment_by_name('production') }
 
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::EnvironmentSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
before_all do
project.add_developer(user)
end
before do
stub_licensed_features(environment_alerts: true)
end
it_behaves_like 'avoid N+1 on environments serialization'
def create_environment_with_associations(project)
create(:environment, project: project).tap do |environment|
create(:deployment, :success, environment: environment, project: project)
create(:deployment, :running, environment: environment, project: project)
create(:protected_environment, :maintainers_can_deploy, name: environment.name, project: project)
prometheus_alert = create(:prometheus_alert, project: project, environment: environment)
create(:alert_management_alert, :triggered, :prometheus, project: project, environment: environment, prometheus_alert: prometheus_alert)
end
end
end
Loading
Loading
@@ -3,8 +3,10 @@
require 'spec_helper'
 
RSpec.describe EnvironmentSerializer do
let(:user) { create(:user) }
let(:project) { create(:project) }
include CreateEnvironmentsHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
 
let(:json) do
described_class
Loading
Loading
@@ -12,43 +14,18 @@
.represent(resource)
end
 
before do
before_all do
project.add_developer(user)
end
 
context 'when there is a single object provided' do
let(:project) { create(:project, :repository) }
let(:deployable) { create(:ci_build) }
let(:deployment) do
create(:deployment, :success,
deployable: deployable,
user: user,
project: project,
sha: project.commit.id)
end
let(:resource) { deployment.environment }
before do
create(:ci_build, :manual, name: 'manual1', pipeline: deployable.pipeline)
end
it 'contains important elements of environment' do
expect(json)
.to include(:name, :external_url, :environment_path, :last_deployment)
end
it_behaves_like 'avoid N+1 on environments serialization'
 
it 'contains relevant information about last deployment' do
last_deployment = json.fetch(:last_deployment)
context 'when there is a collection of objects provided' do
let(:resource) { project.environments }
 
expect(last_deployment)
.to include(:ref, :user, :commit, :deployable, :manual_actions)
before_all do
create_list(:environment, 2, project: project)
end
end
context 'when there is a collection of objects provided' do
let(:project) { create(:project) }
let(:resource) { create_list(:environment, 2) }
 
it 'contains important elements of environment' do
expect(json.first)
Loading
Loading
@@ -207,4 +184,11 @@
end
end
end
def create_environment_with_associations(project)
create(:environment, project: project).tap do |environment|
create(:deployment, :success, environment: environment, project: project)
create(:deployment, :running, environment: environment, project: project)
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'avoid N+1 on environments serialization' do
it 'avoids N+1 database queries with grouping', :request_store do
create_environment_with_associations(project)
control = ActiveRecord::QueryRecorder.new { serialize(grouping: true) }
create_environment_with_associations(project)
expect { serialize(grouping: true) }.not_to exceed_query_limit(control.count)
end
it 'avoids N+1 database queries without grouping', :request_store do
create_environment_with_associations(project)
control = ActiveRecord::QueryRecorder.new { serialize(grouping: false) }
create_environment_with_associations(project)
expect { serialize(grouping: false) }.not_to exceed_query_limit(control.count)
end
def serialize(grouping:)
EnvironmentSerializer.new(current_user: user, project: project).yield_self do |serializer|
serializer.within_folders if grouping
serializer.represent(Environment.where(project: project))
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