Rails adds support for Fiber-safe ActiveRecord ConnectionPools


One of the major focuses for Ruby 3 was parallelism and concurrency. A trailblazer in this space is the use of Ruby Fibers. Fibers are a concurrency mechanism that allows us to pause, loop, and resume execution while consuming far fewer context switches. It consumes less memory than threads while giving the developer control over code segment execution.

Fibers are the best solution for scalable non-blocking clients and servers. However, it has taken Rails a while to make use of it.

Before

As Rails continues to replace the usage of threads with fibers, the performance will only get better. One area of development is ActiveRecord which until now still depended on threads, despite configuring ActiveSupport’s isolation level. An isolation level determines how database tractions are propagated to other users and systems.

Why is that important? Well for that we need to understand what ActiveRecord’s ConnectionPool is. A connection pool manages multiple ActiveRecord connections. A connection essentially performs a transaction on a database. To perform this transaction, Rails offloads the I/O operation to a thread where every thread maintains a separate DB connection (in the case of Puma). Otherwise individual conversations with the database could mix up. Now that there is a more performant alternative to threads, ActiveRecord should switch over! Sadly, it does not.

Let’s configure config.active_support.isolation_level to fiber,

module Myapp
  class Application < Rails::Application
    config.active_support.isolation_level = :fiber
  end
end

Now let’s check how a ConnectionPool converses with the database.

> Rails.application.config.active_support.isolation_level
=> :fiber
> ActiveRecord::Base.connection_pool.send(:current_thread)
=> #<Thread:0x00007f9ba485fa88 run>

ActiveRecord’s ConnectionPool does not honor ActiveSupport’s isolation level!

After

Fortunately, this PR migrated overall ConnectionPool’s conversations to either use Threads or Fibers.

Now let’s have a look!

> Rails.application.config.active_support.isolation_level
=> :fiber
> ActiveRecord::Base.connection_pool.send(:current_thread)
=> #<Fiber:0x00007fb5515d5238 (resumed)>

Great! But, what does this mean? Ideally, in the foreseeable future, we can expect good performance improvements to Rails I/O operations. Currently, Rails abstracts execution using IsolatedExecutionState, at least that’s what it is supposed to do. However, as one looks around the Rails codebase, there seems to be a lot of code that is hard-wired to a thread-centric implementation. So there’s a lot of work for the Rails core team, jump in and contribute today!

Join Our Newsletter