다중 데이터베이스와 Active Record

이 가이드는 Rails 애플리케이션에서 다중 데이터베이스를 사용하는 방법을 다룹니다.

이 가이드를 읽고 나면 다음을 알 수 있습니다:

  • 애플리케이션을 다중 데이터베이스로 설정하는 방법
  • 자동 연결 전환 작동 방식
  • 다중 데이터베이스를 위한 수평 분할 사용 방법
  • 지원되는 기능과 진행 중인 작업

애플리케이션의 인기와 사용량이 늘어남에 따라 새로운 사용자와 데이터를 지원하도록 애플리케이션을 확장해야 합니다. 애플리케이션을 확장해야 할 부분 중 하나가 데이터베이스 수준입니다. Rails는 다중 데이터베이스 사용을 지원하므로 데이터를 한 곳에 저장할 필요가 없습니다.

현재 다음과 같은 기능이 지원됩니다:

  • 각각의 복제본을 가진 다중 작성기 데이터베이스
  • 작업 중인 모델에 대한 자동 연결 전환
  • HTTP 동사와 최근 작성에 따른 작성기와 복제본 간 자동 전환
  • 다중 데이터베이스 생성, 삭제, 마이그레이션, 상호 작용을 위한 Rails 작업

다음과 같은 기능은 (아직) 지원되지 않습니다:

  • 복제본 로드 밸런싱

애플리케이션 설정

Rails는 대부분의 작업을 대신 해주지만, 애플리케이션을 다중 데이터베이스로 준비하기 위해 몇 가지 단계를 수행해야 합니다.

단일 작성기 데이터베이스가 있는 애플리케이션에 새로운 데이터베이스를 추가해야 한다고 가정해 보겠습니다. 새로운 데이터베이스의 이름은 “animals"입니다.

database.yml은 다음과 같습니다:

production:
  database: my_primary_database
  adapter: mysql2
  username: root
  password: <%= ENV['ROOT_PASSWORD'] %>

이제 "animals"라는 두 번째 데이터베이스와 두 데이터베이스 모두에 대한 복제본을 추가해 보겠습니다. 이를 위해 2단계 구성에서 3단계 구성으로 database.yml을 변경해야 합니다.

기본 구성이 제공되면 "기본” 구성으로 사용됩니다. “primary” 구성이 없는 경우 Rails는 각 환경의 첫 번째 구성을 기본값으로 사용합니다. 기본 구성은 기본 Rails 파일 이름을 사용합니다. 예를 들어 기본 구성은 스키마 파일로 schema.rb를 사용하지만, 다른 모든 항목은 [CONFIGURATION_NAMESPACE]_schema.rb를 사용합니다.

production:
  primary:
    database: my_primary_database
    username: root
    password: <%= ENV['ROOT_PASSWORD'] %>
    adapter: mysql2
  primary_replica:
    database: my_primary_database
    username: root_readonly
    password: <%= ENV['ROOT_READONLY_PASSWORD'] %>
    adapter: mysql2
    replica: true
  animals:
    database: my_animals_database
    username: animals_root
    password: <%= ENV['ANIMALS_ROOT_PASSWORD'] %>
    adapter: mysql2
    migrations_paths: db/animals_migrate
  animals_replica:
    database: my_animals_database
    username: animals_readonly
    password: <%= ENV['ANIMALS_READONLY_PASSWORD'] %>
    adapter: mysql2
    replica: true

다중 데이터베이스를 사용할 때 몇 가지 중요한 설정이 있습니다.

첫째, primaryprimary_replica의 데이터베이스 이름은 같아야 합니다. 이는 동일한 데이터를 포함하기 때문입니다. animalsanimals_replica도 마찬가지입니다.

둘째, 작성기와 복제본의 사용자 이름은 달라야 하며, 복제본 사용자의 데이터베이스 권한은 읽기만 가능하도록 설정해야 합니다.

복제 데이터베이스를 사용할 때는 database.ymlreplica: true 항목을 추가해야 합니다. 그렇지 않으면 Rails에서 어떤 것이 복제본이고 어떤 것이 작성기인지 알 수 없습니다. Rails는 마이그레이션과 같은 특정 작업을 복제본에서 실행하지 않습니다.

마지막으로 새 작성기 데이터베이스의 경우 migrations_paths를 해당 데이터베이스의 마이그레이션이 저장된 디렉토리로 설정해야 합니다. 이 가이드의 후반부에서 migrations_paths에 대해 자세히 살펴보겠습니다.

또한 schema_dump 옵션을 사용하여 스키마 덤프 파일을 구성하거나 schema_dump: false를 설정하여 스키마 덤프를 완전히 건너뛸 수 있습니다.

이제 새 데이터베이스가 준비되었으므로 연결 모델을 설정해 보겠습니다. 새 데이터베이스를 사용하려면 새 추상 클래스를 만들고 animals 데이터베이스에 연결해야 합니다.

class AnimalsRecord < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :animals, reading: :animals_replica }
end

그런 다음 ApplicationRecord를 업데이트하여 새 복제본을 인식하도록 해야 합니다.

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :primary_replica }
end

애플리케이션 레코드에 다른 이름의 클래스를 사용하는 경우 primary_abstract_class를 대신 설정해야 합니다. 그래야 Rails가 ActiveRecord::Base가 어떤 클래스와 연결을 공유해야 하는지 알 수 있습니다.

class PrimaryApplicationRecord < ActiveRecord::Base
  primary_abstract_class
end

기본 추상 클래스에서 상속하는 클래스는 표준 Rails 애플리케이션과 같습니다:

class Person < ApplicationRecord
end

기본적으로 Rails는 기본 및 복제본에 대해 각각 writingreading 데이터베이스 역할을 예상합니다. 기존 시스템에 이미 설정된 역할이 있고 변경하고 싶지 않은 경우 애플리케이션 구성에서 새 역할 이름을 설정할 수 있습니다.

config.active_record.writing_role = :default
config.active_record.reading_role = :readonly

단일 모델에 데이터베이스에 연결하고 해당 모델에서 테이블을 상속하는 것이 중요합니다. 데이터베이스 클라이언트에는 열 수 있는 연결 수에 제한이 있으며, 이렇게 하면 Rails가 모델 클래스 이름을 연결 사양 이름으로 사용하므로 연결 수가 늘어납니다.

이제 database.yml과 새 모델이 설정되었으므로 데이터베이스를 생성할 차례입니다. Rails에는 다중 데이터베이스를 사용하는 데 필요한 모든 명령이 포함되어 있습니다.

bin/rails --help를 실행하면 사용할 수 있는 모든 명령을 볼 수 있습니다. 다음과 같은 내용이 표시되어야 합니다:

$ bin/rails --help
...
db:create                          # Create the database from DATABASE_URL or config/database.yml for the ...
db:create:animals                  # Create animals database for current environment
db:create:primary                  # Create primary database for current environment
db:drop                            # Drop the database from DATABASE_URL or config/database.yml for the cu...
db:drop:animals                    # Drop animals database for current environment
db:drop:primary                    # Drop primary database for current environment
db:migrate                         # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)
db:migrate:animals                 # Migrate animals database for current environment
db:migrate:primary                 # Migrate primary database for current environment
db:migrate:status                  # Display status of migrations
db:migrate:status:animals          # Display status of migrations for animals database
db:migrate:status:primary          # Display status of migrations for primary database
db:reset                           # Drop and recreates all databases from their schema for the current environment and loads the seeds
db:reset:animals                   # Drop and recreates the animals database from its schema for the current environment and loads the seeds
db:reset:primary                   # Drop and recreates the primary database from its schema for the current environment and loads the seeds
db:rollback                        # Roll the schema back to the previous version (specify steps w/ STEP=n)
db:rollback:animals                # Rollback animals database for current environment (specify steps w/ STEP=n)
db:rollback:primary                # Rollback primary database for current environment (specify steps w/ STEP=n)
db:schema:dump                     # Create a database schema file (either db/schema.rb or db/structure.sql  ...
db:schema:dump:animals             # Create a database schema file (either db/schema.rb or db/structure.sql  ...
db:schema:dump:primary             # Create a db/schema.rb file that is portable against any DB supported  ...
db:schema:load                     # Load a database schema file (either db/schema.rb or db/structure.sql  ...
db:schema:load:animals             # Load a database schema file (either db/schema.rb or db/structure.sql  ...
db:schema:load:primary             # Load a database schema file (either db/schema.rb or db/structure.sql  ...
db:setup                           # Create all databases, loads all schemas, and initializes with the seed data (use db:reset to also drop all databases first)
db:setup:animals                   # Create the animals database, loads the schema, and initializes with the seed data (use db:reset:animals to also drop the database first)
db:setup:primary                   # Create the primary database, loads the schema, and initializes with the seed data (use db:reset:primary to also drop the database first)
...

