Skip to content

Commit 494f553

Browse files
committed
feat(dashboard api): optimise dashboard query by creating manual materialized views for the matrix
Dashboard query was taking 20 seconds.
1 parent 317a64d commit 494f553

12 files changed

+190
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Sequel.migration do
2+
up do
3+
create_table(:materialized_matrix, charset: 'utf8') do
4+
Integer :consumer_id, null: false
5+
String :consumer_name, null: false
6+
Integer :consumer_version_id, null: false
7+
String :consumer_version_number, null: false
8+
Integer :consumer_version_order, null: false
9+
Integer :pact_publication_id, null: false
10+
Integer :pact_version_id, null: false
11+
String :pact_version_sha, null: false
12+
Integer :pact_revision_number, null: false
13+
DateTime :pact_created_at, null: false
14+
Integer :provider_id, null: false
15+
String :provider_name, null: false
16+
Integer :provider_version_id
17+
String :provider_version_number
18+
Integer :provider_version_order
19+
Integer :verification_id
20+
Boolean :success
21+
Integer :verification_number
22+
DateTime :verification_executed_at
23+
String :verification_build_url
24+
index [:consumer_id], name: 'ndx_mm_consumer_id'
25+
index [:provider_id], name: 'ndx_mm_provider_id'
26+
index [:consumer_version_order], name: 'ndx_mm_cv_ord'
27+
end
28+
29+
from(:materialized_matrix).insert(from(:matrix).select_all)
30+
end
31+
32+
down do
33+
drop_table(:materialized_matrix)
34+
end
35+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Sequel.migration do
2+
up do
3+
create_table(:materialized_latest_matrix, charset: 'utf8') do
4+
Integer :consumer_id, null: false
5+
String :consumer_name, null: false
6+
Integer :consumer_version_id, null: false
7+
String :consumer_version_number, null: false
8+
Integer :consumer_version_order, null: false
9+
Integer :pact_publication_id, null: false
10+
Integer :pact_version_id, null: false
11+
String :pact_version_sha, null: false
12+
Integer :pact_revision_number, null: false
13+
DateTime :pact_created_at, null: false
14+
Integer :provider_id, null: false
15+
String :provider_name, null: false
16+
Integer :provider_version_id
17+
String :provider_version_number
18+
Integer :provider_version_order
19+
Integer :verification_id
20+
Boolean :success
21+
Integer :verification_number
22+
DateTime :verification_executed_at
23+
String :verification_build_url
24+
index [:consumer_id], name: 'ndx_mlm_consumer_id'
25+
index [:provider_id], name: 'ndx_mlm_provider_id'
26+
index [:consumer_version_order], name: 'ndx_mlm_cv_ord'
27+
end
28+
29+
from(:materialized_latest_matrix).insert(from(:latest_matrix).select_all)
30+
end
31+
32+
down do
33+
drop_table(:materialized_latest_matrix)
34+
end
35+
end

lib/pact_broker/api/resources/base_resource.rb

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def initialize
4141
end
4242

4343
def finish_request
44+
if !request.get? && !request.head?
45+
matrix_service.refresh(identifier_from_path)
46+
end
4447
PactBroker.configuration.after_resource.call(self)
4548
end
4649

lib/pact_broker/matrix/actual_latest_row.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
module PactBroker
44
module Matrix
5-
# Latest pact revision for each consumer/provider => latest verification
65
class ActualLatestRow < Row
7-
set_dataset(:latest_matrix)
6+
set_dataset(:materialized_latest_matrix)
87

98
# For some reason, with MySQL, the success column value
109
# comes back as an integer rather than a boolean

lib/pact_broker/matrix/repository.rb

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ class Repository
1818
GROUP_BY_PROVIDER = [:consumer_name, :consumer_version_number, :provider_name]
1919
GROUP_BY_PACT = [:consumer_name, :provider_name]
2020

21+
def refresh params
22+
PactBroker::Matrix::Row.refresh(params)
23+
PactBroker::Matrix::ActualLatestRow.refresh(params)
24+
end
25+
2126
# Return the latest matrix row (pact/verification) for each consumer_version_number/provider_version_number
2227
def find selectors, options = {}
2328
# The group with the nil provider_version_numbers will be the results of the left outer join

lib/pact_broker/matrix/row.rb

+30-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
module PactBroker
66
module Matrix
7-
class Row < Sequel::Model(:matrix)
7+
class Row < Sequel::Model(:materialized_matrix)
88

99
associate(:one_to_many, :latest_triggered_webhooks, :class => "PactBroker::Webhooks::LatestTriggeredWebhook", primary_key: :pact_publication_id, key: :pact_publication_id)
1010
associate(:one_to_many, :webhooks, :class => "PactBroker::Webhooks::Webhook", primary_key: [:consumer_id, :provider_id], key: [:consumer_id, :provider_id])
@@ -14,6 +14,35 @@ class Row < Sequel::Model(:matrix)
1414
dataset_module do
1515
include PactBroker::Repositories::Helpers
1616

17+
def refresh params
18+
source_view_name = model.table_name.to_s.gsub('materialized_', '').to_sym
19+
if params[:consumer_name] || params[:provider_name]
20+
criteria = {}
21+
if params[:consumer_name]
22+
pacticipant = PactBroker::Domain::Pacticipant.where(name_like(:name, params[:consumer_name])).single_record
23+
criteria[:consumer_id] = pacticipant.id if pacticipant
24+
end
25+
26+
if params[:provider_name]
27+
pacticipant = PactBroker::Domain::Pacticipant.where(name_like(:name, params[:provider_name])).single_record
28+
criteria[:provider_id] = pacticipant.id if pacticipant
29+
end
30+
if criteria.any?
31+
db[model.table_name].where(criteria).delete
32+
db[model.table_name].insert(db[source_view_name].where(criteria))
33+
end
34+
end
35+
36+
if params[:pacticipant_name]
37+
pacticipant = PactBroker::Domain::Pacticipant.where(name_like(:name, params[:pacticipant_name])).single_record
38+
if pacticipant
39+
db[model.table_name].where(consumer_id: pacticipant.id).or(provider_id: pacticipant.id).delete
40+
new_rows = db[source_view_name].where(consumer_id: pacticipant.id).or(provider_id: pacticipant.id)
41+
db[model.table_name].insert(new_rows)
42+
end
43+
end
44+
end
45+
1746
def matching_selectors selectors
1847
if selectors.size == 1
1948
where_consumer_or_provider_is(selectors.first)

lib/pact_broker/matrix/service.rb

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
require 'pact_broker/repositories'
2+
require 'pact_broker/matrix/row'
3+
require 'pact_broker/matrix/actual_latest_row'
24

35
module PactBroker
46
module Matrix
@@ -8,6 +10,10 @@ module Service
810
extend PactBroker::Repositories
911
extend PactBroker::Services
1012

13+
def refresh params
14+
matrix_repository.refresh params
15+
end
16+
1117
def find criteria, options = {}
1218
matrix_repository.find criteria, options
1319
end

spec/lib/pact_broker/api/resources/group_spec.rb

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'spec_helper'
22
require 'pact_broker/api/resources/group'
3+
require 'pact_broker/groups/service'
34
require 'rack/test'
45

56
module PactBroker::Api

spec/lib/pact_broker/matrix/row_spec.rb

