Scaling ActiveRecord in Adhearsion

Using ActiveRecord with Adhearsion opens up a world of possibilities for highly dynamic voice applications, but what happens when you reach a large number of concurrent calls? Well, unless you carefully manage your database connection pool, things get a little tricky.

Here’s the issue: you need to keep the number of connections to your database to a minimum; at the same time, you need enough connections to service all of your calls happening concurrently. The simple solution to 100 concurrent calls? 100 database connections. So, you merrily go ahead and increase your ActiveRecord connection pool to 100 connections and things appear to work. This, though, is really not a good solution. Call number 101 will struggle.

The problem is that Adhearsion runs each call in its own thread, and by default ActiveRecord doesn’t play well with multiple threads. Take this example dialplan:

lookup_stuff {
  answer
  stuff = Thing.all
  stuff.each do |thing|
    speak "Thing number #{thing.id} is called #{thing.name}"
  end
  hangup
}

In this case, if there are a large number of Thing records, speaking them all will take a while. The catch here is that ActiveRecord will keep hold of your database connection for the duration of the thread (call).  Only after the thread, and call, has ended and garbage collection has run will it become available for re-use. Note that this means the connection is not immediately checked back in to the pool when the thread ends. The connection be unavailable for reuse until some amount of time after the call ends!

The solution to this problem is to check your database connections back into the pool as soon as you are done with them. The best way to do this is using #with_connection, and looks like this:

def db(&block)
  ActiveRecord::Base.connection_pool.with_connection &block
end

lookup_stuff {
  answer
  stuff = db { Thing.all }
  stuff.each do |thing|
    speak "Thing number #{thing.id} is called #{thing.name}"
  end
  hangup
}

Now your call thread will only keep hold of the connection long enough to return from the query, and so you can keep your thread pool nice and small; probably far smaller than 100. But be careful; even a single database call that’s not wrapped in #db will hit you hard. You have to be thorough about this if you’re going to see any improvement.

This then means that your dialplan will look fairly ugly, and you run a very high risk of missing something. Surely there’s a better way to handle this automatically? Well, there is. We just released the ‘activerecord-wrap-with-connection’ gem which automatically wraps all methods which touch a database connection with #with_connection. Simply add it to your Adhearsion app’s Gemfile after activerecord and you should be good to go. You can find the code here.

NB: This gem will only work with ActiveRecord >= 3.0.0. For a 2.x version, see here. This code may be modified to use #with_connection if desired with ActiveRecord >= 2.3.5.

Subscribe to our mailing list

* indicates required
I want to read about...
Email Format

One thought on “Scaling ActiveRecord in Adhearsion

What do you think?