bin/rails db:create 명령을 실행하면 primary와 animals 데이터베이스가 모두 생성됩니다. 데이터베이스 사용자를 생성하는 명령은 없으며, 복제본의 읽기 전용 사용자를 지원하려면 수동으로 수행해야 합니다. animals 데이터베이스만 생성하려면 bin/rails db:create:animals를 실행할 수 있습니다.

스키마 및 마이그레이션 관리 없이 데이터베이스에 연결

외부 데이터베이스에 연결하되 스키마 관리, 마이그레이션, 시드 등의 데이터베이스 관리 작업을 수행하고 싶지 않은 경우 데이터베이스별 구성 옵션 database_tasks: false를 설정할 수 있습니다. 기본적으로 이 옵션은 true로 설정되어 있습니다.

production:
  primary:
    database: my_database
    adapter: mysql2
  animals:
    database: my_animals_database
    adapter: mysql2
    database_tasks: false

생성기 및 마이그레이션

다중 데이터베이스의 마이그레이션은 구성의 데이터베이스 키 이름으로 시작하는 고유 폴더에 있어야 합니다.

또한 데이터베이스 구성에서 migrations_paths를 설정하여 Rails가 마이그레이션을 찾을 수 있도록 해야 합니다.

예를 들어 animals 데이터베이스는 db/animals_migrate 디렉토리에서 마이그레이션을 찾고, primarydb/migrate에서 찾습니다. Rails 생성기는 이제 --database 옵션을 사용하므로 파일이 올바른 디렉토리에 생성됩니다. 다음과 같이 명령을 실행할 수 있습니다:

$ bin/rails generate migration CreateDogs name:string --database animals

Rails 생성기를 사용하는 경우 스캐폴드와 모델 생성기가 추상 클래스를 자동으로 생성합니다. 명령줄에 데이터베이스 키를 전달하기만 하면 됩니다.

$ bin/rails generate scaffold Dog name:string --database animals

데이터베이스 이름과 Record가 포함된 클래스가 생성됩니다. 이 예에서 데이터베이스는 Animals이므로 AnimalsRecord가 생성됩니다:

class AnimalsRecord < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :animals }
end

생성된 모델은 자네, 번역을 계속하겠습니다.

자동으로 AnimalsRecord를 상속합니다.

class Dog < AnimalsRecord
end

참고: Rails는 작성기에 대한 복제본이 어떤 것인지 알 수 없으므로 작업이 완료된 후 추상 클래스에 이를 추가해야 합니다.

Rails는 새 클래스를 한 번만 생성합니다. 새 스캐폴드로 인해 덮어쓰이지 않으며, 스캐폴드가 삭제되어도 삭제되지 않습니다.

추상 클래스의 이름이 AnimalsRecord와 다른 경우 --parent 옵션을 전달하여 다른 추상 클래스를 사용하도록 지정할 수 있습니다:

$ bin/rails generate scaffold Dog name:string --database animals --parent Animals::Record

이렇게 하면 다른 부모 클래스를 사용하도록 지정했기 때문에 AnimalsRecord를 생성하지 않습니다.

자동 역할 전환 활성화

마지막으로 애플리케이션에서 읽기 전용 복제본을 사용하려면 미들웨어를 활성화하여 자동 전환을 수행해야 합니다.

자동 전환을 통해 애플리케이션은 HTTP 동사와 요청 사용자의 최근 작성 여부에 따라 작성기와 복제본 간에 전환할 수 있습니다.

애플리케이션이 POST, PUT, DELETE 또는 PATCH 요청을 받으면 자동으로 작성기 데이터베이스에 기록됩니다. 이러한 메서드가 아닌 요청이지만 애플리케이션이 최근에 작성한 경우에도 작성기 데이터베이스가 사용됩니다. 그 외의 모든 요청은 복제본 데이터베이스를 사용합니다.

자동 연결 전환 미들웨어를 활성화하려면 자동 전환 생성기를 실행할 수 있습니다:

$ bin/rails g active_record:multi_db

그리고 다음 줄의 주석을 제거합니다:

Rails.application.configure do
  config.active_record.database_selector = { delay: 2.seconds }
  config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
  config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end

Rails는 “자신의 작성 읽기"를 보장하며, delay 창 내에 있는 GET 또는 HEAD 요청을 작성기로 보냅니다. 기본적으로 지연 시간은 2초로 설정되어 있습니다. 데이터베이스 인프라에 따라 이 값을 변경해야 합니다. Rails는 지연 창 내에서 다른 사용자의 "최근 작성 읽기"를 보장하지 않으며, 최근에 작성하지 않은 경우 GET 및 HEAD 요청을 복제본으로 보냅니다.

Rails의 자동 연결 전환은 상대적으로 기본적이며 의도적으로 많은 기능을 하지 않습니다. 목표는 애플리케이션 개발자가 충분히 사용자 정의할 수 있는 자동 연결 전환 시스템을 보여주는 것입니다.

Rails의 설정을 통해 전환 방식과 기준 매개변수를 쉽게 변경할 수 있습니다. 예를 들어 세션 대신 쿠키를 사용하여 연결을 전환하려면 다음과 같은 사용자 정의 클래스를 작성할 수 있습니다:

class MyCookieResolver < ActiveRecord::Middleware::DatabaseSelector::Resolver
  def self.call(request)
    new(request.cookies)
  end

  def initialize(cookies)
    @cookies = cookies
  end

  attr_reader :cookies

  def last_write_timestamp
    self.class.convert_timestamp_to_time(cookies[:last_write])
  end

  def update_last_write_timestamp
    cookies[:last_write] = self.class.convert_time_to_timestamp(Time.now)
  end

  def save(response)
  end
end

그리고 미들웨어에 전달합니다:

config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = MyCookieResolver

수동 연결 전환 사용

자동 연결 전환으로는 충분하지 않은 경우가 있습니다. 예를 들어 특정 요청에 대해 항상 복제본으로 보내고 싶지만, POST 요청 경로에 있는 경우에도 그렇게 하고 싶을 수 있습니다.

이를 위해 Rails는 필요한 연결로 전환할 수 있는 connected_to 메서드를 제공합니다.

ActiveRecord::Base.connected_to(role: :reading) do
  # 이 블록의 모든 코드는 읽기 역할에 연결됩니다.
end

connected_to 호출의 "역할"은 해당 연결 핸들러(또는 역할)에 연결된 연결을 조회합니다. reading 연결 핸들러에는 connects_to를 통해 reading 역할 이름으로 연결된 모든 연결이 포함됩니다.

connected_to로 역할을 전환하면 기존 연결을 조회하고 연결 사양 이름을 사용하여 전환합니다. 따라서 connected_to(role: :nonexistent)와 같이 알 수 없는 역할을 전달하면 ActiveRecord::ConnectionNotEstablished (No connection pool for 'ActiveRecord::Base' found for the 'nonexistent' role.)와 같은 오류가 발생합니다.

쿼리가 읽기 전용인지 확실히 하려면 prevent_writes: true를 전달하세요. 이렇게 하면 쓰기 작업으로 보이는 쿼리가 데이터베이스로 전송되는 것을 방지합니다. 복제본 데이터베이스를 읽기 전용 모드로 구성해야 합니다.

ActiveRecord::Base.connected_to(role: :reading, prevent_writes: true) do
  # Rails는 각 쿼리가 읽기 쿼리인지 확인합니다.
end

수평 분할

수평 분할은 각 데이터베이스 서버의 행 수를 줄이기 위해 데이터베이스를 분할하는 것이지만, "샤드” 간에 동일한 스키마를 유지합니다. 이를 “다중 테넌트” 분할이라고도 합니다.

Rails의 수평 분할 API는 Rails 6.0부터 존재한 다중 데이터베이스/수직 분할 API와 유사합니다.

샤드는 다음과 같이 3단계 구성에 선언됩니다:

production:
  primary:
    database: my_primary_database
    adapter: mysql2
  primary_replica:
    database: my_primary_database
    adapter: mysql2
    replica: true
  primary_shard_one:
    database: my_primary_shard_one
    adapter: mysql2
    migrations_paths: db/migrate_shards
  primary_shard_one_replica:
    database: my_primary_shard_one
    adapter: mysql2
    replica: true
  primary_shard_two:
    database: my_primary_shard_two
    adapter: mysql2
    migrations_paths: db/migrate_shards
  primary_shard_two_replica:
    database: my_primary_shard_two
    adapter: mysql2
    replica: true

모델은 그런 다음 connects_to API를 통해 shards 키로 연결됩니다:

class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  connects_to database: { writing: :primary, reading: :primary_replica }
end

class ShardRecord < ApplicationRecord
  self.abstract_class = true

  connects_to shards: {
    shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica },
    shard_two: { writing: :primary_shard_two, reading: :primary_shard_two_replica }
  }
end

샤드를 사용하는 경우 migrations_pathsschema_dump가 모든 샤드에 대해 변경되지 않도록 해야 합니다. 마이그레이션을 생성할 때 --database 옵션을 전달하고 샤드 이름 중 하나를 사용할 수 있습니다. 경로가 모두 동일하므로 어떤 것을 선택해도 상관없습니다.

$ bin/rails g scaffold Dog name:string --database primary_shard_one

그런 다음 모델은 수동으로 connected_to API를 통해 샤드를 전환할 수 있습니다. 샤드를 사용하는 경우 roleshard를 모두 전달해야 합니다:

ActiveRecord::Base.connected_to(role: :writing, shard: :default) do
  @id = Person.create! # :default 샤드에 레코드 생성
end

ActiveRecord::Base.connected_to(role: :writing, shard: :shard_one) do
  Person.find(@id) # 레코드를 찾을 수 없음, :default 샤드에 생성되었기 때문
end

수평 분할 API는 읽기 복제본도 지원합니다. connected_to API를 사용하여 역할과 샤드를 전환할 수 있습니다.

ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do
  Person.first # shard one의 읽기 복제본에서 레코드 조회
end

자동 샤드 전환 활성화

애플리케이션은 제공된 미들웨어를 사용하여 요청별로 자동으로 샤드를 전환할 수 있습니다.

ShardSelector 미들웨어는 자동으로 샤드를 전환하는 프레임워크를 제공합니다. Rails는 어떤 샤드로 전환할지 결정하는 기본 프레임워크를 제공하며, 필요한 경우 애플리케이션에서 사용자 정의 전략을 작성할 수 있습니다.

ShardSelector는 미들웨어 동작을 변경하는 데 사용할 수 있는 옵션 집합(현재 lock만 지원)을 받습니다. lock은 기본적으로 true이며, 블록 내부에서 샤드 전환을 금지합니다. lock이 false인 경우 샤드 전환이 허용됩니다. 테넌트 기반 분할의 경우 lock은 항상 true여야 합니다.

데이터베이스 선택기와 동일한 생성기를 사용하여 자동 샤드 전환을 위한 파일을 생성할 수 있습니다:

$ bin/rails g active_record:multi_db

그런 다음 파일에서 다음 내용의 주석을 제거합니다:

Rails.application.configure do
  config.active_record.shard_selector = { lock: true }
  config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard }
end

애플리케이션은 해결기 코드를 제공해야 하며, 이는 애플리케이션별 모델에 따라 달라집니다. 다음과 같은 해결기 예시가 있습니다:

config.active_record.shard_resolver = ->(request) {
  subdomain = request.subdomain
  tenant = Tenant.find_by_subdomain!(subdomain)
  tenant.shard
}

세부적인 데이터베이스 연결 전환

Rails 6.1에서는 전체 데이터베이스 대신 하나의 데이터베이스에 대해서만 연결을 전환할 수 있습니다.

세부적인 데이터베이스 연결 전환을 통해 모든 데이터베이스 전역적으로 영향을 미치지 않고 추상 연결 클래스에서 연결을 전환할 수 있습니다. 이는 AnimalsRecord 쿼리를 복제본에서 읽도록 전환하면서 ApplicationRecord 쿼리가 기본 데이터베이스로 가도록 하는 데 유용합니다.

AnimalsRecord.connected_to(role: :reading) do
  Dog.first # animals_replica에서 읽음
  Person.first  # primary에서 읽음
end

샤드에 대해서도 세부적으로 연결을 전환할 수 있습니다.

AnimalsRecord.connected_to(role: :reading, shard: :shard_one) do
  Dog.first # shard_one_replica에서 네, 번역을 계속하겠습니다.

```ruby
AnimalsRecord.connected_to(role: :reading, shard: :shard_one) do
  Dog.first # shard_one_replica에서 읽음. shard_one_replica 연결이 없는 경우 ConnectionNotEstablished 오류가 발생합니다.
  Person.first # primary 작성기에서 읽음
end

기본 데이터베이스 클러스터만 전환하려면 ApplicationRecord를 사용합니다:

ApplicationRecord.connected_to(role: :reading, shard: :shard_one) do
  Person.first # primary_shard_one_replica에서 읽음
  Dog.first # animals_primary에서 읽음
end

ActiveRecord::Base.connected_to는 전역적으로 연결을 전환할 수 있는 기능을 유지합니다.

데이터베이스 간 조인이 있는 연관 관계 처리

Rails 7.0+ 버전부터 Active Record에는 데이터베이스 간 조인을 처리하는 옵션이 있습니다. 데이터베이스 간 조인이 필요한 has many through 또는 has one through 연관 관계의 경우 disable_joins: true 옵션을 전달하면 2개 이상의 쿼리를 실행하도록 할 수 있습니다.

예를 들어:

class Dog < AnimalsRecord
  has_many :treats, through: :humans, disable_joins: true
  has_many :humans

  has_one :home
  has_one :yard, through: :home, disable_joins: true
end

class Home
  belongs_to :dog
  has_one :yard
end

class Yard
  belongs_to :home
end

이전에는 @dog.treats를 호출할 때 disable_joins를 사용하지 않거나 @dog.yard를 호출할 때 disable_joins를 사용하지 않으면 데이터베이스 간 조인을 처리할 수 없어 오류가 발생했습니다. disable_joins 옵션을 사용하면 클러스터 간 조인을 시도하지 않도록 여러 개의 선택 쿼리를 생성합니다. 위의 연관 관계에 대해 @dog.treats는 다음과 같은 SQL을 생성합니다:

SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ?  [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?)  [["human_id", 1], ["human_id", 2], ["human_id", 3]]

그리고 @dog.yard는 다음과 같은 SQL을 생성합니다:

SELECT "home"."id" FROM "homes" WHERE "homes"."dog_id" = ? [["dog_id", 1]]
SELECT "yards".* FROM "yards" WHERE "yards"."home_id" = ? [["home_id", 1]]

이 옵션과 관련된 중요한 사항은 다음과 같습니다:

  1. 이제 조인 대신 두 개 이상의 쿼리가 실행되므로 성능 문제가 발생할 수 있습니다. humans에 대한 선택 결과가 많은 ID를 반환하면 treats에 대한 선택 쿼리에 너무 많은 ID가 전송될 수 있습니다.
  2. 더 이상 조인을 수행하지 않으므로 정렬 또는 제한이 있는 쿼리는 메모리 내에서 정렬됩니다. 한 테이블의 순서를 다른 테이블에 적용할 수 없기 때문입니다.
  3. 이 설정은 조인을 비활성화하려는 모든 연관 관계에 추가해야 합니다. Rails는 지연 로딩 때문에 이를 추측할 수 없습니다. @dog.treats에서 treats를 로드하려면 Rails가 이미 생성해야 할 SQL을 알고 있어야 합니다.

스키마 캐싱

각 데이터베이스에 대한 스키마 캐시를 로드하려면 각 데이터베이스 구성에 schema_cache_path를 설정하고 애플리케이션 구성에 config.active_record.lazily_load_schema_cache = true를 설정해야 합니다. 데이터베이스 연결이 설정될 때 캐시가 지연 로드됩니다.

주의 사항

복제본 로드 밸런싱

Rails는 복제본의 자동 로드 밸런싱도 지원하지 않습니다. 이는 인프라에 매우 의존적입니다. 향후 기본적이고 원시적인 로드 밸런싱을 구현할 수 있지만, 규모가 큰 애플리케이션의 경우 이는 Rails 외부에서 처리해야 합니다.