Scaling down - operating low traffic applications efficiently
I absolutely love learning about operating applications at a large scale. Reading about great (and crazy) solutions developers have invented for handling millions of users a day is really exciting.
But perhaps, like me, your own applications aren't quite there. You might be running at a low scale and financing your project out of your own pocket and working on it in the evenings.
Maybe it's just not popular yet.
This article relates primarily to Ruby on Rails but the principles remain the same when applied to most other environments too.
I constantly come across warnings against "premature optimisation". It's sensible to spend more of your time fleshing out the application and adding features, rather than making small gains in page load times in the early days of the project.
But what if we're not just optimising for speed, but for cost efficiency? Or better still; what if we're optimising because we can?
There's little harm in learning and experimenting in performance tweaking on a low-traffic application (unless you're under heavy time constraints) and it's good practice for when you do hit it big. Days Out Near Me (currently my only live, personal app) was totally prematurely optimised; its traffic is low and I probably didn't need to implement streaming requests or heavy caching. But then it hit the front page of Hacker News and nothing bad happened. I'd already eliminated any of the large bottlenecks which could have brought it down when I was most eager for people to be trying it out.
I'm going to detail a few tweaks I've used in the past to help run applications at a low scale.
It takes very little effort to start implementing caching. Use Memcached and just shovel stuff into it; you'd be surprised at how little really needs to be loaded fresh every time (dependent on the application, obviously). There's a good article in the Rails guides to get you going and I've written before about the benefits of going deeper and using low level caching.
Memcached has a great feature for running on a budget; it will automatically expire the oldest records if it's running out of memory. This makes it very viable to use one of the free options on the Heroku addons when operating at low scale.
When I first launched Days Out Near Me I had a cron task running once per night to fetch and cache Flickr and DuckDuckGo data for each place and event. That worked fine for a few weeks until I was sending a few thousand requests out each night. I remedied it for a while with a hack; I set random cache timers between 1 and 3 days. That reduced the amount of requests being made each day, but it still wouldn't scale much further without getting me rate limited on those APIs.
Instead I moved all the queries for API data into jQuery.load requests pulling in the relevant areas of the view after the page had loaded. This is a (vastly simplified) technique used by applications like Facebook to improve the perceived speed of the site.
Here's a simple example; the following forms the basis for how a place page loads extra information from DuckDuckGo on DONM (e.g. Castle Drogo):
First we have the method on the model which returns the relevant information from the API for a record. This API data is cached for a few days as it's highly unlikely to change for historical properties:
Then we have a new method in the controller which returns the HTML for that information without a layout:
You need that new action in your routes:
And finally we want to load that content into the page. Here's a very simple example of how you could do that:
This also has the added benefit of failing invisibly for the user if something goes wrong with the API request, such as a timeout. If the ddg_info action throws an error it simply doesn't appear on the page.
Useful services & libraries
Cloudflare is fantastic and will really help your page loading speed performance. It's also free, which makes it even better.
Want to save yourself storage costs in combination with Cloudflare? Use Dragonfly for your file upload needs. Dragonfly generates thumbnails on the fly and relies on a caching proxy to store those generated versions. This means you don't have to store multiple versions of the same image; only the original. The best bit? It works with Cloudflare.
If you want to save some time in view rendering then give Slim a go.
For the purposes of this section I'll be using the Heroku terminology of web/worker/other processes.
When it comes time to deploying your application you'll no doubt be making the choice between managing your infrastructure yourself, or entrusting that to a Platform as a Service (PaaS) such as Heroku or Engine Yard. The primary resources you'll be concerned with differ between the two setups, though ultimately boil down to the total monetary cost:
On a traditional platform (VPS/dedicated server etc.) processes will cost you memory, CPU, and I/O.
A typical Rails environment rather likes its RAM and will abscond with copious amounts of it, depending on the application. You also don't want to be running so many processes that you max out your CPU or storage allocation.
On Heroku processes will cost you money
At $35 per month per process; you don't want to be running more than you need.
Unicorn, as I've mentioned before, will help you serve more requests with fewer processes/dynos. Make sure you check out the original article from railsonfire. For easier reference, here's my Unicorn config:
Processing certain tasks in the background can greatly improve the user experience by moving slower, blocking requests (such as sending an email or generating thumbnails) into a queue which is processed separately from the main web request.
If you're doing quite a bit of background processing you may want to look into Sidekiq. This is a late addition to this section as it has somehow completely passed me by; I only just found out about it via Sam Soffes' blog post about scaling Cheddar. Essentially it's a more efficient background processing library which is compatible with Resque. This should allow you to run fewer workers to handle the same background load.
One of the easiest options for scaling Rails workers on Heroku is the HireFire gem (also available as a hosted service). This hooks into your application and automatically launches workers (which will shut down on completion) when there is work to be done. Unfortunately there's currently no support for the Cedar stack in the gem, so if you're not looking to add another fee to your monthly bill (which kind of negates the point of this article) you could instead try the following alternative.
This example uses Resque as it's the one queue system I know of which supports this out of the box; I'd be happy to add methods for other libraries if you want to fork the example and let me know.
Resque takes an INTERVAL number as an environment option when you start up the rake task. This dictates how often the worker will poll Redis for new jobs. Looking through the source for Resque, however, I found the following in lib/resque/worker.rb:
So if we pass an interval of 0 to the rake task, the worker will automatically quit once there are no longer any tasks to process:
rake environment resque:work QUEUE=* INTERVAL=0
Great; your workers will now quit whenever they don't have something to do. That's just dandy.
Don't run workers; run one-off processes
When operating at low scale you probably aren't sending one email per second, and spending $35 a month (or another chunk of resources) to relieve a little bit of frontend latency is probably not something you're overly happy about. Instead you could run a worker as a one-off process via a cron task (scheduler on Heroku) every 5/10/some minutes and significantly cut your costs. You might want to do this from your own custom task which invokes the Resque task, so that you can check for existing workers before launching more, or log statistics etc.
If you're on your own server you'll want to add that command to your crontab, and for those on Heroku you can read up on how to use the scheduler in the documentation. The whenever gem is a nice way of managing your cron tasks with Ruby, if that tickles your fancy.
Some projects are probably not destined for high traffic and amazing success, but equally they aren't so limited in scope that they can be run from a freebie Heroku instance. You have to be willing to put some money into fully realising your idea. Maybe that idea won't work out and you'll have lost money on it, or perhaps you're on to something and your investment of time and money at the start helps you to fully realise your project; either way it's certainly no wasted effort to spend a little extra time optimising your application so that you get the most for your money.
I'm not advocating that everyone spends hours, days, or weeks performance tweaking their application from day one. But why not take it into consideration as you build? Performance tweaks themselves scale well; in low traffic applications they improve the user experience and save you money, and as your app grows in size it will lay the foundations for further improvements and give you a little room to breathe.
What tricks do you know for running an application on a shoe-string budget?