기존 열에 외래 키 제약 조건 추가

외래 키는 관련된 데이터베이스 테이블 간의 일관성을 보장합니다. 현재 데이터베이스 검토 프로세스는 다른 테이블의 레코드를 참조하는 테이블을 생성할 때 항상 외래 키를 추가할 것을 권장합니다.

Rails 버전 4부터는 Rails가 데이터베이스 테이블에 외래 키 제약 조건을 추가하는 마이그레이션 도우미를 포함하고 있습니다. Rails 4 이전에는 연관 정의의 dependent 옵션이 데이터 일관성을 보장하는 유일한 방법이었습니다. 응용 프로그램 수준에서 데이터 일관성을 보장하는 것은 불행한 경우에 실패할 수 있으므로 테이블에 일관성을 보장하는 프레임워크 지원이 없었던 이전 테이블에 주로 영향을 미칩니다. 이러한 데이터 불일치는 예기치 않은 응용프로그램 동작 또는 버그를 유발할 수 있습니다.

기존 데이터베이스 열에 외래 키 제약 조건을 추가하는 것은 데이터베이스 구조 변경과 잠재적인 데이터 변경을 필요로 합니다. 해당 테이블이 사용 중인 경우, 일관성이없는 데이터가 있다고 가정해야 합니다.

기존 열에 외래 키 제약 조건을 추가하려면:

  1. GitLab 버전 N.M: 일관성이 없는 레코드를 생성하지 않도록 NOT VALID 외래 키 제약 조건을 해당 열에 추가합니다.
  2. GitLab 버전 N.M: 기존 레코드를 수정하거나 정리하는 데이터 마이그레이션을 추가합니다.
  3. 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는 이를 대신 처리하고 외래 키가 이미 존재하는지 확인합니다.

caution
소스 및 대상 테이블이 동일하지 않은 한, 마이그레이션 파일 당 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
    # 데이터 일관성이 배포 전 및 후 버전의 응용 프로그램에 영향을 미치지 않는 경우 무작위 작업이 될 수 있습니다.
    # 이 경우에는 `users` 테이블에서 연관된 레코드가 더 이상 없는 `emails` 테이블에 레코드가 있을 수 있습니다.
  end
end

외래 키 유효성 검사

외래 키를 유효성 검사하면 전체 테이블을 스캔하여 각 관계가 올바른지 확인합니다. 다행히도 이 작업을 실행하는 동안 소스 테이블(users)은 잠기지 않습니다.

note
배치 백그라운드 마이그레이션을 사용할 때 외래 키 유효성 검사는 다음 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에 영향을 최소화하기 위해 주말에 비동기적으로 유효성 검사를 수행할 수 있습니다. 일반적으로 트래픽이 적고 배포가 적은 주말에 외래 키 유효성 검사를 낮은 수준의 위험으로 진행할 수 있습니다.

낮은 영향 시간에 외래 키 유효성 검사 일정화

  1. FK를 유효성 검사할 수 있도록 일정화
  2. MR이 배포되고 FK가 프로덕션에서 유효한지 확인
  3. 외래 키를 동기적으로 유효성 검사하는 마이그레이션 추가

FK를 유효성 검사할 수 있도록 일정화

  1. 비동기적으로 FK를 준비하는 포스트-배포 마이그레이션을 포함하는 MR을 생성하여 외래 키를 준비합니다.
  2. 외래 키를 동기적으로 유효성 검사하는 마이그레이션을 추가하기 위한 후속 이슈를 작성합니다.
  3. 비동기적인 외래 키 준비 MR에 후속 이슈를 언급하는 코멘트를 추가합니다.

비동기 도우미를 사용하여 외래 키를 유효성 검사하는 예시:

# db/post_migrate/ 내에서

FK_NAME = :fk_be5624bf37

# TODO: FK를 동기적으로 유효성 검사할 예정인 이슈나 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가 프로덕션에서 유효한지 확인

  1. ChatOps를 사용하여 GitLab.com에서 포스트-배포 마이그레이션이 실행되었는지 확인합니다. /chatops run auto_deploy status <merge_sha>를 사용합니다. 출력이 db/gprd를 반환하면 포스트-배포 마이그레이션이 프로덕션 데이터베이스에서 실행되었습니다. 자세한 내용은 GitLab.com에서 포스트-배포 마이그레이션이 실행되었는지 확인하는 방법을 참조하십시오.
  2. FK가 주말에 유효성 검사되었는지 확인하기 위해 다음 주말까지 기다립니다.
  3. Database Lab을 사용하여 유효성 검사가 성공적으로 수행되었는지 확인합니다. 외래 키가 NOT VALID로 나오지 않는지 확인하십시오.

FK 동기적으로 유효성 검증하는 마이그레이션 추가

외래 키가 프로덕션 데이터베이스에서 유효한 경우, 외래 키를 동기적으로 유효성을 검증하는 두 번째 Merge Request을 생성합니다. 스키마 변경은 이 두 번째 Merge Request에서 structure.sql에 업데이트되고 커밋되어야 합니다. 동기적인 마이그레이션은 GitLab.com에서는 no-op(아무것도 하지 않음)으로 처리되지만, 다른 설치에 대한 예상대로 여전히 마이그레이션을 추가해야 합니다. 아래 블록은 이전의 비동기적인 예제에 대한 두 번째 마이그레이션을 생성하는 방법을 보여줍니다.

caution
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을 생성하기 전에 로컬에서 데이터베이스 외래 키 변경을 테스트해야 합니다.

비동기로 유효성을 검증한 외래 키 확인

로컬 환경에서 비동기 도우미를 사용하여 외래 키를 유효성을 검증하는 변경 사항을 테스트합니다:

  1. Rails 콘솔에서 Feature.enable(:database_async_foreign_key_validation) 실행하여 피처 플래그를 활성화합니다.
  2. bundle exec rails db:migrate를 실행하여 비동기 유효성 확인 테이블에 항목을 만듭니다.
  3. bundle exec rails gitlab:db:validate_async_constraints:all을 실행하여 모든 데이터베이스에서 외래 키를 비동기적으로 유효성을 검증합니다.
  4. PostgreSQL 콘솔을 열고, 외래 키가 유효한지 확인하기 위해 gdk psql 명령어를 사용하여 커맨드 \d+ table_name을 실행합니다. 성공적인 유효성 검증은 외래 키 정의에서 NOT VALID를 제거합니다.