단일 테이블 상속

요약:Single Table Inheritance (STI)를 사용하여 새 테이블을 디자인하지 마십시오. STI를 패턴으로 사용하는 기존 테이블의 경우 새 유형을 추가하지 않고 별도의 테이블로 분할하는 것을 고려하십시오.

STI는 하나의 테이블이 다른 유형의 레코드를 저장하는 데이터베이스 디자인 패턴입니다. 이러한 레코드에는 일부 공통 열의 하위 집합과 응용 프로그램에 이 레코드가 어떤 개체로 표현되어야 하는지를 지시하는 다른 열이 있습니다. 예를 들어 같은 테이블에 두 가지 다른 유형의 SSH 키를 저장하는 데 사용할 수 있습니다. ActiveRecord는 이를 활용하고 STI 사용을 더 편리하게 만드는 몇 가지 기능을 제공합니다.

새로운 STI 테이블을 더 이상 허용하지 않는 이유:

  • 테이블에 대량의 행이 포함되어야 할 때 테이블이 거대해지는 문제가 발생하며, 테이블을 작게 유지해야 합니다.
  • 경량 락 사용률을 높이는 추가적인 인덱스가 필요하며, 포화시 사건을 유발할 수 있습니다.
  • 값을 기준으로 모든 데이터를 필터링해야 하므로 읽기 시에 더 많은 페이지 액세스로 오버헤드가 발생합니다.
  • 개체의 올바른 클래스를로드하기 위해 class_name을 사용하지만, 클래스 이름을 저장하는 것은 손해가 크고 불필요합니다.

STI 대신 다음 대안을 고려하십시오.

  • 각 유형마다 다른 테이블을 사용합니다.
  • *_type 열을 추가하지 않습니다. 이는 나중에 새 유형이 추가될 가능성을 나타낼 수 있는 코드 냄새로, 나중에 리팩터링하기가 훨씬 어려울 수 있습니다.
  • _type 열에 효과적으로 STI인 테이블이 이미있으면:
    • 기존 데이터를 여러 테이블로 분할합니다.
    • 새 유형을 새 테이블로 추가하면서 기존 것을 유지하는 리팩터링(예: 기본 클래스의 로직을 관심사로 이동)을 고려하십시오.

만약, 위의 모든 단점과 대안을 고려한 후에도STI가 해당 문제의 유일한 해결책이라면, 적어도 레코드에 클래스 이름을 저장하는 문제를 enum 유형을 사용하여 회피할 수 있으며 EnumInheritance 관심사를 사용할 수 있습니다:

class Animal < ActiveRecord::Base
  include EnumInheritance

  enum species: {
    dog: 1,
    cat: 2
  }

  def self.inheritance_column_to_class_map = {
    dog: 'Dog',
    cat: 'Cat'
  }

  def self.inheritance_column = 'species'
end

class Dog < Animal
  self.allow_legacy_sti_class = true
end

class Cat < Animal
  self.allow_legacy_sti_class = true
end

테이블이 이미 *_type을 가지고 있는 경우, 새로운 유형의 클래스를 필요한대로 추가할 수 있습니다.

마이그레이션에서

모델이 마이그레이션에서 사용될 때마다, 단일 테이블 상속을 비활성화해야 합니다. (마이그레이션에서도)Rails가 연결을 로드하는 방식 때문에 STI를 비활성화하지 않으면 예상치 못한 코드나 연결을로드하여 의도하지 않은 부작용이나 업그레이드 중 실패를 야기할 수 있습니다.

class SomeMigration < Gitlab::Database::Migration[2.1]
  class Services < MigrationRecord
    self.table_name = 'services'
    self.inheritance_column = :_type_disabled
  end

  def up
  ...

모델에 EachBatch를 비활성화하는 이상 모델에 추가할 것이 없는 경우, 클래스를 정의하는 대신 도우미 define_batchable_model을 사용하십시오. 이를 통해 마이그레이션이 마이그레이션에 대한 열을 격리하여로드하고 도우미가 기본적으로 STI를 비활성화합니다.

class EnqueueSomeBackgroundMigration < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  def up
    define_batchable_model('services').select(:id).in_batches do |relation|
      jobs = relation.pluck(:id).map do |id|
        ['ExtractServicesUrl', [id]]
      end

      BackgroundMigrationWorker.bulk_perform_async(jobs)
    end
  ...