Single Table Inheritance
개요: 새로운 테이블을 설계할 때 Single Table Inheritance (STI)를 사용하지 마십시오. STI를 패턴으로 사용하는 기존 테이블의 경우 새로운 유형을 추가하지 않도록 하고 별도의 테이블로 분할을 고려하십시오.
STI는 하나의 테이블이 다른 유형의 레코드를 저장하는 데이터베이스 설계 패턴입니다. 이러한 레코드에는 일부 공통 열의 하위 집합이 있으며 응용 프로그램에 그 레코드가 어떤 객체를 대표해야 하는지 알려주는 다른 열이 있습니다. 이를 예로 들면, SSH 키의 두 가지 다른 유형을 동일한 테이블에 저장할 수 있습니다. ActiveRecord는 이를 활용하고 STI 사용을 더 편리하게 만드는 몇 가지 기능을 제공합니다.
우리는 더 이상 새로운 STI 테이블을 허용하지 않습니다. 왜냐하면:
- 테이블의 행 수가 많아져야 하는데, 테이블을 작게 유지해야 합니다.
- 가벼운 락을 사용하여 인덱스를 추가해야 하며, 포화되면 인시던트를 발생시킬 수 있는 가벼운 락 사용을 늘립니다.
- 값으로 모든 데이터를 필터링해야 하므로 오버헤드가 추가되어 읽기에 더 많은 페이지 액세스가 발생합니다.
- 객체에 대한 올바른 클래스를 로드하기 위해
class_name
을 사용하지만, 클래스 이름을 저장하는 것은 비용이 많이 들고 불필요합니다.
STI 대신 다음 대안을 고려해보십시오:
- 각 유형에 대해 다른 테이블을 사용합니다.
-
*_type
열을 추가하지 마십시오. 이는 앞으로 새로운 유형이 추가될 수 있음을 나타내는 코드 스멜일 수 있으며, 나중에 리팩터링하는 것이 훨씬 어려울 수 있습니다. - 이미
_type
열에 STI와 유사한 효과를 내는 테이블이 있는 경우 다음을 고려하십시오:- 기존 데이터를 여러 테이블로 분할합니다.
- 기존 로직을 concern으로 이동하여 새로운 테이블에 새 유형을 추가할 수 있도록 리팩터링합니다.
만약, 위의 모든 단점과 대안을 고려한 후에도 문제 해결을 위해 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를 사용하지 않아도) 마이그레이션에서도 STI를 비활성화하지 않으면 예상치 못한 코드 또는 연관을 로드하여 의도하지 않은 부작용이나 업그레이드 중 실패를 유발할 수 있습니다.
class SomeMigration < Gitlab::Database::Migration[2.1]
class Services < MigrationRecord
self.table_name = 'services'
self.inheritance_column = :_type_disabled
end
def up
...
모델에 EachBatch
외에 STI나 추가할 것이 없는 경우, 클래스를 정의하는 대신 도우미 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
...