Implementing Turbolinks and Russian Doll Caching

Implementing Turbolinks and Russian Doll Caching in Tick

Every six-weeks, our team at our parent company Higher Pixels, starts a new work cycle. The whole team pitches ideas, features, and upgrades and then we pick the best ones to work one. This last cycle we decided to focus on performance upgrades for Tick.

The idea to focus on speed didn't come out of thin air. We read over 1,000 customer reviews on Capterra, read tons of great customer requests, and researched recurring issues in support. One thing that stuck out was that our customers wanted speed! (And not the amphetamine sulfate kind. Hopefully.)

We specifically wanted to address speed issues for our long-time customers, many of whom had been with us for over 11 years. But with an app running original code, we had to address some technical debt. Most pages weren't designed to display thousands of projects that many of our original customers now had. Not to mention the associated time entries, and calculations to provide budget feedback.

While this is all valuable data, it came at a huge cost to performance.

The first step was identifying how we could redesign these pages with performance in mind. In this day and age, everyone is moving to the new client-side approach with Node.js, AngularJS, React, etc. The benefit of a client-side approach is that you can speed up the apps and maintain the MVC architecture that the developer community loves.

But we're a small team, and there wasn't any way we could rebuild the main web app with a client-side framework in 6-weeks. And it would also mean we'd have to rebuild our other apps—dramatically increasing the complexity of our code. It would require diving into the web app's code and framework as well as Swift for iOS and Java (or maybe Kotlin) for Android. Pretty soon we'd have more coding languages than developers on our team! We needed something simpler.

To speed up development, we utilize hybrid apps for iOS and Android. Hybrid applications allow us to quickly design and develop web pages that can be deployed every day. Now we can push updates to our mobile, Android, and iOS apps all at once.

No more compiling and resubmitting apps to their respective stores only to catch a bug or find something else we'd like to update. The hybrid approach put our development team in control.

While this improves our development rate, it does come with a cost to speed. The good news is that it allows all of our apps to benefit from any performance solution we found for Ruby on Rails.

With this in mind, we decided to focus on adding Turbolinks to Tick.

Turbolinks works by only fetching a portion of a new web page, rather than the entire page. Turbolinks can do this because it doesn't request resources that it has already loaded like images, Javascript, and CSS code.

By focusing our efforts on Turbolinks, we can utilize our current code and only need to ensure our unobtrusive javascript is delegating the responsibility from the root document. The implementation took about two days of developer time to roll it out to all our apps!

We could feel the effects immediately, and it made using Tick more fun. An ancillary benefit was that as page load time was now primarily a function of the content needed, we identified other pages that had slow unoptimized queries. You could literally feel that something was wrong when you went to a page because it was slower. In the past, these queries were lost in the noise of slow page loads, but with Turbolinks there was nowhere to hide!

Turbolinks implementation problems

Implementing Turbolinks and optimizing the queries led to a speed improvement of about 500ms per page load. In fact, the improvements feel even faster to the user because Turbolinks is also caching your HTML and displaying that as it gets the updated page from the server. This gives the user a very 'native' like experience.

After implementing Turbolinks and optimizing queries, we still have pages that were slow due to the amount of data required—something Turbolinks couldn't solve. The data requirements required a different approach, so we looked into Russian doll caching.

Russian Doll caching is the technique of nesting fragment caches to maximize cache hits. It ensures high reusability of your cache fragments. For Tick, the slowdown was coming from the budget status feedback. By adding caching around each status, we were able to remove the repeated queries necessary to show that data. Now whenever anyone views a project's budget status, it gets saved as a fragment in the cache server and is then used whenever any user views that project's budget status. That means when the first person views the budget status, all the queries needed runs once and then doesn't have to run again until someone does something changing the budget status. This caching technique creates much faster pages and helps free up resources on our server. Now Tick users can view thousands of projects with their budget status without the experience of a slow page.

It's hard to quantify this improvement since there is such a variety in how different companies use Tick. Some of our oldest customers saw pages that use to take 3-5 seconds to load drop to less than a second, while customers with just a few projects would see an improvement of about half a second. In the world of optimizations, these are both dramatic gains and were honestly relatively easy to implement by using Turbolinks, query optimization, and Russian doll caching.

Our thanks to everybody who reviewed Tick or wrote into support to tell us how we could make Tick better for your business.