Commit a93b591b authored by Arturo Herrero's avatar Arturo Herrero

Reduce number of SQL queries for service templates

- Move logic from Admin::ServicesController to Service.
- Improve performance avoiding N+1 queries:
  - Retrieve service templates reduced from 39 to 6 query counts.
  - Create service templates reduced from 178 to 80 query counts.
parent 1f4f729a
...@@ -3,11 +3,10 @@ ...@@ -3,11 +3,10 @@
class Admin::ServicesController < Admin::ApplicationController class Admin::ServicesController < Admin::ApplicationController
include ServiceParams include ServiceParams
before_action :whitelist_query_limiting, only: [:index]
before_action :service, only: [:edit, :update] before_action :service, only: [:edit, :update]
def index def index
@services = services_templates @services = Service.find_or_create_templates
end end
def edit def edit
...@@ -30,22 +29,9 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -30,22 +29,9 @@ class Admin::ServicesController < Admin::ApplicationController
private private
# rubocop: disable CodeReuse/ActiveRecord
def services_templates
Service.available_services_names.map do |service_name|
service_template = "#{service_name}_service".camelize.constantize
service_template.where(template: true).first_or_create
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def service def service
@service ||= Service.find_by(id: params[:id], template: true) @service ||= Service.find_by(id: params[:id], template: true)
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42430')
end
end end
...@@ -46,6 +46,7 @@ class Service < ApplicationRecord ...@@ -46,6 +46,7 @@ class Service < ApplicationRecord
scope :active, -> { where(active: true) } scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) } scope :without_defaults, -> { where(default: false) }
scope :by_type, -> (type) { where(type: type) } scope :by_type, -> (type) { where(type: type) }
scope :templates, -> { where(template: true) }
scope :push_hooks, -> { where(push_events: true, active: true) } scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
...@@ -259,6 +260,27 @@ class Service < ApplicationRecord ...@@ -259,6 +260,27 @@ class Service < ApplicationRecord
self.category == :issue_tracker self.category == :issue_tracker
end end
# Find all service templates; if some of them do not exist, create them
# within a transaction to perform the lowest possible SQL queries.
def self.find_or_create_templates
if templates.size == available_services_types.size
templates
else
create_nonexistent_templates
templates.reset
end
end
private_class_method def self.create_nonexistent_templates
nonexistent_services = available_services_types - templates.map(&:type)
transaction do
nonexistent_services.each do |service_type|
service_type.constantize.create(template: true)
end
end
end
def self.available_services_names def self.available_services_names
service_names = %w[ service_names = %w[
alerts alerts
...@@ -301,6 +323,10 @@ class Service < ApplicationRecord ...@@ -301,6 +323,10 @@ class Service < ApplicationRecord
service_names.sort_by(&:downcase) service_names.sort_by(&:downcase)
end end
def self.available_services_types
available_services_names.map { |service_name| "#{service_name}_service".camelize }
end
def self.build_from_template(project_id, template) def self.build_from_template(project_id, template)
service = template.dup service = template.dup
......
---
title: Reduce number of SQL queries for service templates
merge_request: 27396
author:
type: performance
...@@ -9,14 +9,31 @@ describe Admin::ServicesController do ...@@ -9,14 +9,31 @@ describe Admin::ServicesController do
sign_in(admin) sign_in(admin)
end end
describe 'GET #edit' do describe 'GET #index' do
let!(:project) { create(:project) } it 'avoids N+1 queries' do
query_count = ActiveRecord::QueryRecorder.new { get :index }.count
expect(query_count).to be <= 80
end
context 'with all existing templates' do
before do
Service.insert_all(
Service.available_services_types.map { |type| { template: true, type: type } }
)
end
it 'avoids N+1 queries' do
query_count = ActiveRecord::QueryRecorder.new { get :index }.count
Service.available_services_names.each do |service_name| expect(query_count).to eq(6)
context "#{service_name}" do end
end
end
describe 'GET #edit' do
let!(:service) do let!(:service) do
service_template = "#{service_name}_service".camelize.constantize create(:jira_service, :template)
service_template.where(template: true).first_or_create
end end
it 'successfully displays the template' do it 'successfully displays the template' do
...@@ -25,8 +42,6 @@ describe Admin::ServicesController do ...@@ -25,8 +42,6 @@ describe Admin::ServicesController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
end
end
describe "#update" do describe "#update" do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -149,9 +149,37 @@ describe Service do ...@@ -149,9 +149,37 @@ describe Service do
end end
end end
describe "Template" do describe 'template' do
let(:project) { create(:project) } let(:project) { create(:project) }
describe '.find_or_create_templates' do
it 'creates service templates' do
expect { Service.find_or_create_templates }.to change { Service.count }.from(0).to(Service.available_services_names.size)
end
context 'with all existing templates' do
before do
Service.insert_all(
Service.available_services_types.map { |type| { template: true, type: type } }
)
end
it 'does not create service templates' do
expect { Service.find_or_create_templates }.to change { Service.count }.by(0)
end
end
context 'with a few existing templates' do
before do
JiraService.create(template: true)
end
it 'creates the rest of the service templates' do
expect { Service.find_or_create_templates }.to change { Service.count }.from(1).to(Service.available_services_names.size)
end
end
end
describe '.build_from_template' do describe '.build_from_template' do
context 'when template is invalid' do context 'when template is invalid' do
it 'sets service template to inactive when template is invalid' do it 'sets service template to inactive when template is invalid' 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