Skip to content

Commit 2b6b992

Browse files
authored
Optimize ServiceInstanceListFetcher (#4223)
Reduce the overall query complexity by fetching instances and shared instances separately. Apply filters (e.g. for readability) to each dataset instead of JOINing them and having multiple WHERE conditions. UNION the two datasets to get the final result set. On large foundations this reduces the query execution time from multiple 100ms to less than 100ms.
1 parent eb575b5 commit 2b6b992

File tree

2 files changed

+80
-52
lines changed

2 files changed

+80
-52
lines changed

app/fetchers/service_instance_list_fetcher.rb

Lines changed: 76 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,98 @@ module VCAP::CloudController
55
class ServiceInstanceListFetcher < BaseListFetcher
66
class << self
77
def fetch(message, omniscient: false, readable_spaces_dataset: nil, eager_loaded_associations: [])
8-
dataset = ServiceInstance.dataset.eager(eager_loaded_associations).
9-
join(:spaces, id: Sequel[:service_instances][:space_id]).
10-
left_join(:service_instance_shares, service_instance_guid: Sequel[:service_instances][:guid])
11-
12-
unless omniscient
13-
dataset = dataset.where do
14-
(Sequel[:spaces][:guid] =~ readable_spaces_dataset) |
15-
(Sequel[:service_instance_shares][:target_space_guid] =~ readable_spaces_dataset)
16-
end
17-
end
8+
dataset = ServiceInstance.dataset.select_all(:service_instances)
9+
10+
if omniscient
11+
dataset = filter(dataset, message)
12+
else
13+
# Reduce query complexity by fetching instances and shared instances separately
14+
instances = dataset.clone.join(:spaces, id: :service_instances__space_id)
15+
instances = instances.where(Sequel[:spaces][:guid] =~ readable_spaces_dataset)
16+
instances = filter(instances, message)
17+
18+
shared_instances = dataset.clone.join(:service_instance_shares, service_instance_guid: :service_instances__guid)
19+
shared_instances = shared_instances.where(Sequel[:service_instance_shares][:target_space_guid] =~ readable_spaces_dataset)
20+
shared_instances = filter(shared_instances, message)
1821

19-
if message.requested?(:service_plan_names) || message.requested?(:service_plan_guids)
20-
dataset = dataset.left_join(:service_plans,
21-
id: Sequel[:service_instances][:service_plan_id])
22+
# UNION the two datasets
23+
dataset = instances.union(shared_instances, all: true, alias: :service_instances)
2224
end
2325

24-
filter(dataset, message).
25-
select_all(:service_instances).
26-
distinct
26+
dataset = dataset.distinct(:service_instances__id)
27+
dataset.eager(eager_loaded_associations)
2728
end
2829

2930
private
3031

3132
def filter(dataset, message)
32-
dataset = dataset.where(service_instances__name: message.names) if message.requested?(:names)
33-
34-
if message.requested?(:type)
35-
dataset = case message.type
36-
when 'managed'
37-
dataset.where { (Sequel[:service_instances][:is_gateway_service] =~ true) }
38-
when 'user-provided'
39-
dataset.where { (Sequel[:service_instances][:is_gateway_service] =~ false) }
40-
end
41-
end
33+
dataset = filter_names(dataset, message) if message.requested?(:names)
34+
dataset = filter_type(dataset, message) if message.requested?(:type)
35+
dataset = filter_organization_guids(dataset, message) if message.requested?(:organization_guids)
36+
dataset = filter_space_guids(dataset, message) if message.requested?(:space_guids)
37+
dataset = filter_service_plan_names(dataset, message) if message.requested?(:service_plan_names)
38+
dataset = filter_service_plan_guids(dataset, message) if message.requested?(:service_plan_guids)
39+
dataset = filter_label(dataset, message) if message.requested?(:label_selector)
4240

43-
if message.requested?(:organization_guids)
44-
spaces_in_orgs = Space.dataset.select(:spaces__guid).
45-
join(:organizations, id: Sequel[:spaces][:organization_id]).
46-
where(Sequel[:organizations][:guid] =~ message.organization_guids)
41+
super(message, dataset, ServiceInstance)
42+
end
4743

48-
dataset = dataset.where do
49-
(Sequel[:spaces][:guid] =~ spaces_in_orgs) |
50-
(Sequel[:service_instance_shares][:target_space_guid] =~ spaces_in_orgs)
51-
end
52-
end
44+
def filter_names(dataset, message)
45+
dataset.where(service_instances__name: message.names)
46+
end
5347

54-
if message.requested?(:space_guids)
55-
dataset = dataset.where do
56-
(Sequel[:spaces][:guid] =~ message.space_guids) |
57-
(Sequel[:service_instance_shares][:target_space_guid] =~ message.space_guids)
58-
end
48+
def filter_type(dataset, message)
49+
case message.type
50+
when 'managed'
51+
dataset.where(Sequel[:service_instances][:is_gateway_service] =~ true)
52+
when 'user-provided'
53+
dataset.where(Sequel[:service_instances][:is_gateway_service] =~ false)
54+
else
55+
dataset
5956
end
57+
end
6058

61-
dataset = dataset.where { Sequel[:service_plans][:guid] =~ message.service_plan_guids } if message.requested?(:service_plan_guids)
59+
def filter_organization_guids(dataset, message)
60+
dataset = dataset.join(:spaces, id: :service_instances__space_id) unless joined?(dataset, :spaces)
61+
dataset = dataset.left_join(:service_instance_shares, service_instance_guid: :service_instances__guid) unless joined?(dataset, :service_instance_shares)
6262

63-
dataset = dataset.where { Sequel[:service_plans][:name] =~ message.service_plan_names } if message.requested?(:service_plan_names)
63+
spaces_in_orgs = Space.dataset.select(:spaces__guid).
64+
join(:organizations, id: :spaces__organization_id).
65+
where(Sequel[:organizations][:guid] =~ message.organization_guids)
6466

65-
if message.requested?(:label_selector)
66-
dataset = LabelSelectorQueryGenerator.add_selector_queries(
67-
label_klass: ServiceInstanceLabelModel,
68-
resource_dataset: dataset,
69-
requirements: message.requirements,
70-
resource_klass: ServiceInstance
71-
)
72-
end
67+
dataset.where((Sequel[:spaces][:guid] =~ spaces_in_orgs) | (Sequel[:service_instance_shares][:target_space_guid] =~ spaces_in_orgs))
68+
end
7369

74-
super(message, dataset, ServiceInstance)
70+
def filter_space_guids(dataset, message)
71+
dataset = dataset.join(:spaces, id: :service_instances__space_id) unless joined?(dataset, :spaces)
72+
dataset = dataset.left_join(:service_instance_shares, service_instance_guid: :service_instances__guid) unless joined?(dataset, :service_instance_shares)
73+
74+
dataset.where((Sequel[:spaces][:guid] =~ message.space_guids) | (Sequel[:service_instance_shares][:target_space_guid] =~ message.space_guids))
75+
end
76+
77+
def filter_service_plan_names(dataset, message)
78+
dataset = dataset.left_join(:service_plans, id: :service_instances__service_plan_id) unless joined?(dataset, :service_plans)
79+
80+
dataset.where(Sequel[:service_plans][:name] =~ message.service_plan_names)
81+
end
82+
83+
def filter_service_plan_guids(dataset, message)
84+
dataset = dataset.left_join(:service_plans, id: :service_instances__service_plan_id) unless joined?(dataset, :service_plans)
85+
86+
dataset.where(Sequel[:service_plans][:guid] =~ message.service_plan_guids)
87+
end
88+
89+
def filter_label(dataset, message)
90+
LabelSelectorQueryGenerator.add_selector_queries(
91+
label_klass: ServiceInstanceLabelModel,
92+
resource_dataset: dataset,
93+
requirements: message.requirements,
94+
resource_klass: ServiceInstance
95+
)
96+
end
97+
98+
def joined?(dataset, table)
99+
dataset.opts[:join]&.any? { |j| j.table_expr == table }
75100
end
76101
end
77102
end

app/models/services/service_instance.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ class InvalidServiceBinding < StandardError; end
1010
plugin :serialization
1111
plugin :single_table_inheritance, :is_gateway_service,
1212
model_map: lambda { |is_gateway_service|
13-
if is_gateway_service
13+
# When selecting a UNION of multiple sub-queries, MySQL does not maintain the original type - i.e. tinyint(1) - and
14+
# thus Sequel does not convert the value to a boolean.
15+
# See https://bugs.mysql.com/bug.php?id=30886
16+
if ActiveModel::Type::Boolean.new.cast(is_gateway_service)
1417
VCAP::CloudController::ManagedServiceInstance
1518
else
1619
VCAP::CloudController::UserProvidedServiceInstance

0 commit comments

Comments
 (0)