기존 열에 외래 키 제약 조건 추가
외래 키는 관련 데이터베이스 테이블 간의 일관성을 보장합니다. 현재 데이터베이스 검토 프로세스는 항상 다른 테이블의 기록을 참조할 때 외래 키를 추가하도록 권장합니다. foreign keys
Rails 버전 4부터, Rails는 데이터베이스 테이블에 외래 키 제약 조건을 추가하기 위한 마이그레이션 도우미를 포함합니다. Rails 4 이전에는 어떤 수준의 일관성을 보장하는 유일한 방법은 연관 정의의 dependent
옵션이었습니다. 애플리케이션 수준에서 데이터 일관성을 보장하는 것은 불행한 경우에 실패할 수 있으므로 테이블에 일관성 없는 데이터가 남게 될 수 있습니다. 이는 주로 데이터베이스 수준에서 일관성을 보장하는 프레임워크 지원이 없었던 오래된 테이블에 영향을 미칩니다. 이러한 데이터 불일치는 예기치 않은 애플리케이션 동작이나 버그를 유발할 수 있습니다.
기존 데이터베이스 열에 외래 키를 추가하려면 데이터베이스 구조 변경 및 잠재적인 데이터 변경이 필요합니다. 테이블이 사용 중인 경우, 항상 일관성 없는 데이터가 있을 것이라고 가정해야 합니다.
기존 열에 외래 키 제약 조건을 추가하려면:
- GitLab 버전
N.M
: GitLab이 일관성 없는 레코드를 생성하지 않도록 열에NOT VALID
외래 키 제약 조건을 추가합니다. - GitLab 버전
N.M
: 기존 레코드를 수정하거나 정리하기 위해 데이터 마이그레이션을 추가합니다. - GitLab 버전
N.M+1
: 외래 키를VALID
로 만들어 전체 테이블을 검증합니다.
예제
다음 테이블 구조를 고려하십시오:
users
테이블:
-
id
(integer, primary key) -
name
(string)
emails
테이블:
-
id
(integer, primary key) -
user_id
(integer) -
email
(string)
ActiveRecord
에서 관계를 표현합니다:
class User < ActiveRecord::Base
has_many :emails
end
class Email < ActiveRecord::Base
belongs_to :user
end
문제: 사용자가 제거되면 제거된 사용자와 관련된 이메일 기록이 emails
테이블에 남습니다:
user = User.find(1)
user.destroy
emails = Email.where(user_id: 1) # 삭제된 사용자의 이메일을 반환합니다
유효하지 않은 레코드 방지
레코드 변경 시 일관성을 강제하는 NOT VALID
외래 키 제약 조건을 테이블에 추가하십시오.
위의 예제에서는 여전히 emails
테이블의 레코드를 업데이트할 수 있습니다. 그러나 존재하지 않는 값으로 user_id
를 업데이트하려고 하면 제약 조건이 데이터베이스 오류를 유발합니다.
NOT VALID
외래 키를 추가하는 마이그레이션 파일:
class AddNotValidForeignKeyToEmailsUser < Gitlab::Database::Migration[2.1]
def up
add_concurrent_foreign_key :emails, :users, column: :user_id, on_delete: :cascade, validate: false
end
def down
remove_foreign_key_if_exists :emails, column: :user_id
end
end
유효성을 검사하지 않고 외래 키를 추가하는 것은 빠른 작업입니다. 새로운 데이터에 대해 제약 조건을 시행할 수 있기 전에 테이블에 대해 짧은 잠금만 필요합니다.
여전히 높은 트래픽과 큰 테이블의 잠금 재시도를 활성화하고 싶습니다. add_concurrent_foreign_key
는 이를 수행하고 외래 키가 이미 존재하는지 여부도 확인합니다.
경고:
소스 테이블과 대상을 동일하게 하지 않으면 마이그레이션 파일당 add_foreign_key
또는 add_concurrent_foreign_key
제약 조건을 한 번 이상 사용하지 마십시오.
기존 레코드를 수정하기 위한 데이터 마이그레이션
여기서의 접근 방식은 데이터 양과 정리 전략에 따라 다릅니다. 데이터베이스 쿼리를 통해 “잘못된” 레코드를 찾을 수 있고 레코드 수가 많지 않다면, 데이터 마이그레이션은 Rails 마이그레이션에서 실행될 수 있습니다.
데이터 양이 많을 경우(>1000 레코드), 백그라운드 마이그레이션을 만드는 것이 좋습니다. 확실하지 않은 경우 데이터베이스 팀에 문의하여 조언을 받으십시오.
데이터베이스 마이그레이션에서 emails
테이블의 레코드를 정리하기 위한 예시:
class RemoveRecordsWithoutUserFromEmailsTable < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
class Email < ActiveRecord::Base
include EachBatch
end
def up
Email.where('user_id NOT IN (SELECT id FROM users)').each_batch do |relation|
relation.delete_all
end
end
def down
# 데이터 불일치가 애플리케이션의 배포 전후 버전에 영향을 미치지 않을 때는 아무 작업도 하지 않아도 됩니다.
# 이 경우, `emails` 테이블에 사용자의 레코드가 더 이상 존재하지 않는 경우가 있을 수 있습니다.
end
end
외래 키 검증
외래 키 검증은 전체 테이블을 스캔하여 각 관계가 올바른지 확인합니다. 다행히도, 실행 중에 소스 테이블(users
)이 잠기지 않습니다.
참고:
배치 백그라운드 마이그레이션을 사용할 때, 외래 키 검증은 다음 GitLab 릴리즈에서 발생해야 합니다.
외래 키를 검증하는 마이그레이션 파일:
# frozen_string_literal: true
class ValidateForeignKeyOnEmailUsers < Gitlab::Database::Migration[2.1]
def up
validate_foreign_key :emails, :user_id
end
def down
# 데이터 불일치를 롤백하지 않는다면 안전하게 아무 작업도 하지 않아도 됩니다.
end
end
비동기적으로 외래 키 검증
매우 큰 테이블의 경우, 외래 키 검증은 여러 시간에 걸쳐 실행될 때 관리하기 어려울 수 있습니다. autovacuum
과 같은 필수 데이터베이스 작업이 실행되지 못하고, GitLab.com에서는 배포 프로세스가 마이그레이션이 완료될 때까지 차단됩니다.
GitLab.com에 미치는 영향을 제한하기 위해 주말 시간에 비동기적으로 검증하는 프로세스가 존재합니다. 일반적으로 트래픽이 낮고 배포가 적기 때문에 FK 검증은 낮은 수준의 위험으로 진행할 수 있습니다.
낮은 영향을 주는 시간에 외래 키 검증 예약
FK를 검증하도록 예약하기
- 비동기 검증을 위해 외래 키를 준비하는 배포 후 마이그레이션을 포함하는 병합 요청을 만듭니다.
- 외래 키를 동기적으로 검증하는 마이그레이션을 추가하기 위한 후속 이슈를 생성합니다.
- 비동기 외래 키를 준비하는 병합 요청에 후속 이슈를 언급하는 댓글을 추가합니다.
비동기 도우미를 사용하여 외래 키를 검증하는 예시는 아래 블록에서 확인할 수 있습니다. 이 마이그레이션은 외래 키 이름을 postgres_async_foreign_key_validations
테이블에 입력합니다. 주말에 실행되는 프로세스는 이 테이블에서 외래 키를 가져와 검증을 시도합니다.
# in db/post_migrate/
FK_NAME = :fk_be5624bf37
# TODO: 이슈나 병합 요청에서 동기적으로 검증할 외래 키
def up
# `some_column`은 열 배열이 될 수 있으며, `name`이 제공된 경우 필수는 아닙니다.
# `name`은 다른 인수보다 우선합니다.
prepare_async_foreign_key_validation :ci_builds, :some_column, name: FK_NAME
# 또는 분할 테이블의 경우:
prepare_partitioned_async_foreign_key_validation :p_ci_builds, :some_column, name: FK_NAME
end
def down
unprepare_async_foreign_key_validation :ci_builds, :some_column, name: FK_NAME
# 또는 분할 테이블의 경우:
unprepare_partitioned_async_foreign_key_validation :p_ci_builds, :some_column, name: FK_NAME
end
MR이 배포되었고 FK가 프로덕션에서 유효한지 확인하십시오
-
/chatops run auto_deploy status <merge_sha>
를 사용하여 ChatOps에서 GitLab.com에서 배포 후 마이그레이션이 실행되었는지 확인하십시오. 출력이db/gprd
를 반환하면 배포 후 마이그레이션이 프로덕션 데이터베이스에서 실행되었습니다. 자세한 내용은 GitLab.com에서 배포 후 마이그레이션이 실행되었는지 확인하는 방법을 참조하세요. -
FK가 주말 동안 검증될 수 있도록 다음 주까지 기다립니다.
-
Database Lab를 사용하여 검증이 성공했는지 확인합니다. 출력에 외래 키가
NOT VALID
로 표시되지 않아야 합니다.
FK를 동기적으로 검증하는 마이그레이션 추가
프로덕션 데이터베이스에서 외래 키가 유효하면, 외래 키를 동기적으로 검증하는 두 번째
병합 요청을 생성합니다. 스키마 변경사항은 이 두 번째 병합 요청에서 structure.sql
에 업데이트하고 커밋해야 합니다.
동기적 마이그레이션은 GitLab.com에서 no-op 결과를 초래하지만, 다른 설치를 위해 예상대로 마이그레이션을 추가해야 합니다. 아래 블록은
이전 비동기 예를 위한 두 번째 마이그레이션을 생성하는 방법을 보여줍니다.
경고:
두 번째 마이그레이션을 validate_foreign_key
와 함께 병합하기 전에 프로덕션에서 외래 키가 유효한지 확인하십시오.
검증이 실행되기 전에 두 번째 마이그레이션이 배포되면, 두 번째 마이그레이션 실행 시 외래 키가 동기적으로 검증됩니다.
# in db/post_migrate/
FK_NAME = :fk_be5624bf37
def up
validate_foreign_key :ci_builds, :some_column, name: FK_NAME
end
def down
# 불일치 데이터를 롤백하지 않으면 안전하게 no-op일 수 있습니다.
end
end
로컬에서 데이터베이스 FK 변경 사항 테스트
병합 요청을 생성하기 전에 로컬에서 데이터베이스 외래 키 변경 사항을 테스트해야 합니다.
비동기적으로 검증된 외래 키 확인
비동기 도우미를 사용하여 로컬 환경에서 외래 키 검증 변경 사항을 테스트하십시오:
-
Rails 콘솔에서
Feature.enable(:database_async_foreign_key_validation)
을 실행하여 기능 플래그를 활성화합니다. -
bundle exec rails db:migrate
를 실행하여 비동기 검증 테이블에 항목을 생성합니다. -
bundle exec rails gitlab:db:validate_async_constraints:all
를 실행하여 모든 데이터베이스에서 FK가 비동기적으로 검증됩니다. -
외래 키를 검증하려면, GDK 를 사용하여 PostgreSQL 콘솔을 열고
\d+ table_name
명령을 실행하여 외래 키가 유효한지 확인합니다. 성공적인 검증은 외래 키 정의에서NOT VALID
를 제거합니다.