기존 열에 외래 키 제약 조건 추가하기
외래 키는 관련된 데이터베이스 테이블 간의 일관성을 보장합니다. 현재 데이터베이스 검토 과정은 다른 테이블에서 레코드를 참조하는 테이블을 만들 때 항상 외래 키를 추가하도록 권장합니다.
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 레코드), 백그라운드 마이그레이션을 만드는 것이 좋습니다. 확실하지 않은 경우 데이터베이스 팀에 조언을 구하세요.
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
# 데이터 일관성이 응용 프로그램의 배포 전 및 후 버전에 영향을 미치지 않을 경우 no-op일 수 있습니다.
# 이 경우, `emails` 테이블에 레코드는 더 이상 `users` 테이블의 연결된 레코드가 없을 수 있습니다.
end
end
외래 키 검증
외래 키 검증은 전체 테이블을 스캔하고 각 관계가 올바른지 확인합니다. 다행히도 실행 중에 소스 테이블인 users
는 잠그지 않습니다.
외래 키를 검증하기 위한 마이그레이션 파일:
# frozen_string_literal: true
class ValidateForeignKeyOnEmailUsers < Gitlab::Database::Migration[2.1]
def up
validate_foreign_key :emails, :user_id
end
def down
# 불일치하는 데이터를 롤백하지 않는 경우 안전한 no-op일 수 있습니다.
end
end
비동기로 외래 키 검증
매우 큰 테이블의 경우 외래 키 검증은 여러 시간에 걸쳐서 실행하는 것이 도전적일 수 있습니다. autovacuum
과 같은 필요한 데이터베이스 작업을 실행할 수 없으며, GitLab.com에서는 마이그레이션이 완료될 때까지 배포 프로세스가 차단됩니다.
GitLab.com에 영향을 최소화하기 위해 주말 시간에 외래 키 검증을 비동기로 실행하는 프로세스가 있습니다. 일반적으로 트래픽이 적고 배포 횟수도 적기 때문에 이때 외래 키 검증을 수행할 수 있습니다.
저영향 시간에 외래 키 검증 예약
검증할 외래 키 예약
- 비동기적으로 외래 키를 준비하는 사후 배포 마이그레이션을 포함하는 MR을 생성하여 외래 키를 준비합니다.
- 외래 키를 동기적으로 검증하는 마이그레이션을 추가하는 후속 이슈를 생성합니다.
- 비동기 외래 키를 준비하는 MR에 후속 이슈를 언급하는 코멘트를 추가합니다.
비동기 도우미를 사용하여 외래 키를 검증하는 예제:
# in db/post_migrate/
FK_NAME = :fk_be5624bf37
# TODO: 이슈나 MR에서 외래 키를 동기적으로 검증할 것
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>
를 사용하여 GitLab.com에서 챗옵스를 이용하여 포스트-디플로이 마이그레이션이 실행되었는지 확인합니다. 출력 결과가db/gprd
로 나오면, 포스트-디플로이 마이그레이션이 프로덕션 데이터베이스에서 실행된 것입니다. 자세한 정보는 GitLab.com에서 포스트-디플로이 마이그레이션이 실행되었는지 확인하는 방법을 참조하세요. - FK가 주말에 유효화되도록 다음 주까지 기다립니다.
- 검증이 성공적으로 이루어졌는지 확인하기 위해 Database Lab를 사용하세요. 출력 결과가 외래 키가
NOT VALID
로 표시되지 않도록 확인합니다.
FK를 동기적으로 검증하는 마이그레이션 추가
프로덕션 데이터베이스에서 외래 키가 유효해진 후, 외래 키를 동기적으로 유효화하는 두 번째 Merge Request을 생성하세요. 스키마 변경 내용은 이 두 번째 Merge Request의 structure.sql
에 업데이트되고 커밋되어야 합니다. 동기적 마이그레이션은 GitLab.com에서 무효 조치가 되지만, 여전히 다른 설치에 대한 기대에 따라 마이그레이션을 추가해야 합니다. 아래 블록에서는 이전 비동기적 예제에 대한 두 번째 마이그레이션을 생성하는 방법을 보여줍니다.
validate_foreign_key
로 Merge하기 전에 프로덕션에서 외래 키가 유효한지 확인하세요. 두 번째 마이그레이션이 실행되기 전에 검증이 실행되지 않은 경우 두 번째 마이그레이션이 실행될 때 외래 키가 동기적으로 유효화됩니다.# 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 변경 테스트
Merge Request을 생성하기 전에 지역 환경에서 데이터베이스 외래 키 변경을 테스트해야 합니다.
비동기적으로 검증된 외래 키 확인
외래 키를 검증하는 비동기적 헬퍼를 로컬 환경에서 사용하여 변경 내용을 테스트하세요:
- Rails 콘솔에서
Feature.enable(:database_async_foreign_key_validation)
을 실행하여 피처 플래그를 활성화합니다. -
bundle exec rails db:migrate
를 실행하여 비동기 검증 테이블에 항목을 만듭니다. -
bundle exec rails gitlab:db:validate_async_constraints:all
을 실행하여 모든 데이터베이스에서 외래 키가 비동기적으로 유효화되도록합니다. - 외래 키를 확인하기 위해 GDK 명령어인
gdk psql
을 사용하여 PostgreSQL 콘솔을 열고 명령어\d+ table_name
을 실행하여 외래 키가 유효한지 확인합니다. 성공적인 유효화는 외래 키 정의에서NOT VALID
를 제거합니다.