Commit 29648c60 authored by Adam Hegyi's avatar Adam Hegyi

Use UNION instead of OR for milestone queries

This MR optimizes some of the milestones (timebox like model) queries by
using UNION instead of OR.
parent 19b78d28
...@@ -12,7 +12,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -12,7 +12,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def index def index
respond_to do |format| respond_to do |format|
format.html do format.html do
@milestone_states = Milestone.states_count(group_projects_with_access, [group]) @milestone_states = Milestone.states_count(group_projects_with_access.without_order, [group])
@milestones = milestones.page(params[:page]) @milestones = milestones.page(params[:page])
end end
format.json do format.json do
......
...@@ -5,6 +5,10 @@ class ApplicationRecord < ActiveRecord::Base ...@@ -5,6 +5,10 @@ class ApplicationRecord < ActiveRecord::Base
alias_method :reset, :reload alias_method :reset, :reload
def self.without_order
reorder(nil)
end
def self.id_in(ids) def self.id_in(ids)
where(id: ids) where(id: ids)
end end
......
...@@ -9,6 +9,7 @@ module Timebox ...@@ -9,6 +9,7 @@ module Timebox
include IidRoutes include IidRoutes
include Referable include Referable
include StripAttribute include StripAttribute
include FromUnion
TimeboxStruct = Struct.new(:title, :name, :id) do TimeboxStruct = Struct.new(:title, :name, :id) do
# Ensure these models match the interface required for exporting # Ensure these models match the interface required for exporting
...@@ -65,7 +66,11 @@ module Timebox ...@@ -65,7 +66,11 @@ module Timebox
groups = groups.compact if groups.is_a? Array groups = groups.compact if groups.is_a? Array
groups = [] if groups.nil? groups = [] if groups.nil?
where(project_id: projects).or(where(group_id: groups)) if Feature.enabled?(:optimized_timebox_queries)
from_union([where(project_id: projects), where(group_id: groups)], remove_duplicates: false)
else
where(project_id: projects).or(where(group_id: groups))
end
end end
scope :within_timeframe, -> (start_date, end_date) do scope :within_timeframe, -> (start_date, end_date) do
......
---
title: Optimize SQL queries on Milestone index page
merge_request: 32953
author:
type: performance
...@@ -225,70 +225,88 @@ describe Milestone do ...@@ -225,70 +225,88 @@ describe Milestone do
end end
end end
describe '#for_projects_and_groups' do shared_examples '#for_projects_and_groups' do
let(:project) { create(:project) } describe '#for_projects_and_groups' do
let(:project_other) { create(:project) } let_it_be(:project) { create(:project) }
let(:group) { create(:group) } let_it_be(:project_other) { create(:project) }
let(:group_other) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:group_other) { create(:group) }
before(:all) do
create(:milestone, project: project)
create(:milestone, project: project_other)
create(:milestone, group: group)
create(:milestone, group: group_other)
end
before do subject { described_class.for_projects_and_groups(projects, groups) }
create(:milestone, project: project)
create(:milestone, project: project_other) shared_examples 'filters by projects and groups' do
create(:milestone, group: group) it 'returns milestones filtered by project' do
create(:milestone, group: group_other) milestones = described_class.for_projects_and_groups(projects, [])
end
expect(milestones.count).to eq(1)
expect(milestones.first.project_id).to eq(project.id)
end
it 'returns milestones filtered by group' do
milestones = described_class.for_projects_and_groups([], groups)
subject { described_class.for_projects_and_groups(projects, groups) } expect(milestones.count).to eq(1)
expect(milestones.first.group_id).to eq(group.id)
end
shared_examples 'filters by projects and groups' do it 'returns milestones filtered by both project and group' do
it 'returns milestones filtered by project' do milestones = described_class.for_projects_and_groups(projects, groups)
milestones = described_class.for_projects_and_groups(projects, [])
expect(milestones.count).to eq(1) expect(milestones.count).to eq(2)
expect(milestones.first.project_id).to eq(project.id) expect(milestones).to contain_exactly(project.milestones.first, group.milestones.first)
end
end end
it 'returns milestones filtered by group' do context 'ids as params' do
milestones = described_class.for_projects_and_groups([], groups) let(:projects) { [project.id] }
let(:groups) { [group.id] }
expect(milestones.count).to eq(1) it_behaves_like 'filters by projects and groups'
expect(milestones.first.group_id).to eq(group.id)
end end
it 'returns milestones filtered by both project and group' do context 'relations as params' do
milestones = described_class.for_projects_and_groups(projects, groups) let(:projects) { Project.where(id: project.id).select(:id) }
let(:groups) { Group.where(id: group.id).select(:id) }
expect(milestones.count).to eq(2) it_behaves_like 'filters by projects and groups'
expect(milestones).to contain_exactly(project.milestones.first, group.milestones.first)
end end
end
context 'ids as params' do context 'objects as params' do
let(:projects) { [project.id] } let(:projects) { [project] }
let(:groups) { [group.id] } let(:groups) { [group] }
it_behaves_like 'filters by projects and groups' it_behaves_like 'filters by projects and groups'
end end
context 'relations as params' do it 'returns no records if projects and groups are nil' do
let(:projects) { Project.where(id: project.id).select(:id) } milestones = described_class.for_projects_and_groups(nil, nil)
let(:groups) { Group.where(id: group.id).select(:id) }
it_behaves_like 'filters by projects and groups' expect(milestones).to be_empty
end
end end
end
context 'objects as params' do context 'when `optimized_timebox_queries` feature flag is enabled' do
let(:projects) { [project] } before do
let(:groups) { [group] } stub_feature_flags(optimized_timebox_queries: true)
it_behaves_like 'filters by projects and groups'
end end
it 'returns no records if projects and groups are nil' do it_behaves_like '#for_projects_and_groups'
milestones = described_class.for_projects_and_groups(nil, nil) end
expect(milestones).to be_empty context 'when `optimized_timebox_queries` feature flag is disabled' do
before do
stub_feature_flags(optimized_timebox_queries: false)
end end
it_behaves_like '#for_projects_and_groups'
end end
describe '.upcoming_ids' do describe '.upcoming_ids' 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