Traditionally, ActiveRecord queries have been synchronous, causing the application to pause and wait for the database to respond before continuing execution.
While this synchronous behavior works well in many cases, it can become a bottleneck when dealing with lengthy or resource-intensive queries, impacting the overall responsiveness of the application.
Asynchronous querying, however, is executed in a background thread. This enables our application main thread to remain responsive and handle incoming requests concurrently while the queries are executed in the background.
This asynchronous approach drastically enhances performance, especially in scenarios where waiting for the database response might hinder the application’s responsiveness.
Before
Rails 7 introduced ActiveRecord load_async method that allows multiple Active Record queries to be executed in parallel using the background thread and while our foreground thread continues on with the request.
This is useful for queries that can be performed long enough before their result will be needed, or for controllers which need to perform several independent queries.
It misses methods to load aggregate queries such as count, sum, etc.
After
Building upon the foundation laid in previous versions, Rails 7.1 introduced a suite of asynchronous methods in Active Record, significantly expanding its capabilities beyond traditional synchronous querying.
These new methods cater to various scenarios, including aggregates, single record retrieval, and custom SQL operations. They offer developers the flexibility to execute queries asynchronously, resulting in improved performance for specific types of database operations.
The following are the asynchronous methods in ActiveRecord:
async_count
async_sum
async_minimum
async_maximum
async_average
async_pluck
async_pick
async_ids
async_find_by_sql
async_count_by_sql
Asynchronous queries return promise objects. To retrieve the actual result of the asynchronous operation, we need to call the value
method on the returned promise. This method fetches the eventual value generated by the asynchronous query when it’s resolved.
Below is an example of using async_count
async_count
operates similarly to count
but executes asynchronously, allowing the application to perform other tasks while the count
operation runs in the background. It provides the flexibility to maintain responsiveness in the application, especially beneficial when dealing with resource-intensive queries.
In the above example, User.count
fetches total (53) records in 58.7ms. Using User.async_count
, it fetches in 3.6ms as asynchronous operation schedules in the background. Calling value
on promise retrieves the records count (53), akin to synchronous count, affirming identical results asynchronously.
- Here is an example of
async_sum
async queries aims to optimize the handling of slower queries, particularly targeting aggregate functions (like count, sum) and methods that fetch single records or any result beyond a Relation, aiming for greater efficiency in their execution.
Conclusion
Implementing asynchronous queries in a Rails application offers diverse advantages. They optimize performance by executing multiple tasks concurrently, particularly benefiting complex or slow queries and applications handling concurrent requests.
Additionally, they improve application responsiveness by seamlessly managing requests while queries operate in the background. Asynchronous queries also aid in preserving memory by executing operations in separate threads, particularly advantageous for applications handling extensive datasets or high request volumes.
However, adopting best practices is crucial. Reserve asynchronous queries for slower or intricate tasks, avoid unnecessary overhead for simple queries, and conduct thorough testing to ensure compatibility and expected functionality, considering potential limitations with ActiveRecord features like transactions and callbacks.
Ultimately, embracing asynchronous queries presents a robust approach to enhancing performance, responsiveness, and memory management in our Rails application.