Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(graphql): Reuse request results to improve performance #1452

Merged

Conversation

kvenn
Copy link
Contributor

@kvenn kvenn commented Aug 4, 2024

Problem

If you have two observables for the same request, it will unnecessarily do extra (expensive) work

  • maybeRebroadcast will diff, denormalize, and then require parsing again
    • This is more substantial since this gets fired on every network response and cache write
  • fetch(policy.cache) will need to denormalize and require parsing again
    • This will cause a flash of delay when attaching to an observable that already has data for it
  • ex: two observables of the GetUser query for the same resource (id: xyz)

Solution

I went with a solution that doesn't require changing any core flows or data structures. Slightly based on how Ferry keeps a map of requests.

The more observables you have for the same Requests, the bigger improvements you will see

  • _getQueryResultByRequest - Instead of introducing a new cache, we iterate through the queries array to find if any queries match the Request
  • When we maybeRebroadcast for a fetch from cache, we check this first
  • The new copyWith on QueryResult lets us re-use the bulk of the existing query result (including the parsed data)

Other improvement added - call maybeRebroadcast less

  • Don't call it on failures (since the cached data didn't change)
  • This requires setting carryForwardDataOnException: false in your Options. I've found that behavior to match my expectations for failures

Addendum

  • I think the broader issue (and motivation for this) was that maybeRebroadcast uses a brute force approach to notifying of changes and relies on synchronicity (which definitely has its pros - like readability and reducing bugs). Which has led to jank/missed frames in our application
  • There are small improvements that could be made like
    • Throttling/debouncing/queuing calls to maybeRebroadcast (based on this)
    • Memoizing the normalization (since that is 80% of the remaining cost of maybeRebroadcast)
  • A short term solve would be making maybeRebroadcast (and normalize) async so at least it isn't holding up the main thread the entire time (atomically)
  • The other option would be allowing the entire GraphQL client (and it's cache) to live in an isolate. 80ms every call to maybeRebroadcast is only unacceptable because it's on the main thread. It'd be fine on a separate thread.

@kvenn kvenn changed the title feat(graphql): Reuse request results feat(graphql): Reuse request results to improve performance Aug 4, 2024
Copy link
Collaborator

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks

@vincenzopalazzo vincenzopalazzo merged commit b365a83 into zino-hofmann:main Aug 5, 2024
4 checks passed
@kvenn kvenn deleted the reuse-request-results-zino branch August 5, 2024 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants