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.
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.
I also considered:
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).
As per usual, the rewrite took much longer than expected. And the haskell tooling itself let me down at times:
initINotifykernel 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).
My main aim was to cut costs, which it did:
Costs so far for NearlyFreeSpeech for 2017
|Historical Resource Usage|
|Historical Resource Usage|
|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.
I mentioned the cross-compilation issues above. Minus that, Haskell works well with NearlyFreeSpeech. Some issues/gotchas I have found with NearlyFreeSpeech have been:
Also, shoutout to Michael Snoyman. His blog post provided some encouragement in getting me to actually write this post.