question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Apollo client executes for 1000 milliseconds on React-Native worker thread during single mutation

See original GitHub issue

Intended outcome: Have a performant React Native application that performs mutations without locking up the JS thread.

Actual outcome: Apollo seems to block the main JS worker thread for ~1000ms any time I perform a mutation. It’s obviously doing lots of things, most of which seem to have to do with trying to read and write the cache, but it’s unclear to me why it would be necessary to do all this work for a single mutation, and why it is necessary to completely block the thread while doing so.

The following screenshot shows the “after-effects” of a single “updateJob” mutation. I’m using cache-and-network, which does mean two responses to the mutation, but the responses to the mutation don’t come until the last 100 ms of this trace.

Screenshot 2019-04-02 21 50 19

This is an example of the data item being updated, as returned from the mutation. example_job_pp_json.txt

How to reproduce the issue: Unfortunately I’m not clear on what, exactly, is going on in the bowels of the Apollo code, so I’m not sure how to reproduce it outside my own application. I’m simply executing a mutation and then watching as my app locks up for nearly a second. I’m hoping that these profiling screenshots will make it possible for someone who understands the codebase to at least theorize about it.

Versions System: OS: macOS 10.14.4 Binaries: Node: 8.11.3 - ~/.nvm/versions/node/v8.11.3/bin/node Yarn: 1.13.0 - ~/workspace/VisionNxMobile/node_modules/.bin/yarn npm: 6.2.0 - ~/.nvm/versions/node/v8.11.3/bin/npm Browsers: Chrome: 73.0.3683.86 Firefox: 65.0.1 Safari: 12.1 npmPackages: apollo: 2.4 => 2.4.4 apollo-client: ^2.4.13 => 2.4.13 react-apollo: ^2.2.4 => 2.3.2

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:11
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
petergaultneycommented, Apr 3, 2019

we just upgraded to React Native 0.57 from 0.56, which anecdotally seemed to make things worse (and is when we started digging in to this). But I have no real reason to believe that anecdote.

We only call the updateJobMutation once, but we do have an update callback on that that is doing some manual cache updating that someone else wrote and that I suspect we don’t need. However, I’ve also tried pulling those out and the overall slowness still exists. I’ll see if I can find that branch and pull out a profile of the code without that extra update method.

              updateJobMutation({
                variables: inputData,
                optimisticResponse: {
                  updateJob: {
                    __typename: 'Mutation',
                    ...jobOptimisticResponse,
                  },
                },
                update: (dataProxy, { data: { updateJob }, errors }) => {
                  if (errors || !updateJob) {
                    captureMessage('AddDocumentation cannot update cache', {
                      errors,
                      updateJob,
                    })
                    return
                  }

                  // I suspect these force-updates to the cache aren't necessary
                  let data = { getJob: {} }
                  try {
                    data = dataProxy.readQuery({ query: GetJobQuery, variables: { id: job.id } })
                  } catch (err) {
                    console.log('Error reading existing job from cache for detail updates')
                    captureException(err)
                  }
                  data.getJob = { ...data.getJob, ...updateJob }
                  dataProxy.writeQuery({ query: GetJobQuery, variables: { id: job.id }, data })

                  data = { listJobs: { items: [] } }
                  try {
                    data = dataProxy.readQuery({ query: ListJobsQuery })
                  } catch (err) {
                    console.log('Error reading jobs list from cache')
                    console.log(err)
                  }

                  const filteredJobItems = _.filter(
                    data.listJobs.items,
                    item => item.id !== updateJob.id
                  )

                  filteredJobItems.push(updateJob)
                  data.listJobs.items = filteredJobItems
                  dataProxy.writeQuery({ query: ListJobsQuery, data })

                  FileSystemService.jobs = filteredJobItems
                  FileSystemService.cleanMedia().catch(err => console.log(err))
                },
              })

Yeah, the user-facing issue is definitely the synchronicity, though obviously it would be nice to consume less battery than we are. The thing is, we don’t get much advantage from Apollo’s bells and whistles (particularly the normalization), since we only deal with our data objects in business logic at a high level, as there are only meaningful IDs (from a backend perspective) at the top (Job) level. So partly I just wish we could turn off all the normalization and extra work and use the cache as a simple key-value store.

In the meantime, though, we guessed that having our major ListJobs-consuming component querying for the entire Job (which is simpler) might be causing Apollo to do extra work, so we did a manual prefetch of the Jobs and then changed that component to only request the specific fields it needs. That reduced our total JS Apollo runtime to about 400 ms, as seen in the following profile.

Screenshot 2019-04-03 13 52 04

As you can see, the render portions are still less than half of the ~400ms overall runtime.

Here is a comparison profile of the same operation, but taken with the Hermes cache, where you can see that all but about 40ms of the synchronous call is taken up by our own (admittedly very unoptimized) render performance.

Screenshot 2019-04-03 13 53 20

0reactions
hwillsoncommented, Apr 27, 2021

If anyone can provide the reproduction mentioned in https://github.com/apollographql/apollo-client/issues/4658#issuecomment-481036943, we’ll take a closer look. Thanks!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mutations in Apollo Client - Apollo GraphQL Docs
The useMutation React hook is the primary API for executing mutations in an Apollo application. To execute a mutation, you first call useMutation...
Read more >
Overcoming single-threaded limitations in React Native
We'll take a look at certain features that limit React Native's performance ... There are three threads that mainly run a React Native...
Read more >
Gatsby Changelog | 5.3.0
Now, when a code update or data update is made for the <Header> component, only the HTML for the Slice will be updated,...
Read more >
Search Results - CVE
1, remote code execution can be achieved through user-submitted Jinja2 template. The REST API endpoint for validating device configuration files in lava-server ...
Read more >
GraphQL on the client side with Apollo, React, and TypeScript
This article aims to be an introduction to the Apollo Client. It gives an overview of its features while providing examples with TypeScript....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found