Skip to content

Commit 50c28a4

Browse files
yndajasmike3985
andcommitted
Reduce query counts in GraphQL dataloader sources
We found that the previous eager loading approach wasn't using joins effectively, so we're more manually crafting the queries to join in an efficient way. We also found that some associations we're still lazy loaded after the initial queries - this new approach avoids that issue For now we're selecting all columns from the editions table. We'll reduce the selections down to a subset in a later change The change to using a UNION affects the order of the resulting editions, so we've added a test to document our expectations Co-authored-by: Mike Patrick <mike.patrick2@digital.cabinet-office.gov.uk>
1 parent 308d748 commit 50c28a4

File tree

3 files changed

+113
-36
lines changed

3 files changed

+113
-36
lines changed

app/graphql/sources/linked_to_editions_source.rb

+55-23
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ def initialize(content_store:)
77
# rubocop:enable Lint/MissingSuper
88

99
def fetch(editions_and_link_types)
10+
all_selections = {
11+
links: %i[link_type position],
12+
documents: %i[content_id],
13+
}
1014
edition_id_tuples = []
1115
content_id_tuples = []
1216
link_types_map = {}
@@ -17,37 +21,65 @@ def fetch(editions_and_link_types)
1721
link_types_map[[edition.content_id, link_type]] = []
1822
end
1923

20-
edition_links = Link
21-
.joins(:edition, { target_documents: @content_store })
22-
.includes(:edition, { target_documents: @content_store })
24+
link_set_links_target_editions = Edition
25+
.joins(document: { reverse_links: :link_set })
2326
.where(
24-
'("editions"."id", "links"."link_type") IN (?)',
25-
Arel.sql(edition_id_tuples.join(",")),
27+
'("link_sets"."content_id", "links"."link_type") IN (?)',
28+
Arel.sql(content_id_tuples.join(",")),
29+
)
30+
.where(
31+
editions: { content_store: @content_store },
32+
documents: { locale: "en" },
33+
)
34+
.select(
35+
"editions.*",
36+
all_selections,
37+
{ link_sets: { content_id: :source_content_id } },
2638
)
27-
.where(target_documents: { locale: "en" })
28-
.order(link_type: :asc, position: :asc)
2939

30-
link_set_links = Link
31-
.joins(:link_set, { target_documents: @content_store })
32-
.includes(:link_set, { target_documents: @content_store })
40+
edition_links_target_editions = Edition
41+
.joins(document: :reverse_links)
42+
.joins(
43+
<<~SQL,
44+
INNER JOIN editions source_editions
45+
ON source_editions.id = links.edition_id
46+
SQL
47+
)
48+
.joins(
49+
<<~SQL,
50+
INNER JOIN documents source_documents
51+
ON source_documents.id = source_editions.document_id
52+
SQL
53+
)
3354
.where(
34-
'("link_sets"."content_id", "links"."link_type") IN (?)',
35-
Arel.sql(content_id_tuples.join(",")),
55+
'("source_editions"."id", "links"."link_type") IN (?)',
56+
Arel.sql(edition_id_tuples.join(",")),
57+
)
58+
.where(
59+
editions: { content_store: @content_store },
60+
documents: { locale: "en" },
61+
)
62+
.select(
63+
"editions.*",
64+
all_selections,
65+
{ source_documents: { content_id: :source_content_id } },
3666
)
37-
.where(target_documents: { locale: "en" })
38-
.order(link_type: :asc, position: :asc)
3967

40-
all_links = edition_links + link_set_links
68+
all_editions = Edition
69+
.from(
70+
<<~SQL,
71+
(
72+
#{link_set_links_target_editions.to_sql}
73+
UNION
74+
#{edition_links_target_editions.to_sql}
75+
) AS editions
76+
SQL
77+
)
78+
.order(link_type: :asc, position: :asc)
4179

