기존 열에 외래 키 제약 조건 추가
외래 키는 관련된 데이터베이스 테이블 간 일관성을 보장합니다. 현재 데이터베이스 검토 프로세스는 다른 테이블의 레코드를 참조하는 테이블을 만들 때 외래 키를 추가할 것을 항상 권장합니다.
Rails 버전 4부터는 Rails에 데이터베이스 테이블에 외래 키 제약 조건을 추가하는 마이그레이션 도우미가 포함됩니다. Rails 4 이전에는 연관성 정의에서 dependent
옵션이 유일한 일관성 보장 방법이었습니다. 응용 프로그램 수준에서 데이터 일관성을 보장하는 것은 불행한 경우에 실패할 수 있어 테이블에 일관성이 없는 데이터가 남을 수 있습니다. 이는 대부분의 경우에서 틀린 영향을 미치며, 이는 데이터베이스 수준에서 일관성을 보장하는 프레임워크 지원이 없었던 이전 테이블에 주로 영향을 줍니다. 이러한 데이터 불일치는 예기치 않은 응용프로그램 동작이나 버그를 유발할 수 있습니다.
기존 데이터베이스 열에 외래 키를 추가하려면 데이터베이스 구조 변경 및 잠재적인 데이터 변경이 필요합니다. 테이블이 사용 중인 경우 일관성이 없는 데이터가 있는 것으로 가정해야 합니다.
기존 열에 외래 키 제약 조건 추가 방법:
- GitLab 버전
N.M
:NOT VALID
외래 키 제약 조건을 열에 추가하여 GitLab이 일관성 없는 레코드를 생성하지 않도록 보장합니다. - GitLab 버전
N.M
: 기존 레코드를 수정하거나 정리하기 위한 데이터 마이그레이션을 추가합니다. - GitLab 버전
N.M+1
: 외래 키를VALID
로 만들어 전체 테이블을 유효성 검사합니다.
예제
다음과 같은 테이블 구조를 고려해보세요.
users
테이블:
-
id
(정수, 기본 키) -
name
(문자열)
emails
테이블:
-
id
(정수, 기본 키) -
user_id
(정수) -
email
(문자열)
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 레코드), 백그라운드 마이그레이션을 생성하는 것이 더 좋습니다. 확실하지 않다면 데이터베이스 팀에 조언을 구하세요.
데이터 마이그레이션에 이메일 테이블의 레코드를 정리하는 예시:
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
# 데이터 불일치가 응용프로그램의 이전 및 배포 후 버전에 영향을 미치지 않을 때는 무시할 수 있습니다.
# 여기서는 `users` 테이블의 연결된 레코드가 더 이상 없는 `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를 유효성 검사하도록 예약
- 외래 키를 준비하여 비동기로 유효성을 검사하는 포스트-디플로이먼트 마이그레이션을 포함한 MR(합병 요청)를 만듭니다.
- 외래 키를 동기적으로 유효성을 검사하기 위한 마이그레이션을 추가하는 후속 이슈를 만듭니다.
- 비동기 외래 키를 준비하는 MR에 후속 이슈를 언급하는 코멘트를 추가합니다.
아래 블록에서 비동기 도우미를 사용하여 외래 키를 유효성 검사하는 예시를 확인할 수 있습니다. 이 마이그레이션은 외래 키 이름을 postgres_async_foreign_key_validations
테이블에 입력합니다. 주말에 실행되는 프로세스가 이 테이블에서 외래 키를 가져와 유효성을 검사합니다.
# in db/post_migrate/
FK_NAME = :fk_be5624bf37
# TODO: FK to be validated synchronously in issue or merge request
def up
# `some_column` can be an array of columns, and is not mandatory if `name` is supplied.
# `name` takes precedence over other arguments.
prepare_async_foreign_key_validation :ci_builds, :some_column, name: FK_NAME
# Or in case of partitioned tables, use:
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
# Or in case of partitioned tables, use:
unprepare_partitioned_async_foreign_key_validation :p_ci_builds, :some_column, name: FK_NAME
end
MR가 배포되고 FK가 프로덕션 환경에서 유효한지 확인합니다
-
/chatops run auto_deploy status <merge_sha>
를 사용하여 GitLab.com에서 ChatOps를 이용하여 포스트-디플로이 마이그레이션이 실행되었는지 확인합니다. 출력이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 변경사항 테스트하기
합병 요청을 생성하기 전에 로컬에서 데이터베이스 외래 키 변경사항을 테스트해야 합니다.
비동기적으로 확인된 외래 키 확인하기
로컬 환경에서 비동기적 헬퍼를 사용하여 외래 키를 확인하는 변경사항을 테스트합니다:
- 레일즈 콘솔에서
Feature.enable(:database_async_foreign_key_validation)
을 실행하여 기능 플래그를 활성화합니다. -
bundle exec rails db:migrate
를 실행하여 비동기적 확인 테이블에 항목을 생성합니다. -
bundle exec rails gitlab:db:validate_async_constraints:all
을 실행하여 모든 데이터베이스에서 외래 키가 비동기적으로 확인될 수 있도록 합니다. - PostgreSQL 콘솔을 열고
\d+ table_name
명령을 실행하여 외래 키가 유효한지 확인합니다. 성공적인 검증은 외래 키 정의에서NOT VALID
이 삭제됩니다. GDK 명령인gdk psql
을 사용하여 PostgreSQL 콘솔을 열고 명령을 실행합니다.