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!