42-
all_links.each_with_object(link_types_map) { |link, hash|
43-
hash[[(link.link_set || link.edition).content_id, link.link_type]].concat(editions_for_link(link))
80+
all_editions.each_with_object(link_types_map) { |edition, hash|
81+
hash[[edition.source_content_id, edition.link_type]] << edition
4482
}.values
4583
end
46-
47-
private
48-
49-
def editions_for_link(link)
50-
link.target_documents.map { |document| document.send(@content_store) }
51-
end
5284
end
5385
end

app/graphql/sources/reverse_linked_to_editions_source.rb

+30-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ def initialize(content_store:)
77
# rubocop:enable Lint/MissingSuper
88

99
def fetch(editions_and_link_types)
10+
all_selections = {
11+
links: %i[target_content_id link_type link_set_id edition_id],
12+
documents: %i[content_id],
13+
}
1014
content_id_tuples = []
1115
link_types_map = {}
1216

@@ -15,26 +19,39 @@ def fetch(editions_and_link_types)
1519
link_types_map[[edition.content_id, link_type]] = []
1620
end
1721

18-
all_links = Link
22+
link_set_links_source_editions = Edition
23+
.joins(:document)
24+
.joins("INNER JOIN link_sets ON link_sets.content_id = documents.content_id")
25+
.joins("INNER JOIN links ON links.link_set_id = link_sets.id")
26+
.where(editions: { content_store: @content_store })
1927
.where(
2028
'("links"."target_content_id", "links"."link_type") IN (?)',
2129
Arel.sql(content_id_tuples.join(",")),
2230
)
23-
.includes(source_documents: @content_store)
31+
.select("editions.*", all_selections)
2432

25-
all_links.each_with_object(link_types_map) { |link, hash|
26-
if link.link_set
27-
hash[[link.target_content_id, link.link_type]].concat(editions_for_link_set_link(link))
28-
elsif link.edition
29-
hash[[link.target_content_id, link.link_type]] << link.edition
30-
end
31-
}.values
32-
end
33+
edition_links_source_editions = Edition
34+
.joins(:document, :links)
35+
.where(editions: { content_store: @content_store })
36+
.where(
37+
'("links"."target_content_id", "links"."link_type") IN (?)',
38+
Arel.sql(content_id_tuples.join(",")),
39+
)
40+
.select("editions.*", all_selections)
3341

34-
private
42+
all_editions = Edition.from(
43+
<<~SQL,
44+
(
45+
#{link_set_links_source_editions.to_sql}
46+
UNION
47+
#{edition_links_source_editions.to_sql}
48+
) AS editions
49+
SQL
50+
)
3551

36-
def editions_for_link_set_link(link)
37-
link.source_documents.map { |document| document.send(@content_store) }
52+
all_editions.each_with_object(link_types_map) { |edition, hash|
53+
hash[[edition.target_content_id, edition.link_type]] << edition
54+
}.values
3855
end
3956
end
4057
end

spec/graphql/sources/linked_to_editions_source_spec.rb

+28
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,32 @@
7777
expect(request.load).to match_array([target_edition_3, target_edition_4])
7878
end
7979
end
80+
81+
it "returns editions in order of their associated link's `position`" do
82+
target_edition_0 = create(:edition)
83+
target_edition_1 = create(:edition)
84+
target_edition_2 = create(:edition)
85+
target_edition_3 = create(:edition)
86+
87+
source_edition = create(:edition, content_store: "draft")
88+
create(:link, edition: source_edition, target_content_id: target_edition_1.content_id, position: 1, link_type: "test_link")
89+
create(:link, edition: source_edition, target_content_id: target_edition_3.content_id, position: 3, link_type: "test_link")
90+
91+
link_set = create(:link_set, content_id: source_edition.content_id)
92+
create(:link, link_set:, target_content_id: target_edition_0.content_id, position: 0, link_type: "test_link")
93+
create(:link, link_set:, target_content_id: target_edition_2.content_id, position: 2, link_type: "test_link")
94+
95+
GraphQL::Dataloader.with_dataloading do |dataloader|
96+
request = dataloader.with(
97+
described_class,
98+
content_store: source_edition.content_store,
99+
).request([
100+
source_edition,
101+
"test_link",
102+
%i[id base_path title document_id],
103+
])
104+
105+
expect(request.load).to eq([target_edition_0, target_edition_1, target_edition_2, target_edition_3])
106+
end
107+
end
80108
end

0 commit comments

Comments
 (0)