외래 키와 연관성
모델에 연관성을 추가할 때 외래 키도 추가해야 합니다. 예를 들어, 다음과 같은 모델이 있다고 가정해보겠습니다:
class User < ActiveRecord::Base
has_many :posts
end
여기에 posts.user_id
열에 외래 키를 추가하세요. 이렇게 하면 데이터 일관성이 데이터베이스 수준에서 강제되며, 외래 키는 관련 데이터(예: 사용자 제거 시)를 빠르게 제거할 수 있도록 해줍니다. 이렇게 함으로써 Rails가 이를 처리할 필요가 없습니다.
마이그레이션에서 외래 키 추가
Gitlab::Database::MigrationHelpers
에 정의된 add_concurrent_foreign_key
를 사용하여 외래 키를 동시에 추가할 수 있습니다. 자세한 내용은 마이그레이션 스타일 가이드를 참조하세요.
기존 테이블에 외래 키를 안전하게 추가하려면 고아 행(orphanded rows)을 제거한 후에 외래 키를 추가해야 합니다. add_concurrent_foreign_key
메서드는 이를 처리하지 않으므로 매뉴얼으로 처리해야 합니다. 기존 열에 외래 키 제약 조건 추가도 확인하세요.
마이그레이션에서 외래 키 업데이트
가끔 외래 키 제약 조건을 유지하면서 열만 업데이트해야 할 수도 있습니다. 예를 들어, ON DELETE CASCADE
에서 ON DELETE SET NULL
로 또는 그 반대로 이동하는 경우입니다.
PostgreSQL은 서로 겹치는 외래 키를 추가하는 것을 방지하지 않습니다. 최근에 추가된 제약 조건을 적용합니다. 이를 통해 기존 외래 키를 결코 잃지 않고 외래 키를 대체할 수 있습니다.
외래 키를 대체하려면:
-
유효하지 않은 레코드를 방지하기 위해 새 외래 키를 추가
새 외래 키를 추가하기 전에 외래 키 제약 조건의 이름을 변경해야 합니다.
class ReplaceFkOnPackagesPackagesProjectId < Gitlab::Database::Migration[2.1] disable_ddl_transaction! NEW_CONSTRAINT_NAME = 'fk_new' def up add_concurrent_foreign_key(:packages_packages, :projects, column: :project_id, on_delete: :nullify, validate: false, name: NEW_CONSTRAINT_NAME) end def down with_lock_retries do remove_foreign_key_if_exists(:packages_packages, column: :project_id, on_delete: :nullify, name: NEW_CONSTRAINT_NAME) end end end
-
class ValidateFkNew < Gitlab::Database::Migration[2.1] NEW_CONSTRAINT_NAME = 'fk_new' # 새 외래 키를 추가하고 유효성 검증 in <link to MR or path to migration adding new FK> def up validate_foreign_key(:packages_packages, name: NEW_CONSTRAINT_NAME) end def down # 아무것도 하지 않음 end end
-
이전 외래 키를 제거:
class RemoveFkOld < Gitlab::Database::Migration[2.1] OLD_CONSTRAINT_NAME = 'fk_old' # 새 외래 키를 추가하고 유효성 검증 in <link to MR or path to migration adding new FK> def up remove_foreign_key_if_exists(:packages_packages, column: :project_id, on_delete: :cascade, name: OLD_CONSTRAINT_NAME) end def down # 여기서는 유효성 검사가 생략되므로, 롤백 시 별도의 마이그레이션에서 다시 유효성을 검사해야 합니다 add_concurrent_foreign_key(:packages_packages, :projects, column: :project_id, on_delete: :cascade, validate: false, name: OLD_CONSTRAINT_NAME) end end
역참조된 삭제
모든 외래 키는 ON DELETE
절을 정의해야 하며, 99%의 경우에는 CASCADE
로 설정해야 합니다.
인덱스
PostgreSQL에서 외래 키를 추가하면 해당 열이 자동으로 인덱싱되지 않기 때문에 동시에 인덱스도 추가해야 합니다. 이를 하지 않으면 역순 삭제가 매우 느려집니다.
외래 키 명명
기본적으로 Ruby on Rails는 외래 키에 대해 _id
접미사를 사용합니다. 따라서 두 개의 테이블 간 연관 관계에 대해서만 이 접미사를 사용해야 합니다. 외부 플랫폼의 ID를 참조하려는 경우 _xid
접미사를 권장합니다.
spec/db/schema_spec.rb
스펙은 _id
접미사가 있는 모든 열에 외래 키 제약 조건이 있는지를 테스트합니다. 따라서 해당 스펙이 실패하면 IGNORED_FK_COLUMNS
에 열을 추가하는 대신 FK 제약 조건을 추가하거나 다르게 명명하는 등을 고려해야 합니다.
종속적인 삭제
연관 관계를 정의할 때 dependent: :destroy
또는 dependent: :delete
와 같은 옵션을 정의해서는 안 됩니다. 이러한 옵션을 정의하는 것은 데이터베이스가 가장 효율적인 방법으로 데이터를 제거하도록 하는 것이 아니라 Rails가 데이터 제거를 처리하게 합니다.
다시 말해 다음과 같은 방법은 좋지 않으며 절대 사용하면 안 됩니다:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
이를 꼭 필요한 경우에만 사용해야 하며 먼저 데이터베이스 전문가의 승인을 받아야 합니다.
모델에 before_destroy
또는 after_destroy
콜백을 정의해서는 안 됩니다. 절대로 필요한 경우에만, 그리고 데이터베이스 전문가의 승인을 받은 경우에만 이를 사용해야 합니다. 예를 들어, 테이블의 각 행에 파일 시스템에서 파일이 있을 때 after_destroy
후크를 추가하는 것이 유혹스러울 수 있습니다. 그러나 이렇게 하면 모델에 비데이터베이스 논리가 도입되며, 외래 키를 사용하여 데이터를 삭제할 수 없게 되므로 파일 시스템 데이터가 잔류하게 됩니다. 그런 경우에는 대신 데이터를 제거하는 서비스 클래스를 사용해야 합니다.
관계가 여러 데이터베이스에 걸쳐 있는 경우 dependent: :destroy
나 위의 후크를 사용하는 것은 더 큰 문제를 야기합니다. 다른 데이터베이스 간 dependent: :nullify
와 dependent: :destroy
피하기에서 대체 방법에 대해 자세히 알아볼 수 있습니다.
has_one
연관 관계에서 대체 기본 키
가끔 has_one
관계를 사용하여 일대일 관계를 만드는 경우가 있습니다:
class User < ActiveRecord::Base
has_one :user_config
end
class UserConfig < ActiveRecord::Base
belongs_to :user
end
이러한 경우에는 연결된 테이블의 불필요한 id
열을 제거할 수 있는 기회가 있습니다. 예를 들어, 이 예제의 user_config.id
와 같이 테이블의 원본 ID를 연관된 테이블의 기본 키로 사용할 수 있습니다:
create_table :user_configs, id: false do |t|
t.references :users, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade }
...
end
default: nil
로 설정하여 기본 키 시퀀스가 생성되지 않도록하고, 기본 키가 자동으로 인덱스를 갖게 되므로 index: false
로 설정하여 중복 생성을 피합니다. 또한 새 기본 키를 모델에 추가해야 합니다:
class UserConfig < ActiveRecord::Base
self.primary_key = :user_id
belongs_to :user
end
외래 키를 기본 키로 사용하는 것은 공간을 절약할 수 있지만 배치 카운팅 및 서비스 핑에서 효율이 떨어질 수 있습니다. 테이블이 서비스 핑에 관련이 있다면 일반적인 id
열을 사용하는 것을 고려해야 합니다.