단일 테이블 상속
GitLab v19.4요약
Summary: 단일 테이블 상속(Single Table Inheritance, STI)으로 새 테이블을 설계하지 않습니다. STI는 하나의 테이블에 서로 다른 타입의 레코드를 저장하는 데이터베이스 설계 패턴입니다. 새 STI 테이블을 더 이상 허용하지 않는 이유는 다음과 같습니다.
Summary: 단일 테이블 상속(Single Table Inheritance, STI)으로 새 테이블을 설계하지 않습니다. STI를 패턴으로 사용하는 기존 테이블에서는 새 타입 추가를 피하고, 별도 테이블로 분리하는 방안을 검토합니다.
STI는 하나의 테이블에 서로 다른 타입의 레코드를 저장하는 데이터베이스 설계 패턴입니다. 이러한 레코드는 공유하는 칼럼의 부분 집합과, 해당 레코드를 어떤 객체로 표현할지 애플리케이션에 알려 주는 또 하나의 칼럼을 가집니다. 예를 들어 서로 다른 두 종류의 SSH 키를 같은 테이블에 저장하는 데 쓸 수 있습니다. ActiveRecord는 이를 활용하며, STI 사용을 더 편하게 해 주는 기능도 몇 가지 제공합니다.
새 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를 비활성화하지 않으면 예상하지 못한 코드나 연관 관계가 로드되어 업그레이드 중에 의도하지 않은 부작용이나 실패가 발생할 수 있습니다.
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
...