Most of the time in a web application, a single API request consists of multiple database queries. For example:
class DashboardsController < ApplicationController
def dashboard
@users = User.some_complex_scope.load_async
@products = Product.some_complex_scope.load_async
@library_files = LibraryFile.some_complex_scope.load_async
end
end
Quoting snippet from load_async PR description from rails repository.
The queries are executed synchronously, which mostly isn’t a huge concern. But, as the database grows larger in size, the response time of requests is getting longer. A significant part of the query time is often just I/O waits. Since these two queries are totally independent, ideally you could execute them in parallel, so that assuming that each take 50ms, the total query time would be 50ms rather than 100ms. In the above scenario, the queries can be executed asynchronously, and the time saved on the I/O waits can be reutilized for the next query execution.
The load_async
method will initiate a new thread to execute the query, which will reduce the wait time. If you use the usual lazy loading way in the controller (without the load_async
method), the loading will not begin until the view references it. But in the case of load_async
, loading will start before it is referenced in the view.
Configuration for load_async
We need to set the value for async_query_executor
in the environment configuration for load_async to work. By default, Rails initializes async_query_executor
to nil
. The query runs in the foreground if load_async
is called and executor is not defined. async_query_executor
comes with two configuration options.
-
:global_thread_pool
is used to create a common thread pool for all database connections.# config/environments/development.rb config.active_record.async_query_executor = :global_thread_pool
-
:multi_thread_pool
is used to create a unique thread pool for each database connection.# config/environments/development.rb config.active_record.async_query_executor = :multi_thread_pool