Image courtesy of Flickr user https://www.flickr.com/photos/sortacrunchy

In the olden days, software used to come in a box. Now, we can make sure our users always have fresh baked software.

Fixing a bug involved making new media, mailing them out, and somehow getting customers to install the new release. Even now, when updates can be easily and automatically downloaded, it can be a struggle to get customers to run the latest and greatest.  Engineering would rather be working on an exciting new feature than trying to debug an old version.  Our customers use our software because they want to make their lives easier; they aren’t interested in our internal versioning scheme.  They want to tell their customer they’ve patched that hole in the wall; they don’t want to download a patch and restart.  Through the magic of the SaaS (software as a service) model, we can make sure everyone is always running our very best software.

The user-facing part of Gridium’s Tikkit software is an Ember application. It lives in the cloud; specifically, in an AWS S3 bucket. When a user types their Tikkit URL into the browser, they download the application as a package of minified, gzipped JavaScript code.  When they click on links within the application, they load just the new data that they need; they don’t re-download the whole application. The application responds quickly, because it’s already loaded, and just fetching a small amount of fresh data (usually less than 10k). Yay modern software architecture!

But wait! An Ember app is a single-page application. Once a user has loaded the application, they don’t need to load it again. They can leave their browser open for days or weeks, and it will continue doing its job: request data, display data. So then what happens when a Gridium engineer fixes a bug or adds a new feature? How do we make sure the user installs our latest and greatest? The short answer is we tell their browser to do it for them.

We use CircleCI to build our deployment package. After running an ember build, we save the Circle build number to a build.txt file, zip everything up, and save it as a build artifact. A deploy fetches the package from Circle and puts in an S3 bucket. (Read all about it in my Deploying Ember Apps to S3 blog post.) The build.txt has Cache-Control set to no-cache, so that users will always fetch a fresh copy. Here’s the relevant section of our circle.yml file:

deployment:
  prod:
    branch: /.*/
    commands:
      - node_modules/ember-cli/bin/ember build --environment=production
      - echo $CIRCLE_BUILD_NUM > dist/build.txt
      - tar czvf $CIRCLE_ARTIFACTS/dist.tar.gz dist

Each deployed build includes a build.txt to identify it. Next, we need to tell the end user’s browser to do something with that information. I wrote an Ember mixin to do this.

checkVersion method compares the current time agains a versionCheckDue property in local storage.
– if it’s time to check the version, it loads build.txt (always the latest and greatest, since Cache-control says no-cache).
– it sets the next version check to 1 day in the future
– it checks the user’s current version, stashed in local storage
– if there isn’t a current version, it sets the just-loaded version as current
– if the user has a different version, they reload to get a fresh one.

Here’s the code:

    checkVersion: function() {
        var due = localStorage.getItem('versionCheckDue');
        var now = moment();
        // X = Unix timestamp, in seconds (1360013296)
        if (due && moment(due, 'X').isAfter(now)) {
            // not expired yet
            return;
        }
        // get deployed version
        this.get('api').ajaxWithoutAuth({url: '/build.txt'}).then((response) => {
            let availableBuild = response.trim();
            let build = localStorage.getItem('build');
            now.add(1, 'days');
            localStorage.setItem('versionCheckDue', now.format('X'));
            if (!build) {  // first load
                localStorage.setItem('build', availableBuild);
                return;
            }
            if (build !== availableBuild) {
                localStorage.removeItem('build');
                // get fresh version of the app!
                location.reload();
            }
        });
    }

This mixin is defined in an addon that we use in several related projects. To actually do the version checks, we include the addon, then run the check version method in the beforeModel on the application route:

import VersionCheck from 'huyang-common/mixins/version-check';

export default Ember.Route.extend(VersionCheck, ApplicationRouteMixin, {
    beforeModel: function(transition) {
        this._super(transition);
        this.checkVersion();
    },

On every route change within the app, we check the version. Usually, this is only super-fast comparison: is it time to ask what’s new? If it is, we asynchronously fetch the latest version number. If there’s something new, the browser requests a whole new app, along with the data to display.  This runs in beforeModel because at that point:

  • the browser has the new target URL (for example, /request/123)
  • the user is allowed to see this URL (_super redirects if not)
  • the browser has not yet requested any new data (specified in the model hook)

A reload makes the browser load the whole application again, along with any supporting data.  It’s exactly the same as if the user had pasted the URL directly into their browser.  As a result of clicking on something in the app, the user now has the latest version of the application, and can continue working without interruption.

We don’t tell the user they’re doing it wrong. We don’t interrupt their workflow and make them go do something else that they’re not even sure they want. We figure out if we’ve got something new and improved, then serve it up. Who wants day-old software? Fresh baked is so much better!

About Kimberly Nicholls

Kimberly Nicholls is a full stack engineer at Gridium who loves to make data useful. She also enjoys reading books and playing outside, especially in the water.

You may also be interested in...

Measuring Aurora query IO with Batch experiments

Running production-scale tests with AWS Batch showed that using SQLAlchemy to modify only changed rows reduced our Storage IO costs, as well as the accumulation of dead tuples in our database.

Migrating to Aurora: easy except the bill

Pay for usage sounds good, but how you use the database is key. Migrating our production database from Postgres to Aurora was easy, until we noticed that our daily database costs more than doubled. Our increased costs were driven by storage IO, a type of usage we’d never before optimized or even measured. We resized… Read More