1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/lib/active_record.rb
Jean Boussier 7fc174aada Allow Adapter#select_all to be performed asynchronously from a background thread pool
Sometimes a controller or a job has to perform multiple independent queries, e.g.:

```
def index
  @posts = Post.published
  @categories = Category.active
end
```

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.

A very naive way to do this is to simply call `Relation#to_a` in a
background thread, the problem is that most Rails applications, and
even Rails itself rely on thread local state (`PerThreadRegistry`,
`CurrentAttributes`, etc). So executing such a high level interface
from another thread is likely to lead to many context loss problems
or even thread safety issues.

What we can do instead, is to schedule a much lower level operation
(`Adapter#select_all`) in a thread pool, and return a future/promise.
This way we kepp most of the risky code on the main thread, but perform
the slow IO in background, with very little chance of executing some
code that rely on state stored in thread local storage.

Also since most users are on MRI, only the IO can really be parallelized,
so scheduling more code to be executed in background wouldn't lead
to better performance.
2021-02-08 19:17:52 +01:00

191 lines
5.2 KiB
Ruby

# frozen_string_literal: true
#--
# Copyright (c) 2004-2021 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
require "active_support"
require "active_support/rails"
require "active_model"
require "arel"
require "yaml"
require "active_record/version"
require "active_model/attribute_set"
require "active_record/errors"
module ActiveRecord
extend ActiveSupport::Autoload
autoload :Base
autoload :Callbacks
autoload :Core
autoload :ConnectionHandling
autoload :CounterCache
autoload :DynamicMatchers
autoload :DelegatedType
autoload :Enum
autoload :InternalMetadata
autoload :Explain
autoload :Inheritance
autoload :Integration
autoload :Migration
autoload :Migrator, "active_record/migration"
autoload :ModelSchema
autoload :NestedAttributes
autoload :NoTouching
autoload :TouchLater
autoload :Persistence
autoload :QueryCache
autoload :Querying
autoload :ReadonlyAttributes
autoload :RecordInvalid, "active_record/validations"
autoload :Reflection
autoload :RuntimeRegistry
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
autoload :StatementCache
autoload :Store
autoload :SignedId
autoload :Suppressor
autoload :Timestamp
autoload :Transactions
autoload :Translation
autoload :Validations
autoload :SecureToken
autoload :DestroyAssociationAsyncJob
eager_autoload do
autoload :ConnectionAdapters
autoload :Aggregations
autoload :Associations
autoload :AttributeAssignment
autoload :AttributeMethods
autoload :AutosaveAssociation
autoload :AsynchronousQueriesTracker
autoload :LegacyYamlAdapter
autoload :Relation
autoload :AssociationRelation
autoload :NullRelation
autoload_under "relation" do
autoload :QueryMethods
autoload :FinderMethods
autoload :Calculations
autoload :PredicateBuilder
autoload :SpawnMethods
autoload :Batches
autoload :Delegation
end
autoload :Result
autoload :FutureResult
autoload :TableMetadata
autoload :Type
end
module Coders
autoload :YAMLColumn, "active_record/coders/yaml_column"
autoload :JSON, "active_record/coders/json"
end
module AttributeMethods
extend ActiveSupport::Autoload
eager_autoload do
autoload :BeforeTypeCast
autoload :Dirty
autoload :PrimaryKey
autoload :Query
autoload :Read
autoload :TimeZoneConversion
autoload :Write
autoload :Serialization
end
end
module Locking
extend ActiveSupport::Autoload
eager_autoload do
autoload :Optimistic
autoload :Pessimistic
end
end
module Scoping
extend ActiveSupport::Autoload
eager_autoload do
autoload :Named
autoload :Default
end
end
module Middleware
extend ActiveSupport::Autoload
autoload :DatabaseSelector, "active_record/middleware/database_selector"
end
module Tasks
extend ActiveSupport::Autoload
autoload :DatabaseTasks
autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
autoload :PostgreSQLDatabaseTasks,
"active_record/tasks/postgresql_database_tasks"
end
autoload :TestDatabases, "active_record/test_databases"
autoload :TestFixtures, "active_record/fixtures"
def self.eager_load!
super
ActiveRecord::Locking.eager_load!
ActiveRecord::Scoping.eager_load!
ActiveRecord::Associations.eager_load!
ActiveRecord::AttributeMethods.eager_load!
ActiveRecord::ConnectionAdapters.eager_load!
end
end
ActiveSupport.on_load(:active_record) do
Arel::Table.engine = self
end
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
end
YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
YAML.load_tags["!ruby/object:ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString"] = "ActiveRecord::Type::String"