+55
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,61 @@
33
module PactBroker
44
module Matrix
55
describe Row do
6+
describe "refresh" do
7+
let(:td) { TestDataBuilder.new(auto_refresh_matrix: false) }
8+
9+
before do
10+
td.create_pact_with_hierarchy("Foo", "1", "Bar")
11+
end
12+
13+
context "with only a consumer_name" do
14+
subject { Row.refresh(consumer_name: "Foo") }
15+
16+
it "refreshes the data for the consumer" do
17+
subject
18+
expect(Row.all.collect(&:values)).to contain_hash(provider_name: "Bar", consumer_name: "Foo")
19+
end
20+
end
21+
22+
context "with only a provider_name" do
23+
subject { Row.refresh(provider_name: "Bar") }
24+
25+
it "refreshes the data for the provider" do
26+
subject
27+
expect(Row.all.collect(&:values)).to contain_hash(provider_name: "Bar", consumer_name: "Foo")
28+
end
29+
end
30+
31+
context "with both consumer_name and provider_name" do
32+
subject { Row.refresh(provider_name: "Bar", consumer_name: "Foo") }
33+
34+
it "refreshes the data for the consumer and provider" do
35+
subject
36+
expect(Row.all.collect(&:values)).to contain_hash(provider_name: "Bar", consumer_name: "Foo")
37+
end
38+
end
39+
40+
context "when there was existing data" do
41+
it "deletes the existing data before inserting the new data" do
42+
Row.refresh(provider_name: "Bar", consumer_name: "Foo")
43+
expect(Row.count).to eq 1
44+
td.create_consumer_version("2")
45+
.create_pact
46+
Row.refresh(provider_name: "Bar", consumer_name: "Foo")
47+
expect(Row.count).to eq 2
48+
end
49+
end
50+
51+
context "with a pacticipant_name" do
52+
subject { Row.refresh(pacticipant_name: "Bar") }
53+
54+
it "refreshes the data for both consumer and provider roles" do
55+
subject
56+
expect(Row.all.collect(&:values)).to contain_hash(provider_name: "Bar", consumer_name: "Foo")
57+
end
58+
end
59+
end
60+
661
describe "<=>" do
762
let(:row_1) do
863
Row.new(

spec/migrations/23_pact_versions_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
let!(:pact_2) { create(:pacts, {version_id: consumer_version_2[:id], provider_id: provider[:id], pact_version_content_sha: '1234', created_at: now, updated_at: pact_updated_at}) }
1515

1616

17-
subject { PactBroker::Database.migrate(34) }
17+
subject { PactBroker::Database.migrate }
1818

1919
it "keeps the same number of pacts" do
2020
subject
@@ -65,7 +65,7 @@
6565
subject
6666

6767
PactBroker::Pacts::Service.create_or_update_pact(
68-
consumer_id: consumer[:id], provider_id: provider[:id], consumer_version_number: '1.2.3', json_content: load_fixture('a_consumer-a_provider.json')
68+
consumer_name: consumer[:name], provider_name: provider[:name], consumer_version_number: '1.2.3', json_content: load_fixture('a_consumer-a_provider.json')
6969
)
7070
end
7171
end

spec/migrations/change_migration_strategy_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def execute command
4545
it "allows data to be inserted" do
4646
consumer_id = @db[:pacticipants].insert(name: 'Foo', created_at: DateTime.now, updated_at: DateTime.now)
4747
provider_id = @db[:pacticipants].insert(name: 'Bar', created_at: DateTime.now, updated_at: DateTime.now)
48-
version_id = @db[:versions].insert(number: '1.2.3', pacticipant_id: consumer_id, created_at: DateTime.now, updated_at: DateTime.now)
48+
version_id = @db[:versions].insert(number: '1.2.3', order: 1, pacticipant_id: consumer_id, created_at: DateTime.now, updated_at: DateTime.now)
4949
pact_json = {consumer: {name: 'Foo'}, provider: {name: 'Bar'}, interactions: []}.to_json
5050
pact_version_id = @db[:pact_versions].insert(sha: '123', content: pact_json, created_at: DateTime.now, consumer_id: consumer_id, provider_id: provider_id)
5151
pact_publication_id = @db[:pact_publications].insert(consumer_version_id: version_id, provider_id: provider_id, revision_number: 1, pact_version_id: pact_version_id, created_at: DateTime.now)

spec/support/test_data_builder.rb

+16
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ class TestDataBuilder
3737
attr_reader :webhook
3838
attr_reader :webhook_execution
3939
attr_reader :triggered_webhook
40+
attr_accessor :auto_refresh_matrix
41+
42+
def initialize(params = {})
43+
@auto_refresh_matrix = params.fetch(:auto_refresh_matrix, true)
44+
end
45+
46+
def refresh_matrix
47+
if auto_refresh_matrix
48+
params = {}
49+
params[:consumer_name] = consumer.name if consumer
50+
params[:provider_name] = provider.name if provider
51+
matrix_service.refresh(params)
52+
end
53+
end
4054

4155
def create_pricing_service
4256
@pricing_service_id = pacticipant_repository.create(:name => 'Pricing Service', :repository_url => 'git@git.realestate.com.au:business-systems/pricing-service').save(raise_on_save_failure: true).id
@@ -185,6 +199,7 @@ def create_pact params = {}
185199
set_created_at_if_set params[:created_at], :pact_publications, {id: @pact.id}
186200
set_created_at_if_set params[:created_at], :pact_versions, {sha: @pact.pact_version_sha}
187201
@pact = PactBroker::Pacts::PactPublication.find(id: @pact.id).to_domain
202+
refresh_matrix
188203
self
189204
end
190205

@@ -253,6 +268,7 @@ def create_verification parameters = {}
253268
PactBroker::Domain::Tag.create(name: tag_name, version: provider_version)
254269
end
255270
end
271+
refresh_matrix
256272
self
257273
end
258274

0 commit comments

Comments
 (0)