Production Haskell

January 14, 2016

These are my notes from Reid Draper's presentation Production Haskell at YOW! 2015.


This talk was originally formerly Wrangling the Internet of things with Haskell, but changed in scope to simply Production Haskell.

In Reid's experience:

Of all of the projects I've worked on, our Haskell codebase has had the lowest defect rate with the same amount of effort of any project I've worked on.

It's also been the easiest to refactor of any language I've used.

Outline of this talk


$ stack build

Stack uses three layers of packagers, two of which are immutable:

This lets us spread our projects into many smaller projects efficiently.

Stack can use GHCi to very quickly/iteratively develop code, e.g. tweak a Web app, see the changes in the browser. See ordeal for more.

When we run stack build, we get a self-contained executable called app. This obviates the need for Haskell build infrastructure in the deployment environment.


Given a compiled project, which gives us a self-contained binary called app, some assets, etc.:

├── assets
│   ├── images
│   │   └── favicon.ico
│   ├── javascript
│   │   └── site.js
│   └── stylesheet
│       └── style.css
└── bin
    └── app

All we need to do is create a tarball of the app/ directory, name it with a short git SHA, and copy it to a deployment environment. Then we deploy it via:

$ tar -xzf app-370b347.tgz
$ ln -s app-370b347 current
$ restart app

The only mutation here is changing the current symlink, so we can rollback in seconds by reverting the symlink. Deployment is fast. Redeployment is fast. Rollback is fast.

In production, under moderate load, we see usage of about 60MB of RAM.



An example property:

propEvent :: Event -> Bool
propEvent a = Just a == decode (encode a)

The first time trying this, it failed due to a timestamp:

ghci> t
1894-02-22 00:00:04.5986 UTC
ghci> Data.Aeson.encode t

We lose a decimal place when encoding to JSON.

This is just some small subcomponent of our larger event; this is the kind of bug that, had we written manual tests for encoding and decoding this large thing, it's very unlikely that we would have come up with this example.

Even for relatively high-level tests we're able to find bugs that are really subtle in subcomponents in our data structures.


The problem that we want to solve is to be able to write reusable, composable queries that can be used within a transaction.

There's a short dive into postgresql-transactional here.