Skip to content

Commit 9eb51e9

Browse files
authored
Rewrite providedTags handling for better perf (#4910)
* Restructure provided tags to improve deletion perf * Accept array of provided tag entries internally for perf
1 parent 48d874f commit 9eb51e9

File tree

6 files changed

+145
-213
lines changed

6 files changed

+145
-213
lines changed

packages/toolkit/src/query/core/apiState.ts

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { BaseQueryError } from '../baseQueryTypes'
33
import type {
44
BaseEndpointDefinition,
55
EndpointDefinitions,
6+
FullTagDescription,
67
InfiniteQueryDefinition,
78
MutationDefinition,
89
PageParamFrom,
@@ -15,14 +16,8 @@ import type { Id, WithRequiredProp } from '../tsHelpers'
1516
export type QueryCacheKey = string & { _type: 'queryCacheKey' }
1617
export type QuerySubstateIdentifier = { queryCacheKey: QueryCacheKey }
1718
export type MutationSubstateIdentifier =
18-
| {
19-
requestId: string
20-
fixedCacheKey?: string
21-
}
22-
| {
23-
requestId?: string
24-
fixedCacheKey: string
25-
}
19+
| { requestId: string; fixedCacheKey?: string }
20+
| { requestId?: string; fixedCacheKey: string }
2621

2722
export type RefetchConfigOptions = {
2823
refetchOnMountOrArgChange: boolean | number
@@ -225,18 +220,15 @@ export type QuerySubState<
225220
D extends BaseEndpointDefinition<any, any, any>,
226221
DataType = ResultTypeFrom<D>,
227222
> = Id<
228-
| ({
229-
status: QueryStatus.fulfilled
230-
} & WithRequiredProp<
223+
| ({ status: QueryStatus.fulfilled } & WithRequiredProp<
231224
BaseQuerySubState<D, DataType>,
232225
'data' | 'fulfilledTimeStamp'
233226
> & { error: undefined })
234-
| ({
235-
status: QueryStatus.pending
236-
} & BaseQuerySubState<D, DataType>)
237-
| ({
238-
status: QueryStatus.rejected
239-
} & WithRequiredProp<BaseQuerySubState<D, DataType>, 'error'>)
227+
| ({ status: QueryStatus.pending } & BaseQuerySubState<D, DataType>)
228+
| ({ status: QueryStatus.rejected } & WithRequiredProp<
229+
BaseQuerySubState<D, DataType>,
230+
'error'
231+
>)
240232
| {
241233
status: QueryStatus.uninitialized
242234
originalArgs?: undefined
@@ -274,18 +266,17 @@ type BaseMutationSubState<D extends BaseEndpointDefinition<any, any, any>> = {
274266
}
275267

276268
export type MutationSubState<D extends BaseEndpointDefinition<any, any, any>> =
277-
| (({
278-
status: QueryStatus.fulfilled
279-
} & WithRequiredProp<
269+
| (({ status: QueryStatus.fulfilled } & WithRequiredProp<
280270
BaseMutationSubState<D>,
281271
'data' | 'fulfilledTimeStamp'
282272
>) & { error: undefined })
283-
| (({
284-
status: QueryStatus.pending
285-
} & BaseMutationSubState<D>) & { data?: undefined })
286-
| ({
287-
status: QueryStatus.rejected
288-
} & WithRequiredProp<BaseMutationSubState<D>, 'error'>)
273+
| (({ status: QueryStatus.pending } & BaseMutationSubState<D>) & {
274+
data?: undefined
275+
})
276+
| ({ status: QueryStatus.rejected } & WithRequiredProp<
277+
BaseMutationSubState<D>,
278+
'error'
279+
>)
289280
| {
290281
requestId?: undefined
291282
status: QueryStatus.uninitialized
@@ -309,10 +300,13 @@ export type CombinedState<
309300
}
310301

311302
export type InvalidationState<TagTypes extends string> = {
312-
[_ in TagTypes]: {
313-
[id: string]: Array<QueryCacheKey>
314-
[id: number]: Array<QueryCacheKey>
303+
tags: {
304+
[_ in TagTypes]: {
305+
[id: string]: Array<QueryCacheKey>
306+
[id: number]: Array<QueryCacheKey>
307+
}
315308
}
309+
keys: Record<QueryCacheKey, Array<FullTagDescription<any>>>
316310
}
317311

318312
export type QueryState<D extends EndpointDefinitions> = {
@@ -346,6 +340,4 @@ export type RootState<
346340
Definitions extends EndpointDefinitions,
347341
TagTypes extends string,
348342
ReducerPath extends string,
349-
> = {
350-
[P in ReducerPath]: CombinedState<Definitions, TagTypes, P>
351-
}
343+
> = { [P in ReducerPath]: CombinedState<Definitions, TagTypes, P> }

packages/toolkit/src/query/core/buildSelectors.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,7 @@ export function buildSelectors<
199199
function withRequestFlags<T extends { status: QueryStatus }>(
200200
substate: T,
201201
): T & RequestStatusFlags {
202-
return {
203-
...substate,
204-
...getRequestStatusFlags(substate.status),
205-
}
202+
return { ...substate, ...getRequestStatusFlags(substate.status) }
206203
}
207204

208205
function selectApiState(rootState: RootState) {
@@ -344,7 +341,7 @@ export function buildSelectors<
344341
const apiState = state[reducerPath]
345342
const toInvalidate = new Set<QueryCacheKey>()
346343
for (const tag of tags.filter(isNotNullish).map(expandTagDescription)) {
347-
const provided = apiState.provided[tag.type]
344+
const provided = apiState.provided.tags[tag.type]
348345
if (!provided) {
349346
continue
350347
}

packages/toolkit/src/query/core/buildSlice.ts

Lines changed: 86 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,7 @@ export function buildSlice({
217217

218218
function writeFulfilledCacheEntry(
219219
draft: QueryState<any>,
220-
meta: {
221-
arg: QueryThunkArg
222-
requestId: string
223-
} & {
220+
meta: { arg: QueryThunkArg; requestId: string } & {
224221
fulfilledTimeStamp: number
225222
baseQueryMeta: unknown
226223
},
@@ -297,11 +294,7 @@ export function buildSlice({
297294
action: PayloadAction<
298295
ProcessedQueryUpsertEntry[],
299296
string,
300-
{
301-
RTK_autoBatch: boolean
302-
requestId: string
303-
timestamp: number
304-
}
297+
{ RTK_autoBatch: boolean; requestId: string; timestamp: number }
305298
>,
306299
) {
307300
for (const entry of action.payload) {
@@ -488,67 +481,66 @@ export function buildSlice({
488481
| ReturnType<ReturnType<InfiniteQueryThunk<any>>>
489482
>
490483

484+
const initialInvalidationState: InvalidationState<string> = {
485+
tags: {},
486+
keys: {},
487+
}
488+
491489
const invalidationSlice = createSlice({
492490
name: `${reducerPath}/invalidation`,
493-
initialState: initialState as InvalidationState<string>,
491+
initialState: initialInvalidationState,
494492
reducers: {
495493
updateProvidedBy: {
496494
reducer(
497495
draft,
498-
action: PayloadAction<{
499-
queryCacheKey: QueryCacheKey
500-
providedTags: readonly FullTagDescription<string>[]
501-
}>,
496+
action: PayloadAction<
497+
Array<{
498+
queryCacheKey: QueryCacheKey
499+
providedTags: readonly FullTagDescription<string>[]
500+
}>
501+
>,
502502
) {
503-
const { queryCacheKey, providedTags } = action.payload
503+
for (const { queryCacheKey, providedTags } of action.payload) {
504+
removeCacheKeyFromTags(draft, queryCacheKey)
504505

505-
for (const tagTypeSubscriptions of Object.values(draft)) {
506-
for (const idSubscriptions of Object.values(tagTypeSubscriptions)) {
507-
const foundAt = idSubscriptions.indexOf(queryCacheKey)
508-
if (foundAt !== -1) {
509-
idSubscriptions.splice(foundAt, 1)
506+
for (const { type, id } of providedTags) {
507+
const subscribedQueries = ((draft.tags[type] ??= {})[
508+
id || '__internal_without_id'
509+
] ??= [])
510+
const alreadySubscribed =
511+
subscribedQueries.includes(queryCacheKey)
512+
if (!alreadySubscribed) {
513+
subscribedQueries.push(queryCacheKey)
510514
}
511515
}
512-
}
513516

514-
for (const { type, id } of providedTags) {
515-
const subscribedQueries = ((draft[type] ??= {})[
516-
id || '__internal_without_id'
517-
] ??= [])
518-
const alreadySubscribed = subscribedQueries.includes(queryCacheKey)
519-
if (!alreadySubscribed) {
520-
subscribedQueries.push(queryCacheKey)
521-
}
517+
// Remove readonly from the providedTags array
518+
draft.keys[queryCacheKey] =
519+
providedTags as FullTagDescription<string>[]
522520
}
523521
},
524-
prepare: prepareAutoBatched<{
525-
queryCacheKey: QueryCacheKey
526-
providedTags: readonly FullTagDescription<string>[]
527-
}>(),
522+
prepare:
523+
prepareAutoBatched<
524+
Array<{
525+
queryCacheKey: QueryCacheKey
526+
providedTags: readonly FullTagDescription<string>[]
527+
}>
528+
>(),
528529
},
529530
},
530531
extraReducers(builder) {
531532
builder
532533
.addCase(
533534
querySlice.actions.removeQueryResult,
534535
(draft, { payload: { queryCacheKey } }) => {
535-
for (const tagTypeSubscriptions of Object.values(draft)) {
536-
for (const idSubscriptions of Object.values(
537-
tagTypeSubscriptions,
538-
)) {
539-
const foundAt = idSubscriptions.indexOf(queryCacheKey)
540-
if (foundAt !== -1) {
541-
idSubscriptions.splice(foundAt, 1)
542-
}
543-
}
544-
}
536+
removeCacheKeyFromTags(draft, queryCacheKey)
545537
},
546538
)
547539
.addMatcher(hasRehydrationInfo, (draft, action) => {
548540
const { provided } = extractRehydrationInfo(action)!
549541
for (const [type, incomingTags] of Object.entries(provided)) {
550542
for (const [id, cacheKeys] of Object.entries(incomingTags)) {
551-
const subscribedQueries = ((draft[type] ??= {})[
543+
const subscribedQueries = ((draft.tags[type] ??= {})[
552544
id || '__internal_without_id'
553545
] ??= [])
554546
for (const queryCacheKey of cacheKeys) {
@@ -564,48 +556,71 @@ export function buildSlice({
564556
.addMatcher(
565557
isAnyOf(isFulfilled(queryThunk), isRejectedWithValue(queryThunk)),
566558
(draft, action) => {
567-
writeProvidedTagsForQuery(draft, action)
559+
writeProvidedTagsForQueries(draft, [action])
568560
},
569561
)
570562
.addMatcher(
571563
querySlice.actions.cacheEntriesUpserted.match,
572564
(draft, action) => {
573-
for (const { queryDescription: arg, value } of action.payload) {
574-
const action: CalculateProvidedByAction = {
575-
type: 'UNKNOWN',
576-
payload: value,
577-
meta: {
578-
requestStatus: 'fulfilled',
579-
requestId: 'UNKNOWN',
580-
arg,
581-
},
582-
}
583-
584-
writeProvidedTagsForQuery(draft, action)
585-
}
565+
const mockActions: CalculateProvidedByAction[] = action.payload.map(
566+
({ queryDescription, value }) => {
567+
return {
568+
type: 'UNKNOWN',
569+
payload: value,
570+
meta: {
571+
requestStatus: 'fulfilled',
572+
requestId: 'UNKNOWN',
573+
arg: queryDescription,
574+
},
575+
}
576+
},
577+
)
578+
writeProvidedTagsForQueries(draft, mockActions)
586579
},
587580
)
588581
},
589582
})
590583

591-
function writeProvidedTagsForQuery(
584+
function removeCacheKeyFromTags(
585+
draft: InvalidationState<any>,
586+
queryCacheKey: QueryCacheKey,
587+
) {
588+
const existingTags = draft.keys[queryCacheKey] ?? []
589+
590+
// Delete this cache key from any existing tags that may have provided it
591+
for (const tag of existingTags) {
592+
const tagType = tag.type
593+
const tagId = tag.id ?? '__internal_without_id'
594+
const tagSubscriptions = draft.tags[tagType]?.[tagId]
595+
596+
if (tagSubscriptions) {
597+
draft.tags[tagType][tagId] = tagSubscriptions.filter(
598+
(qc) => qc !== queryCacheKey,
599+
)
600+
}
601+
}
602+
603+
delete draft.keys[queryCacheKey]
604+
}
605+
606+
function writeProvidedTagsForQueries(
592607
draft: InvalidationState<string>,
593-
action: CalculateProvidedByAction,
608+
actions: CalculateProvidedByAction[],
594609
) {
595-
const providedTags = calculateProvidedByThunk(
596-
action,
597-
'providesTags',
598-
definitions,
599-
assertTagType,
600-
)
601-
const { queryCacheKey } = action.meta.arg
610+
const providedByEntries = actions.map((action) => {
611+
const providedTags = calculateProvidedByThunk(
612+
action,
613+
'providesTags',
614+
definitions,
615+
assertTagType,
616+
)
617+
const { queryCacheKey } = action.meta.arg
618+
return { queryCacheKey, providedTags }
619+
})
602620

603621
invalidationSlice.caseReducers.updateProvidedBy(
604622
draft,
605-
invalidationSlice.actions.updateProvidedBy({
606-
queryCacheKey,
607-
providedTags,
608-
}),
623+
invalidationSlice.actions.updateProvidedBy(providedByEntries),
609624
)
610625
}
611626

0 commit comments

Comments
 (0)