Commit 3e673d46 authored by Arturo Herrero's avatar Arturo Herrero

Merge branch 'id-n-1-for-api-deployments' into 'master'

Resolve N + 1 for deployments API

See merge request gitlab-org/gitlab!57558
parents 19be1668 aceaf417
...@@ -45,6 +45,7 @@ class Deployment < ApplicationRecord ...@@ -45,6 +45,7 @@ class Deployment < ApplicationRecord
scope :active, -> { where(status: %i[created running]) } scope :active, -> { where(status: %i[created running]) }
scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) } scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) }
scope :with_deployable, -> { joins('INNER JOIN ci_builds ON ci_builds.id = deployments.deployable_id').preload(:deployable) } scope :with_deployable, -> { joins('INNER JOIN ci_builds ON ci_builds.id = deployments.deployable_id').preload(:deployable) }
scope :with_api_entity_associations, -> { preload({ deployable: { runner: [], tags: [], user: [], job_artifacts_archive: [] } }) }
scope :finished_after, ->(date) { where('finished_at >= ?', date) } scope :finished_after, ->(date) { where('finished_at >= ?', date) }
scope :finished_before, ->(date) { where('finished_at < ?', date) } scope :finished_before, ->(date) { where('finished_at < ?', date) }
......
---
title: Resolve N + 1 for deployments API
merge_request: 57558
author:
type: performance
...@@ -36,7 +36,9 @@ module API ...@@ -36,7 +36,9 @@ module API
get ':id/deployments' do get ':id/deployments' do
authorize! :read_deployment, user_project authorize! :read_deployment, user_project
deployments = DeploymentsFinder.new(params.merge(project: user_project)).execute deployments =
DeploymentsFinder.new(params.merge(project: user_project))
.execute.with_api_entity_associations
present paginate(deployments), with: Entities::Deployment present paginate(deployments), with: Entities::Deployment
end end
......
...@@ -11,10 +11,10 @@ module API ...@@ -11,10 +11,10 @@ module API
work_information(user) work_information(user)
end end
expose :followers, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user| expose :followers, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
user.followers.count user.followers.size
end end
expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user| expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
user.followees.count user.followees.size
end end
end end
end end
......
...@@ -3,22 +3,26 @@ ...@@ -3,22 +3,26 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Deployments do RSpec.describe API::Deployments do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:non_member) { create(:user) } let_it_be(:non_member) { create(:user) }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
end end
describe 'GET /projects/:id/deployments' do describe 'GET /projects/:id/deployments' do
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let!(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: Time.now, updated_at: Time.now) } let_it_be(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: Time.now, updated_at: Time.now) }
let!(:deployment_2) { create(:deployment, :success, project: project, iid: 12, ref: 'master', created_at: 1.day.ago, updated_at: 2.hours.ago) } let_it_be(:deployment_2) { create(:deployment, :success, project: project, iid: 12, ref: 'master', created_at: 1.day.ago, updated_at: 2.hours.ago) }
let!(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'master', created_at: 2.days.ago, updated_at: 1.hour.ago) } let_it_be(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'master', created_at: 2.days.ago, updated_at: 1.hour.ago) }
def perform_request(params = {})
get api("/projects/#{project.id}/deployments", user), params: params
end
context 'as member of the project' do context 'as member of the project' do
it 'returns projects deployments sorted by id asc' do it 'returns projects deployments sorted by id asc' do
get api("/projects/#{project.id}/deployments", user) perform_request
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -32,7 +36,7 @@ RSpec.describe API::Deployments do ...@@ -32,7 +36,7 @@ RSpec.describe API::Deployments do
context 'with updated_at filters specified' do context 'with updated_at filters specified' do
it 'returns projects deployments with last update in specified datetime range' do it 'returns projects deployments with last update in specified datetime range' do
get api("/projects/#{project.id}/deployments", user), params: { updated_before: 30.minutes.ago, updated_after: 90.minutes.ago } perform_request({ updated_before: 30.minutes.ago, updated_after: 90.minutes.ago })
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
...@@ -42,10 +46,7 @@ RSpec.describe API::Deployments do ...@@ -42,10 +46,7 @@ RSpec.describe API::Deployments do
context 'with the environment filter specifed' do context 'with the environment filter specifed' do
it 'returns deployments for the environment' do it 'returns deployments for the environment' do
get( perform_request({ environment: deployment_1.environment.name })
api("/projects/#{project.id}/deployments", user),
params: { environment: deployment_1.environment.name }
)
expect(json_response.size).to eq(1) expect(json_response.size).to eq(1)
expect(json_response.first['iid']).to eq(deployment_1.iid) expect(json_response.first['iid']).to eq(deployment_1.iid)
...@@ -86,6 +87,16 @@ RSpec.describe API::Deployments do ...@@ -86,6 +87,16 @@ RSpec.describe API::Deployments do
end end
end end
end end
it 'returns multiple deployments without N + 1' do
perform_request # warm up the cache
control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
create(:deployment, :success, project: project, iid: 21, ref: 'master')
expect { perform_request }.not_to exceed_query_limit(control_count)
end
end end
context 'as non member' do context 'as non member' do
......
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