Rewrite providedTags handling for better perf #4910
+145
−213
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Per #4909 , our recent update to add the missing
providesTags
handling forupsertQueryEntries
in #4872 has resulted in a significant perf regression for that use case.The current
api.provides
state structure looks like:When we update tags for a cache key, we first need to go in and remove that cache key from all tag entries that referenced it. Unfortunately, the existing logic is doing that via a brute-force search over all existing tag > cache key arrays. Not only is this O(n), but it's doing so by going through an Immer draft proxy, which is slower:
To make things worse, the
upsertQueryEntries
handling was doing this one cache key at a time. So, when we try to insert 5000 cache entries, we do the whole "painter's algorithm" anti-pattern and iterate through 1, 2, .... 4998, 4999 different tag > cache key arrays just to try to clear out any existing uses of each cache key.This PR restructures the invalidation state to be:
From there, we just save the per-cache-key array of tags in a second lookup table, which then means that deletion simplifies down to grabbing that array of tags and removing the cache key from the corresponding per-tag cache key arrays. So, instead of iterating over the entire list of all tags, we can just grab the few specific tags and target those directly. (To toss two CS-like bits of knowledge in here, we're "trading memory for speed".)
I also updated the
upsertQueryEntries
handling for tags to try to batch together all of the entries into one nested reducer call. Didn't appear to make much of a difference, but shouldn't hurt.In local testing, this knocked the runtime for inserting 5000 entries from 23 seconds down to 90 milliseconds. (Yeah. It was that bad.)
The downside is that this does change the structure of
state.api.provided
. We've always considered that internal, so this is not a public breaking change and can thus be a patch release.Unfortunately, the Redux DevTools RTKQ display does expect the existing state structure, and I've confirmed that trying to view a cache entry with this build causes the entire DevTools to crash when it accesses an undefined field due to the structure change.
So, we're going to need to update the DevTools to handle both cases and get that release out, then publish this fix.
The PR preview build here can be used as a workaround in the meantime.