외부 키 및 연관성
모델에 연관성을 추가할 때 외부 키도 추가해야 합니다. 예를 들어, 다음과 같은 모델이 있다고 가정해봅시다.
class User < ActiveRecord::Base
has_many :posts
end
여기서 posts.user_id
열에 외부 키를 추가하세요. 이렇게 하면 데이터 일관성이 데이터베이스 수준에서 강제됩니다. 외부 키는 또한 데이터베이스가 연결된 데이터를 매우 빠르게 제거할 수 있게 해줍니다(예: 사용자를 제거할 때 Rails가 이 작업을 수행하는 대신).
마이그레이션에서 외부 키 추가
외부 키는 Gitlab::Database::MigrationHelpers
에 정의된 add_concurrent_foreign_key
를 사용하여 병행으로 추가할 수 있습니다. 자세한 내용은 마이그레이션 스타일 가이드를 참조하세요.
기존 테이블에 외부 키를 안전하게 추가할 수 있는 것은 고아 행을 제거한 후입니다. 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' # 새 외부 키 추가: <link to MR or path to migration adding new FK> # 및 유효성 검사: <link to MR or path to migration validating 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
접미사를 사용합니다. 따라서 두 테이블 간의 연관성에 대해서만 이 접미사를 사용해야 합니다. 제3자 플랫폼의 ID를 참조하려면 _xid
접미사를 권장합니다.
spec/db/schema_spec.rb
스펙은 _id
접미사가 있는 모든 열이 외부 키 제약 조건을 가졌는지 테스트합니다. 따라서 해당 스펙이 실패하면 IGNORED_FK_COLUMNS
에 열을 추가하는 대신 외부 키 제약 조건을 추가하거나 다르게 명명하는 것을 고려해야 합니다.
종속적 삭제
연관성을 정의할 때 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
외래 키를 기본 키로 사용함으로써 공간을 절약할 수 있지만, Service Ping의 batch counting을 덜 효율적으로 만들 수 있습니다. 서비스 통계에 필요한 테이블이라면 일반적인 id
열을 사용하는 것을 고려해보세요.