Skip to content

Commit de0928e

Browse files
committed
feat(qp): introduce new optimization to batch siblings using multi-type steps
1 parent a38c812 commit de0928e

File tree

13 files changed

+655
-420
lines changed

13 files changed

+655
-420
lines changed

lib/query-planner/src/ast/merge_path.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,17 @@ impl MergePath {
179179
}
180180
self.common_prefix_len(other) == other.len()
181181
}
182+
183+
pub fn without_type_castings(&self) -> Self {
184+
let new_segments = self
185+
.inner
186+
.iter()
187+
.filter(|segment| !matches!(segment, Segment::Cast(_, _)))
188+
.cloned()
189+
.collect::<Vec<_>>();
190+
191+
Self::new(new_segments)
192+
}
182193
}
183194

184195
impl Display for MergePath {

lib/query-planner/src/planner/fetch/fetch_step_data.rs

Lines changed: 4 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
use std::{collections::BTreeSet, fmt::Display};
22

33
use bitflags::bitflags;
4-
use petgraph::{graph::NodeIndex, visit::EdgeRef};
5-
use tracing::trace;
4+
use petgraph::graph::NodeIndex;
65

76
use crate::{
87
ast::{
98
merge_path::{Condition, MergePath},
109
operation::VariableDefinition,
1110
safe_merge::AliasesRecords,
12-
selection_set::find_arguments_conflicts,
1311
},
1412
planner::{
1513
fetch::{
16-
fetch_graph::FetchGraph,
1714
selections::FetchStepSelections,
1815
state::{MultiTypeFetchStep, SingleTypeFetchStep},
1916
},
@@ -121,76 +118,9 @@ impl<State> FetchStepData<State> {
121118
}
122119
}
123120

124-
impl FetchStepData<MultiTypeFetchStep> {
125-
pub fn can_merge(
126-
&self,
127-
self_index: NodeIndex,
128-
other_index: NodeIndex,
129-
other: &Self,
130-
fetch_graph: &FetchGraph<MultiTypeFetchStep>,
131-
) -> bool {
132-
if self_index == other_index {
133-
return false;
134-
}
135-
136-
if self.service_name != other.service_name {
137-
return false;
138-
}
139-
140-
// We allow to merge root with entity calls by adding an inline fragment with the @include/@skip
141-
if self.is_entity_call() && other.is_entity_call() && self.condition != other.condition {
142-
return false;
143-
}
144-
145-
// If both are entities, their response_paths should match,
146-
// as we can't merge entity calls resolving different entities
147-
if matches!(self.kind, FetchStepKind::Entity) && self.kind == other.kind {
148-
if !self.response_path.eq(&other.response_path) {
149-
return false;
150-
}
151-
} else {
152-
// otherwise we can merge
153-
if !other.response_path.starts_with(&self.response_path) {
154-
return false;
155-
}
156-
}
157-
158-
let has_input_conflicts =
159-
FetchStepSelections::iter_matching_types(&self.input, &other.input, |_, s1, s2| {
160-
find_arguments_conflicts(s1, s2)
161-
})
162-
.iter()
163-
.any(|(_, conflicts)| !conflicts.is_empty());
164-
165-
if has_input_conflicts {
166-
trace!(
167-
"preventing merge of [{}]+[{}] due to input conflicts",
168-
self_index.index(),
169-
other_index.index(),
170-
);
171-
172-
return false;
173-
}
174-
175-
// if the `other` FetchStep has a single parent and it's `this` FetchStep
176-
if fetch_graph.parents_of(other_index).count() == 1
177-
&& fetch_graph
178-
.parents_of(other_index)
179-
.all(|edge| edge.source() == self_index)
180-
{
181-
return true;
182-
}
183-
184-
// if they do not share parents, they can't be merged
185-
if !fetch_graph.parents_of(self_index).all(|self_edge| {
186-
fetch_graph
187-
.parents_of(other_index)
188-
.any(|other_edge| other_edge.source() == self_edge.source())
189-
}) {
190-
return false;
191-
}
192-
193-
true
121+
impl<State> FetchStepData<State> {
122+
pub fn is_fetching_multiple_types(&self) -> bool {
123+
self.input.is_fetching_multiple_types() || self.output.is_fetching_multiple_types()
194124
}
195125
}
196126

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use std::collections::{HashMap, VecDeque};
2+
3+
use petgraph::{graph::NodeIndex, Direction};
4+
use tracing::{instrument, trace};
5+
6+
use crate::planner::fetch::{
7+
error::FetchGraphError, fetch_graph::FetchGraph, fetch_step_data::FetchStepData,
8+
optimize::utils::perform_fetch_step_merge, state::MultiTypeFetchStep,
9+
};
10+
11+
impl FetchGraph<MultiTypeFetchStep> {
12+
#[instrument(level = "trace", skip_all)]
13+
pub(crate) fn batch_multi_type(&mut self) -> Result<(), FetchGraphError> {
14+
let root_index = self
15+
.root_index
16+
.ok_or(FetchGraphError::NonSingleRootStep(0))?;
17+
// Breadth-First Search (BFS) starting from the root node.
18+
let mut queue = VecDeque::from([root_index]);
19+
20+
while let Some(parent_index) = queue.pop_front() {
21+
let mut merges_to_perform = Vec::<(NodeIndex, NodeIndex)>::new();
22+
let mut node_indexes: HashMap<NodeIndex, NodeIndex> = HashMap::new();
23+
let siblings_indices = self
24+
.graph
25+
.neighbors_directed(parent_index, Direction::Outgoing)
26+
.collect::<Vec<NodeIndex>>();
27+
28+
for (i, sibling_index) in siblings_indices.iter().enumerate() {
29+
queue.push_back(*sibling_index);
30+
let current = self.get_step_data(*sibling_index)?;
31+
32+
for other_sibling_index in siblings_indices.iter().skip(i + 1) {
33+
trace!(
34+
"checking if [{}] and [{}] can be batched",
35+
sibling_index.index(),
36+
other_sibling_index.index()
37+
);
38+
39+
let other_sibling = self.get_step_data(*other_sibling_index)?;
40+
41+
if current.can_be_batched_with(other_sibling) {
42+
trace!(
43+
"Found multi-type batching optimization: [{}] <- [{}]",
44+
sibling_index.index(),
45+
other_sibling_index.index()
46+
);
47+
// Register their original indexes in the map.
48+
node_indexes.insert(*sibling_index, *sibling_index);
49+
node_indexes.insert(*other_sibling_index, *other_sibling_index);
50+
51+
merges_to_perform.push((*sibling_index, *other_sibling_index));
52+
}
53+
}
54+
}
55+
56+
for (child_index, other_child_index) in merges_to_perform {
57+
// Get the latest indexes for the nodes, accounting for previous merges.
58+
let child_index_latest = node_indexes
59+
.get(&child_index)
60+
.ok_or(FetchGraphError::IndexMappingLost)?;
61+
let other_child_index_latest = node_indexes
62+
.get(&other_child_index)
63+
.ok_or(FetchGraphError::IndexMappingLost)?;
64+
65+
if child_index_latest == other_child_index_latest {
66+
continue;
67+
}
68+
69+
if self.is_ancestor_or_descendant(*child_index_latest, *other_child_index_latest) {
70+
continue;
71+
}
72+
73+
let (me, other) =
74+
self.get_pair_of_steps_mut(*child_index_latest, *other_child_index_latest)?;
75+
76+
// We "declare" the known type for the step, so later merging will be possible into that type instead of failing with an error.
77+
for (input_type_name, _) in other.input.iter_selections() {
78+
me.input.declare_known_type(input_type_name);
79+
}
80+
81+
// We "declare" the known type for the step, so later merging will be possible into that type instead of failing with an error.
82+
for (output_type_name, _) in other.output.iter_selections() {
83+
me.output.declare_known_type(output_type_name);
84+
}
85+
86+
perform_fetch_step_merge(
87+
*child_index_latest,
88+
*other_child_index_latest,
89+
self,
90+
true,
91+
)?;
92+
93+
// Because `other_child` was merged into `child`,
94+
// then everything that was pointing to `other_child`
95+
// has to point to the `child`.
96+
node_indexes.insert(*other_child_index_latest, *child_index_latest);
97+
}
98+
}
99+
100+
Ok(())
101+
}
102+
}
103+
104+
impl FetchStepData<MultiTypeFetchStep> {
105+
pub fn can_be_batched_with(&self, other: &Self) -> bool {
106+
if self.kind != other.kind {
107+
return false;
108+
}
109+
110+
if self.service_name != other.service_name {
111+
return false;
112+
}
113+
114+
if !self.is_entity_call() || !other.is_entity_call() {
115+
return false;
116+
}
117+
118+
if self.response_path.without_type_castings() != other.response_path.without_type_castings()
119+
{
120+
return false;
121+
}
122+
123+
if self.has_arguments_conflicts_with(other) {
124+
return false;
125+
}
126+
127+
true
128+
}
129+
}

lib/query-planner/src/planner/fetch/optimize/merge_children_with_parents.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl FetchGraph<MultiTypeFetchStep> {
6363
.get(&child_index)
6464
.ok_or(FetchGraphError::IndexMappingLost)?;
6565

66-
perform_fetch_step_merge(*parent_index_latest, *child_index_latest, self)?;
66+
perform_fetch_step_merge(*parent_index_latest, *child_index_latest, self, false)?;
6767

6868
// Because `child` was merged into `parent`,
6969
// then everything that was pointing to `child`

lib/query-planner/src/planner/fetch/optimize/merge_leafs.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
use petgraph::graph::NodeIndex;
22
use tracing::{instrument, trace};
33

4-
use crate::{
5-
ast::selection_set::find_arguments_conflicts,
6-
planner::fetch::{
7-
error::FetchGraphError, fetch_graph::FetchGraph, fetch_step_data::FetchStepData,
8-
optimize::utils::perform_fetch_step_merge, selections::FetchStepSelections,
9-
state::MultiTypeFetchStep,
10-
},
4+
use crate::planner::fetch::{
5+
error::FetchGraphError, fetch_graph::FetchGraph, fetch_step_data::FetchStepData,
6+
optimize::utils::perform_fetch_step_merge, state::MultiTypeFetchStep,
117
};
128

139
impl FetchStepData<MultiTypeFetchStep> {
@@ -57,19 +53,7 @@ impl FetchStepData<MultiTypeFetchStep> {
5753
return false;
5854
}
5955

60-
let input_conflicts = FetchStepSelections::<MultiTypeFetchStep>::iter_matching_types(
61-
&self.input,
62-
&other.input,
63-
|_, self_selections, other_selections| {
64-
find_arguments_conflicts(self_selections, other_selections)
65-
},
66-
);
67-
68-
let has_conflicts = input_conflicts
69-
.iter()
70-
.any(|(_, conflicts)| !conflicts.is_empty());
71-
72-
if has_conflicts {
56+
if self.has_arguments_conflicts_with(other) {
7357
return false;
7458
}
7559

@@ -85,7 +69,7 @@ impl FetchGraph<MultiTypeFetchStep> {
8569
/// meaning the overall depth (amount of parallel layers) is not increased.
8670
pub(crate) fn merge_leafs(&mut self) -> Result<(), FetchGraphError> {
8771
while let Some((target_idx, leaf_idx)) = self.find_merge_candidate()? {
88-
perform_fetch_step_merge(target_idx, leaf_idx, self)?;
72+
perform_fetch_step_merge(target_idx, leaf_idx, self, false)?;
8973
}
9074

9175
Ok(())

lib/query-planner/src/planner/fetch/optimize/merge_siblings.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ impl FetchGraph<MultiTypeFetchStep> {
9292
.get(&other_child_index)
9393
.ok_or(FetchGraphError::IndexMappingLost)?;
9494

95-
perform_fetch_step_merge(*child_index_latest, *other_child_index_latest, self)?;
95+
perform_fetch_step_merge(
96+
*child_index_latest,
97+
*other_child_index_latest,
98+
self,
99+
false,
100+
)?;
96101

97102
// Because `other_child` was merged into `child`,
98103
// then everything that was pointing to `other_child`

lib/query-planner/src/planner/fetch/optimize/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod apply_internal_aliases_patching;
2+
mod batch_multi_type;
23
mod deduplicate_and_prune_fetch_steps;
34
mod merge_children_with_parents;
45
mod merge_leafs;
@@ -29,6 +30,7 @@ impl FetchGraph<MultiTypeFetchStep> {
2930
self.merge_siblings()?;
3031
self.merge_leafs()?;
3132
self.deduplicate_and_prune_fetch_steps()?;
33+
self.batch_multi_type()?;
3234

3335
let node_count_after = self.graph.node_count();
3436
let edge_count_after = self.graph.edge_count();

0 commit comments

Comments
 (0)