Nick Malcolm









Making a Rails Health Check which doesn't hit the database

Note: This post was written for and originally published on the ThisData blog in 2016. Ages ago! I have reposted it here in case it's useful in the future.

In a production application you usually have many servers, and each of those servers get checked periodically to make sure they’re still alive. If they are, then requests can be routed at them, for example by a load balancer. If a server doesn’t respond to the healthcheck, then it is presumed to be dead or unhealthy, and requests are sent to the healthy servers instead. When your production environment uses automated scaling, servers can be killed and rebooted when they’re unhealthy too.

In a Rails app, a health check might look like this:

class HealthcheckController < ApplicationController::Base
  def alive
    render json: { ok: true , node: "I'm alive!"}

When the healthcheck is called, it will hit the DB by checking for the first User record, and if that works, send a successful response. This is advantageous because if it can’t talk to the database, then that server isn’t going to be much use to your users. Most of the time that’s true.

But if your database goes down, all of your servers will go down too, because none of them can access the database. If you have autoscaling, all of your servers will continuously be torn down and set up.

You might think that all you have to do is remove the database call, and then your healthcheck will pass. Unfortunately this isn’t true, because of Rails’ default Middleware. Middleware are bits of code which a request is passed through before it hits your app proper. You can see a list of Ruby on Rails’ default Middleware stack here.

Have a go! Shut down your local database, and see what happens when you hit your healthcheck. For me on Postgres I get

PG::ConnectionBad could not connect to server: Connection refused Is the server running locally and accepting connections on Unix domain socket “/tmp/.s.PGSQL.5432”?

Here’s why. One of those pieces of Middleware is ActiveRecord::QueryCache (view the source here). That middleware is executed on every request, before that request even makes it to the controller action. It connects to the database and enables a caching layer in ActiveRecord. So when a request comes in, and your database is down, this middleware will throw a big error. Since your healthcheck is way up further in the stack, in a controller action, it never gets a chance to run!

To get around this, you’ll need to create your own middleware which comes before ActiveRecord::QueryCache.

Side note: if you have a seperate marketing site which has its own seperate healthcheck, then it should hopefully stay up, and you might not need to create a healthcheck which doesn’t hit the database.

Implementing a Ruby on Rails middleware healthcheck

class MiddlewareHealthcheck
  OK_RESPONSE = [ 200, { 'Content-Type' => 'text/plain' }, ["I'm alive!".freeze] ]

  def initialize(app)
    @app = app

  def call(env)
    if env['PATH_INFO'.freeze] == '/healthcheck'.freeze
      return OK_RESPONSE

Save that in your app to /app/middleware/middleware_healthcheck.rb. In your /config/application.rb file, add the following line:

config.middleware.insert_after "Rails::Rack::Logger", "MiddlewareHealthcheck"

Now when your app starts Rails will add our new healthcheck middleware after initializing the logger, which also happens to be ahead of QueryCache. The middleware will “capture” any requests to /healthcheck and immediately return a 200 text response with “I’m alive!” in the response body. No more database calls!

In the code we’ve frozen some strings to reduce object allocation, which is handy for frequently called bits of code. I’ve called it MiddlewareHealthcheck because, well, it only checks that a request reaches the Middleware layer, and that’s it! You should also add a comment to your routes.rb file, so that there’s less chance for confusion.

So there we have it! A simple middleware for Ruby on Rails which intercepts healthcheck requests so that you don’t have to hit the database. You could go ahead and add more Healthchecks which test for the availability of your other dependencies, to get a more complete picture of your system’s status:

31 August 2016