Tuesday 10 September 2013

Building an assessment app in 2+ɛ days -- dealing with Heroku's slow Scala build times

(This post is retrospective -- students started using it yesterday.)

While Impressory is just running on an AWS instance, I put Assessory for our class on Heroku so that it would get an https address immediately *, and there wouldn't be a delay while I waited for DNS settings for a new domain name to propagate.

Heroku is a Platform-as-a-Service provider that uses a push to deploy mechanism. Add Heroku as a remote to the git repository, and then…

git push heroku master

…and Heroku will build and deploy your code.

This works for a number of platforms, including Scala Play apps such as this one.

In theory.

In practice, Heroku can be very slow building a Scala Play app, even one as simple as this, and it would regularly take longer than the 15 minute maximum that Heroku allows. In which case, Heroku would reject the push, and even though the update might only have been seconds away from going live, I'd be frustratingly bunted back to starting the deployment again.

The modular nature of the app, while great for keeping the code tidy, also seemed to slow down Heroku's builds as it has to go through an update cycle (resolving dependencies) for each of the modules as it compiles them. These seem to take a bit of time on Heroku.

Avoiding Heroku's long build times

The short answer to avoiding Heroku taking an age to compile an app (and often having to compile the compiler interface before it starts), it turns out, is this: don't let it compile it at all.

There's two ways of "not letting it".

  1. Apparently there's an alternative build mechanism for Heroku called Anvil.

    This uses Heroku's build packs on some other AWS servers. It seems to get around the timeout but still takes some time.

    To be honest, I didn't get time to look at it until later, as I needed to get an update up quickly. When I did try it out, while writing this post, it did work -- though it took its merry time too.

  2. The other is a hacky little workaround that involves almost no set-up, and is very much faster.

    Don't push the Play app itself to Heroku. Instead, publish it as a JAR file to a Maven or Ivy repository. Play apps can be published as JAR files just by running this from sbt:

    + publish
    

    This tends to be very fast because you've already compiled your code locally before you decide you want to upload it. (And your development machine is probably quicker than the AWS machines that Heroku builds on.)

    Then we create a second, essentially empty Play app that has our real app as its only dependency. We're treating our app as a library that's used by a trivial wrapper app. (We include only a very few files that we need to be a valid Play app: application.conf, plugins.sbt, build.properties. Perhaps one or two others, but they are straight copies of the files in our "real" app.)

    We push our wrapper app to Heroku, and Heroku will happily fetch our application code -- already compiled and packaged -- from the repository when it does its dependency resolution, as it would any other library. HTTP calls to the almost-empty outer app be served by the code in our JAR file -- including requests for the minified Javascript. Bingo, our app is up and running. Deployments take less than a minute because there is nothing for Heroku to compile.

    If you're not keen on pushing your code to a public Maven or Ivy repository, then you can push it to a local repository that you include in the almost-empty Heroku app.

This second approach feels like cheating, but in practice the only downside I've noticed so far is that public assets (images) from our "real" app would now be served out of the JAR file -- which is slower than serving them straight from a file if the app wasn't packaged up as library.

But in Assessory there are only three images that get served anyway -- the cartoon drawings on the NotFound, Forbidden, and InternalServerError screens. (And as it happens I messed up the URL so they're not appearing in the dependent-JAR-file version because of a double // in the path. The image below is from the un-JARed version in a previous post.)

This is one of the only pages in the app with an image

So I think for a while, I'm just going to put my few static images up somewhere else on the web -- perhaps in Cloudinary, and keep using this dependent app trick to make Assessory deployments fast.

* If a user accesses a site using http rather than https on a passwordless WiFi network (such as UQ's visitor WiFi), then it's possible to intercept their session key over the air. This is fairly well-known, and it's why Facebook, Google and others have moved to https only. For Assessory, where students are marking each other, I'd like to ensure that an https URL is available.

No comments: