단일 테이블 상속

요약: 단일 테이블 상속(STI)을 사용하여 새 테이블을 설계하지 마십시오. STI를 패턴으로 사용하는 기존 테이블의 경우 새 유형을 추가하지 않도록하고 별도의 테이블로 분리하는 것을 고려하십시오.

STI는 하나의 테이블에 서로 다른 유형의 레코드를 저장하는 데이터베이스 설계 패턴입니다. 이러한 레코드는 일부 공유된 열의 하위 집합을 가지고 있으며 다른 열은 해당 레코드가 표현해야하는 객체를 응용 프로그램에 지시합니다. 이는 예를 들어 동일한 테이블에 두 가지 다른 유형의 SSH 키를 저장하는 데 사용될 수 있습니다. ActiveRecord는 이를 활용하고 STI 사용을 더 편리하게 만드는 몇 가지 기능을 제공합니다.

우리는 이제 더 이상 새로운 STI 테이블을 허용하지 않습니다. 왜냐하면:

  • 테이블의 행 수가 많아져야 할 때, 테이블을 작게 유지해야 하는 데 이는 큰 문제가 됩니다.
  • 추가적인 인덱스가 필요하며, 가벼운 잠금 사용량이 늘어나면 포화로 인한 사건이 발생할 수 있습니다.
  • 값으로 모든 데이터를 필터링해야 하는 오버헤드가 발생하여 읽기 시 페이지 액세스가 늘어납니다.
  • 객체에 대한 올바른 클래스를 로드하기 위해 class_name을 사용하지만, 클래스 이름을 저장하는 것은 비용이 많이 들고 불필요합니다.

STI를 사용하는 대신 다음 대안을 고려하십시오:

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

만약 위의 단점과 대안을 모두 고려한 후에도 STI가 현재의 문제에 대한 유일한 해결책이라면, EnumInheritance concern을 사용하여 레코드에 클래스 이름을 저장하는 문제를 피할 수 있습니다.

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
  ...

STI 또는 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
  end
  ...