The tale of the loading spinner that would not spin

An exploration into Web Worker architecture and React/Redux performance tuning

Upthere
what’s up
5 min readNov 15, 2016

--

On our journey to create the new Upthere web application, we reached the point where a majority of the internal machinery was in place, working, and the data was flowing. Leveraging some of the latest tools in web development such as React and Redux, we were able to quickly move from idea to functional prototype. It was an exciting time to see our crazy little concept come to life.

There was just one little problem: our loading spinner would not spin.

I happen to love our loading spinner, to the point that if I had my way, it would fill up the entire screen. Seeing it frozen led us on a wild journey into performance tuning the application — all to see it spin in its magical dance.

Simplest Solution

I’m a big fan of looking for the easiest and dumbest solutions before sinking time into exploring more complex ones. In our applications, the loading spinner is not just a simple pre-rendered image. It is a complex animated canvas element that has the precise pixel values calculated and drawn each time. In the spirit of simple solutions, the obvious first test case was to try a small pre-rendered loading GIF.

Surprisingly, it just sat there. Lifeless.

This was actually an encouraging outcome. While it meant that things were more broken than we had expected, it gave us clues to where the culprit might be lurking. It ruled out any aspect of the code relating to the loading spinner itself being to blame. Specifically, it started to provide some evidence that the JavaScript thread was so busy working that it was unable to even render a visual update on the page.

As our web application performs a lot of sophisticated data computations to create a uniquely tailored experience for a user’s content. Those heavy calculations were a prime target to look at first to see if they were responsible for giving our spinner some stage fright.

Enter Web Workers

In an attempt to reduce the computational workload from the main thread, we implemented Web Workers into the application. Web Workers provide the ability to perform tasks and computation in a separate background thread, leaving the main thread free to handle more pressing issues and concerns.

There was a definite rush of excitement on our first successful trial using the Web Workers. The spinner was actually able to render a few stuttering frames of animation. Emboldened by our success, we set out to see what else could be improved to achieve further performance improvements.

Tuning React

Our next move was to look at a lifecycle method that is part of the React library called shouldComponentUpdate. Within a React component, it acts as the component’s gatekeeper deciding if an application change should cause the component to perform the work of updating itself. Every update that’s allowed through can have an expensive computational cost on a large application (like ours). Using this power, we disallowed the majority of updates to the application until we were assured our data processing had been completed and was ready to be rendered on the page.

The results from this exploration were massively beneficial as our loading spinner began to only drop about 10% of its animation frames. It also taught us a few new things about using shouldComponentUpdate. We found that the sooner you decided not to propagate a change, the larger the benefits to the application. As the component structures of React and the DOM are a tree, if you prevent a change at the top of the tree, the branches and leaves will never be the wiser that there was any cause for concern.

It’s important to point out that our aggressive use of shouldComponentUpdate did come with a few downsides. When adding in new functionality or features, there is the possibility that shouldComponentUpdate might block those updates from rendering on the page. This can cause developer frustration if they were unaware of that effect. They might expect the issue to be hidden within their code, but in reality it is just being blocked by shouldComponentUpdate.

Even with the dramatic improvements to the loading spinner after using Web Workers and use of shouldComponentUpdate throughout the application, we still encountered issues relating to large accounts. In fact in some cases, attempting to process all of the data was crashing the entire browser. There is something I find incredibly satisfying in completely breaking software. It helps to understand and appreciate the edges of what is possible and what is not. So while I was excited to be basically breaking everything, it was, realistically speaking, a sign that we had more work ahead of us!

Redux Optimizations

We decided it was time to start looking at our use of Redux within the application. Generally speaking, we have found Redux to be a pleasure to work with, though with its own limitations. Like any good tool, it takes a bit of time to get a good sense of its use and best practices.

In our initial implementation of Redux, we had been adding our processed data into the application store as it was available. As we examined this behavior, it became clear that each addition to the store had an exponential computational cost on each of the main React components. Considering we were making hundreds of these additions, it made sense that we were starting to hit the ceiling of calculations a modern browser is able to make. While computers are really fast these days, they still have limits.

Armed with this new insight, we took a step back to ask ourselves if it was really necessary to insert the data as it was being received. We wondered if we could introduce all the data in a single processed bundle that would be inserted into the Redux store at once. While this would mean another step of data processing, we had a new shiny weapon to tackle this in the form of our Web Workers.

This final optimization enabled us to go from fully browser-breaking behavior to the thriving, high-performance application we see today.

Most importantly, our loading spinner that started this whole crazy exploration now lives on spinning and spinning its beautiful dance.

__

Shanan Sussman
Software Engineer

Upthere Home is available for Android, iPhone, macOS, and Windows.

--

--

Upthere
what’s up

Our mission is to care for humankind's information.