Skip to content

Commit 5af1024

Browse files
committed
perf(gatsby): fix performance regression with query dependency cleaning (#28032)
* perf(gatsby): fix performance regression with query dependency cleaning * update snapshot * More consistent codestyle (cherry picked from commit de5517b)
1 parent f039123 commit 5af1024

File tree

5 files changed

+84
-25
lines changed

5 files changed

+84
-25
lines changed

packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Object {
5858
"byConnection": Map {},
5959
"byNode": Map {},
6060
"deletedQueries": Set {},
61+
"queryNodes": Map {},
6162
"trackedComponents": Map {
6263
"/Users/username/dev/site/src/templates/my-sweet-new-page.js" => Object {
6364
"componentPath": "/Users/username/dev/site/src/templates/my-sweet-new-page.js",

packages/gatsby/src/redux/reducers/__tests__/__snapshots__/queries.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ Object {
1313
},
1414
},
1515
"deletedQueries": Set {},
16+
"queryNodes": Map {
17+
"/hi/" => Set {
18+
"SuperCoolNode",
19+
},
20+
},
1621
"trackedComponents": Map {},
1722
"trackedQueries": Map {},
1823
}

packages/gatsby/src/redux/reducers/__tests__/queries.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ it(`has expected initial state`, () => {
8888
"byConnection": Map {},
8989
"byNode": Map {},
9090
"deletedQueries": Set {},
91+
"queryNodes": Map {},
9192
"trackedComponents": Map {},
9293
"trackedQueries": Map {},
9394
}

packages/gatsby/src/redux/reducers/queries.ts

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const initialState = (): IGatsbyState["queries"] => {
2020
return {
2121
byNode: new Map<NodeId, Set<QueryId>>(),
2222
byConnection: new Map<ConnectionName, Set<QueryId>>(),
23+
queryNodes: new Map<QueryId, Set<NodeId>>(),
2324
trackedQueries: new Map<QueryId, IQueryState>(),
2425
trackedComponents: new Map<ComponentPath, IComponentState>(),
2526
deletedQueries: new Set<QueryId>(),
@@ -88,12 +89,8 @@ export function queriesReducer(
8889
for (const component of state.trackedComponents.values()) {
8990
component.pages.delete(queryId)
9091
}
91-
for (const nodeQueries of state.byNode.values()) {
92-
nodeQueries.delete(queryId)
93-
}
94-
for (const connectionQueries of state.byConnection.values()) {
95-
connectionQueries.delete(queryId)
96-
}
92+
state = clearNodeDependencies(state, queryId)
93+
state = clearConnectionDependencies(state, queryId)
9794
state.trackedQueries.delete(queryId)
9895
}
9996
state.deletedQueries.clear()
@@ -110,12 +107,12 @@ export function queriesReducer(
110107
}
111108
if (component.query !== query) {
112109
// Invalidate all pages associated with a component when query text changes
113-
component.pages.forEach(queryId => {
110+
for (const queryId of component.pages) {
114111
const query = state.trackedQueries.get(queryId)
115112
if (query) {
116113
query.dirty = setFlag(query.dirty, FLAG_DIRTY_TEXT)
117114
}
118-
})
115+
}
119116
component.query = query
120117
}
121118
return state
@@ -144,27 +141,18 @@ export function queriesReducer(
144141
case `CREATE_COMPONENT_DEPENDENCY`: {
145142
const { path: queryId, nodeId, connection } = action.payload
146143
if (nodeId) {
147-
const queryIds = state.byNode.get(nodeId) ?? new Set<QueryId>()
148-
queryIds.add(queryId)
149-
state.byNode.set(nodeId, queryIds)
144+
state = addNodeDependency(state, queryId, nodeId)
150145
}
151146
if (connection) {
152-
const queryIds =
153-
state.byConnection.get(connection) ?? new Set<QueryId>()
154-
queryIds.add(queryId)
155-
state.byConnection.set(connection, queryIds)
147+
state = addConnectionDependency(state, queryId, connection)
156148
}
157149
return state
158150
}
159151
case `QUERY_START`: {
160152
// Reset data dependencies as they will be updated when running the query
161153
const { path } = action.payload
162-
state.byNode.forEach(queryIds => {
163-
queryIds.delete(path)
164-
})
165-
state.byConnection.forEach(queryIds => {
166-
queryIds.delete(path)
167-
})
154+
state = clearNodeDependencies(state, path)
155+
state = clearConnectionDependencies(state, path)
168156
return state
169157
}
170158
case `CREATE_NODE`:
@@ -177,18 +165,18 @@ export function queriesReducer(
177165
const queriesByConnection =
178166
state.byConnection.get(node.internal.type) ?? []
179167

180-
queriesByNode.forEach(queryId => {
168+
for (const queryId of queriesByNode) {
181169
const query = state.trackedQueries.get(queryId)
182170
if (query) {
183171
query.dirty = setFlag(query.dirty, FLAG_DIRTY_DATA)
184172
}
185-
})
186-
queriesByConnection.forEach(queryId => {
173+
}
174+
for (const queryId of queriesByConnection) {
187175
const query = state.trackedQueries.get(queryId)
188176
if (query) {
189177
query.dirty = setFlag(query.dirty, FLAG_DIRTY_DATA)
190178
}
191-
})
179+
}
192180
return state
193181
}
194182
case `PAGE_QUERY_RUN`: {
@@ -213,6 +201,69 @@ export function hasFlag(allFlags: number, flag: number): boolean {
213201
return allFlags >= 0 && (allFlags & flag) > 0
214202
}
215203

204+
function addNodeDependency(
205+
state: IGatsbyState["queries"],
206+
queryId: QueryId,
207+
nodeId: NodeId
208+
): IGatsbyState["queries"] {
209+
// Perf: using two-side maps.
210+
// Without additional `queryNodes` map we would have to loop through
211+
// all existing nodes in `clearNodeDependencies` to delete node <-> query dependency
212+
let nodeQueries = state.byNode.get(nodeId)
213+
if (!nodeQueries) {
214+
nodeQueries = new Set<QueryId>()
215+
state.byNode.set(nodeId, nodeQueries)
216+
}
217+
let queryNodes = state.queryNodes.get(queryId)
218+
if (!queryNodes) {
219+
queryNodes = new Set<NodeId>()
220+
state.queryNodes.set(queryId, queryNodes)
221+
}
222+
nodeQueries.add(queryId)
223+
queryNodes.add(nodeId)
224+
return state
225+
}
226+
227+
function addConnectionDependency(
228+
state: IGatsbyState["queries"],
229+
queryId: QueryId,
230+
connection: ConnectionName
231+
): IGatsbyState["queries"] {
232+
// Note: not using two-side maps for connections as associated overhead
233+
// for small number of elements is greater then benefits, so no perf. gains
234+
let queryIds = state.byConnection.get(connection)
235+
if (!queryIds) {
236+
queryIds = new Set()
237+
state.byConnection.set(connection, queryIds)
238+
}
239+
queryIds.add(queryId)
240+
return state
241+
}
242+
243+
function clearNodeDependencies(
244+
state: IGatsbyState["queries"],
245+
queryId: QueryId
246+
): IGatsbyState["queries"] {
247+
const queryNodeIds = state.queryNodes.get(queryId) ?? new Set()
248+
for (const nodeId of queryNodeIds) {
249+
const nodeQueries = state.byNode.get(nodeId)
250+
if (nodeQueries) {
251+
nodeQueries.delete(queryId)
252+
}
253+
}
254+
return state
255+
}
256+
257+
function clearConnectionDependencies(
258+
state: IGatsbyState["queries"],
259+
queryId: QueryId
260+
): IGatsbyState["queries"] {
261+
for (const [, queryIds] of state.byConnection) {
262+
queryIds.delete(queryId)
263+
}
264+
return state
265+
}
266+
216267
function registerQuery(
217268
state: IGatsbyState["queries"],
218269
queryId: QueryId

packages/gatsby/src/redux/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export interface IGatsbyState {
210210
queries: {
211211
byNode: Map<Identifier, Set<Identifier>>
212212
byConnection: Map<string, Set<Identifier>>
213+
queryNodes: Map<Identifier, Set<Identifier>>
213214
trackedQueries: Map<Identifier, IQueryState>
214215
trackedComponents: Map<string, IComponentState>
215216
deletedQueries: Set<Identifier>

0 commit comments

Comments
 (0)