Rails 7.1 expands its support for ActiveRecord asynchronous queries

railsOctober 30, 2023Dotby Alkesh Ghorpade

ActiveRecord async queries are a way to execute Active Record queries in parallel, which can improve the performance of your Rails application. This is especially useful for slow or complex queries or for applications that need to handle a lot of concurrent requests.

Asynchronous queries are executed in a separate thread, meaning they don't block the main thread of your Rails application. This allows your application to continue responding to requests while the queries are running in the background.

Before Rails 7.1

The load_async method was added to Rails in version 7.0. load_async allows you to schedule an ActiveRecord query to be executed in a background thread. This can be useful for speeding up requests that involve multiple database queries, especially if those queries are expensive.

For example, the following code will load the user relation object asynchronously:

User.load_async

You can then call the loaded? method on the relation object to check if the query has finished executing. Once the query has finished executing, you can access the result as usual.

For example, the following code will print the first names of all the users to the console:

User.load_async.each do |user|
  puts user.first_name
end

Before Rails 7, there were a few ways to load queries asynchronously. One common approach was to use a gem like concurrent-ruby or async. These gems provide various tools for asynchronous programming, including thread pools and promises.

Another approach was to use a library like Dalliance. Dalliance is a Ruby client for the Memcached distributed cache. It can be used to cache the results of database queries, improving performance by reducing the number of queries that need to be executed.

Finally, it was also possible to implement asynchronous query loading yourself. This involved using Ruby's Thread class to create a new thread for each query. The main thread would then wait for all the threads to finish before continuing.

However, all of these approaches had their own drawbacks. For example, using concurrent-ruby or async requires developers to have a good understanding of asynchronous programming. Using Dalliance required the database to be configured to support Memcached. And implementing asynchronous query loading yourself could be error-prone.

The option to load aggregate queries like count, sum, minimum, maximum, etc, was missing before Rails 7.1.

In Rails 7.1

Rails 7.1 expands its support for ActiveRecord asynchronous queries. If your Rails application has a lot of users, you can load their count in an async manner by using the below query

# Synchronous count
count = User.count

# Asynchronous count
promise = User.async_count
=> #<ActiveRecord::Promise status=pending>

promise.value
=> 1928324

The async_count method returns a promise. You must execute the method value on the promise and fetch the required result. This enhancement addresses the need for more efficient handling of not-so-fast queries, mainly focusing on aggregates (such as count, sum, etc.) and all methods returning a single record or anything other than a Relation.

The new API includes the following asynchronous methods:

  • async_count
  • async_sum
  • async_minimum
  • async_maximum
  • async_average
  • async_pluck
  • async_pick
  • async_ids
  • async_find_by_sql
  • async_count_by_sql

Benefits of using asynchronous queries

There are several benefits to using asynchronous queries in your Rails application:

  • Improved performance: Asynchronous queries can improve the performance of your application by allowing multiple queries to be executed simultaneously. This is especially useful for slow or complex queries or for applications that need to handle a lot of concurrent requests.

  • Increased responsiveness: Asynchronous queries allow your application to continue responding to requests while the queries are running in the background. This can improve the overall responsiveness of your application.

  • Reduced memory usage: Asynchronous queries can reduce the memory usage of your application by executing queries in a separate thread. This can be beneficial for applications that need to handle a lot of concurrent requests or that need to process large datasets.

Best practices for using asynchronous queries

Here are some best practices for using asynchronous queries in your Rails application:

  • Use asynchronous queries for slow or complex queries or for queries that need to be executed concurrently.

  • Avoid using asynchronous queries for simple or fast queries, as this can add unnecessary overhead.

  • Asynchronous queries may not be compatible with all Active Record features, such as callbacks and transactions.

  • Test your application thoroughly when using asynchronous queries to ensure that it is working as expected.

Overall, asynchronous queries are a powerful tool that can improve the performance, responsiveness, and memory usage of your Rails application.

To know more about this feature, please refer to this PR.

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at justin@shakacode.com or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right