Not the Answer

A blog

Migrating a REST API from Javascript to Haskell

Background

For just over a year, I’ve been running the JTime project, which consists of a REST API, an Android app and a website, the latter two of which talk to the REST API. The JTime project allows me to see nearby prayer times. I don’t advertise it, as it is more of an educational project to try new technologies, and I just don’t want to be promoting it in case people think I’m clever for having made it (I don’t like showing off, and generally the people around me are non-programmers). So barely anyone uses it.

Initially, the REST API was written in Javascript, using the loopback framework which helped me to get moving fast and gave a good structure to the code. This api was run on Digital Ocean.

Fast-forward many months, and my DO free student credit was running out, so I looked for the cheapest place to host the api. After browsing around, NearlyFreeSpeech seemed like it could be the cheapest option. So I switched hosts.

After switching, I noticed that running the server was costing me more than I’d like. This was because the rest api consumed too much memory (~200MiB including a postgres instance), which cost me a bit too much money (NFS (NearlyFreeSpeech) charges directly based on CPU, RAM, disk and bandwidth usage). Loopback is a bit RAM-heavy, which I hadn’t had to think about before.

So, to save money, I decided to rewrite the api in Haskell.

Why Haskell?

  • I’ve used it before, so I can get moving faster.
  • It’s a functional programming language. Since learning Haskell I’ve started to fall in love with FP.
  • It can produce small and fast binaries. Not always, but I’ve so far found this to hold true.
  • The type system is awesome (although no dependent types yet).

I also considered:

  • Rewriting in Rust, but the ecosystem is still very new and big changes seemed to still be being made. And it would take time to get up to speed with it (I started reading some Rust tutorials before deciding it would take too long to learn).
  • Using a smaller js framework, as I’d be able to reuse code. But the amount of code that would have to be rewritten anyways meant that I felt it would be worth just switching languages anyways. And AFAICT the issue with Loopback was more that the amount of stuff it was doing, and that it was a js framework (so interpreted with a JIT) meant it took up a bit of ram. I’d essentially be reconstructing a stripped-down version of loopback, and would still have the overhead of the interpreter and JIT.

I also decided to use Servant for routing, which in hindsight was a very good decision (IMO they’ve basically figured out how to define a rest api). If you haven’t heard of it, look into it. It’s really good. In a nutshell, you specify your API as a type. Servant then automagically does routing, serialisation/deserialisation and validation (ok, there is some stuff you need to do, but not too much).

How the rewrite went

As per usual, the rewrite took much longer than expected. And the haskell tooling itself let me down at times:

  • GHC takes ages to compile. Like absolutely ages, especially on older hardware. Compiling from scratch could take over an hour, and each incremental compilation afterwards would take at least a minute, even for minor changes. Using Emacs with Intero helped somewhat to speed up development (though Intero is quite RAM heavy, which slowed it down a bit). But CI builds still take 15-45 minutes.
  • The record types naming issue is annoying. I could have used lenses, but I didn’t want my builds taking even longer.
  • Stack uses way too much disk space. And that’s after setting the option to use the system GHC. (Yes, I’m aware it’s 2017, but that doesn’t mean everyone is running the latest Macbook with plenty of disk space and ram).
  • The server was going to be run on nearly free speech, which uses freebsd. This meant that I either needed to compile on freebsd or cross-compile. Cross-compilation doesn’t seem to be used much, and those who were doing it seemed to have trouble doing it, especially with template Haskell. This meant I needed a freebsd machine. The only way I managed to do this was to run one in a VM. But this was a pain as it restricted what machines I could compile for production on. It also meant I couldn’t do the compilation in gitlab ci (well, I think it is possible, but it would involve running freebsd using kqemu, which someone has managed to do on Travis ci, but I couldn’t get this to work on gitlab ci). In the end, I randomly noticed that NFS had a ubuntu1604 beta realm that isn’t advertised anywhere. There were some random issues with this (eg website going down unexpectedly, filesystem watching not working (as in calling the initINotify kernel function thing would fail)) but it meant I could compile and deploy from Gitlab CI without jumping through cross-compilation hoops.

Also, I underestimated just how lax loopback was regarding the input it would accept. Servant is very strict, which meant I had to add some code to deal with various types of input that loopback would have just happily accepted. I don’t see this as an issue with either library, more that they take different decisions about how strict to be about the kinds of input that are accepted.

Deploying the new server was a bit of fun. I planned and rehearsed the whole sequence, before switching off the old server, migrating the data across (using a Python script and sqlalchemy) and reconfiguring DNS entries (the new server was switched on beforehand to save time).

Results

My main aim was to cut costs, which it did:

Category March April May June July August Total Average
Deposit Fee $1.00 $0.00 $0.00 $0.00 $0.87 $0.00 $1.87 $0.31
Resource Charge $0.53 $5.34 $5.50 $5.29 $1.83 $1.26 $19.75 $3.29
Site Charge $0.03 $0.30 $0.31 $0.35 $0.35 $0.28 $1.62 $0.27
Storage Charge $0.02 $0.08 $0.09 $0.08 $0.13 $0.12 $0.52 $0.09
Total $1.58 $5.72 $5.90 $5.72 $3.18 $1.66 $23.76 $3.96

Costs so far for NearlyFreeSpeech for 2017

Historical Resource Usage
Month RAUs
2017-03 940.62
2017-04 9185.87
2017-05 9479.13
2017-06 9096.4
2017-07 1433.64

Resource usage for the old Javascript server

Historical Resource Usage
Month RAUs
2017-06 25.86
2017-07 1708.9
This Month (so far) 2214.54

Resource usage for the new Haskell server

Yeah, I know, the amount of money involved is peanuts, but I wanted to make this recurring bill as small as possible (and tbh, 4-5 cents a day sounds pretty decent to me). And I’m now down to ~57 Mib (including an nginx instance as I was having issues with serving static files with Haskell) although CPU usage is periodically spiking to 1%.

As with most rewrites, the new code is probably better structured. As it’s Haskell, it is less verbose. The Haskell code runs pretty fast too, but Phoenix, Arizona (where NFS have their servers) is an ocean away from me, so there is more latency than with the old server due to the speed of light.

Sidenote on using NearlyFreeSpeech with Haskell

I mentioned the cross-compilation issues above. Minus that, Haskell works well with NearlyFreeSpeech. Some issues/gotchas I have found with NearlyFreeSpeech have been:

  • No sudo. Only the software installed can be used (quite a bit is installed though). Anything else can only be used if you do a local non-sudo install. Expect to have to compile stuff from scratch if you can’t find a binary.
  • NFS seems to have been initially designed for PHP apps, with support for running custom processes added later. This shows in places in their control panel.
  • Automated deployments are a bit trickier to set up, as there is no API for restarting a process. This means you need some sort of master process that runs your server and watches (or polls) the filesystem for some kind of change that then triggers restarting/reloading the server. Your automated deployment script then just ssh’s into NFS and modifies the filesystem as necessary.
  • Networking between processes (eg between an app server and a database) isn’t done using localhost addresses, but using http://yoursitename.local . Don’t ask me why.
  • NFS is very much a DIY service. Expect to get your hands dirty. They do have a support service, or you can just post in their forum or check their wiki (both of these are for members only, and perhaps unsurpisingly are both PHP apps).

Also, shoutout to Michael Snoyman. His blog post provided some encouragement in getting me to actually write this post.

30 Aug 2017 #Haskell #Javascript #JTime #Programming