단일 테이블 상속
GitLab v19.1요약: 단일 테이블 상속(Single Table Inheritance, STI)을 사용하는 새로운 테이블을 설계하지 마세요. STI는 단일 테이블에 여러 타입의 레코드를 저장하는 데이터베이스 설계 패턴입니다. 더 이상 새로운 STI 테이블을 허용하지 않는 이유는 다음과 같습니다:
요약: 단일 테이블 상속(Single Table Inheritance, STI)을 사용하는 새로운 테이블을 설계하지 마세요. STI 패턴을 사용하는 기존 테이블의 경우, 새로운 타입 추가를 피하고 별도의 테이블로 분리하는 것을 검토하세요.
STI는 단일 테이블에 여러 타입의 레코드를 저장하는 데이터베이스 설계 패턴입니다. 이 레코드들은 공유 칼럼의 부분 집합과, 해당 레코드를 어떤 객체로 표현할지 애플리케이션에 알려주는 또 다른 칼럼을 가집니다. 예를 들어, 두 가지 다른 유형의 SSH 키를 동일한 테이블에 저장하는 데 사용할 수 있습니다. ActiveRecord는 이를 활용하며 STI 사용을 보다 편리하게 만드는 몇 가지 기능을 제공합니다.
더 이상 새로운 STI 테이블을 허용하지 않는 이유는 다음과 같습니다:
-
테이블을 작게 유지하려는 노력과 달리, 많은 수의 행을 가진 테이블로 이어집니다.
-
추가 인덱스가 필요하여 경량 락(lightweight lock) 사용이 증가하며, 이러한 락의 포화는 장애를 유발할 수 있습니다.
-
모든 데이터를 특정 값으로 필터링해야 하는 오버헤드가 발생하여, 읽기 시 더 많은 페이지 접근이 이루어집니다.
-
객체에 맞는 올바른 클래스를 로드하기 위해
class_name을 사용하지만, 클래스 이름을 저장하는 것은 비용이 많이 들고 불필요합니다.
STI 대신 다음과 같은 대안을 고려하세요:
-
각 타입별로 다른 테이블을 사용하세요.
-
*_type칼럼 추가를 피하세요. 이는 향후 새로운 타입이 추가될 수 있음을 나타내는 코드 스멜이며, 나중에 리팩토링하기가 훨씬 어려워집니다. -
이미
_type칼럼을 사용하는 사실상의 STI 테이블이 있다면, 다음을 고려하세요:기존 데이터를 여러 테이블로 분리하세요.
-
기존 테이블은 유지하면서 새로운 타입을 새 테이블로 추가할 수 있도록 리팩토링하세요(예: 기본 클래스의 로직을 concern으로 이동).
위의 모든 단점과 대안을 충분히 고려한 후에도 STI가 유일한 해결책이라면, 열거형(enum) 타입과 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
...