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

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

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

기존 데이터베이스 열에 외래 키를 추가하려면 데이터베이스 구조 변경 및 잠재적인 데이터 변경이 필요합니다. 테이블이 사용 중인 경우 일관성이 없는 데이터가 있는 것으로 가정해야 합니다.

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

  1. GitLab 버전 N.M: NOT VALID 외래 키 제약 조건을 열에 추가하여 GitLab이 일관성 없는 레코드를 생성하지 않도록 보장합니다.
  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가 이 작업을 해주며, 외래 키가 이미 있는지도 확인합니다.

경고: 소스 및 대상 테이블이 동일하지 않은 한 마이그레이션 파일 당 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 유효성 검사를 진행할 수 있습니다.

저영효성 검사를 저영(가장 적은 영향이 있는) 시간에 예약

  1. FK를 유효성 검사하도록 예약
  2. MR이 배포되었는지 및 FK가 프로덕션 환경에서 유효한지 확인
  3. 외래 키를 동기적으로 유효성 검사하는 마이그레이션을 추가.

FK를 유효성 검사하도록 예약

  1. 외래 키를 준비하여 비동기로 유효성을 검사하는 포스트-디플로이먼트 마이그레이션을 포함한 MR(합병 요청)를 만듭니다.
  2. 외래 키를 동기적으로 유효성을 검사하기 위한 마이그레이션을 추가하는 후속 이슈를 만듭니다.
  3. 비동기 외래 키를 준비하는 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가 프로덕션 환경에서 유효한지 확인합니다

  1. /chatops run auto_deploy status <merge_sha>를 사용하여 GitLab.com에서 ChatOps를 이용하여 포스트-디플로이 마이그레이션이 실행되었는지 확인합니다. 출력이 db/gprd로 나오면, 포스트-디플로이 마이그레이션이 프로덕션 데이터베이스에서 실행된 것입니다. 자세한 내용은 GitLab.com에서 포스트-디플로이 마이그레이션이 실행되었는지 확인하는 방법을 참조하십시오.
  2. FK가 주말에 유효성을 검사할 수 있도록 다음 주까지 기다립니다.
  3. 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 변경사항 테스트하기

합병 요청을 생성하기 전에 로컬에서 데이터베이스 외래 키 변경사항을 테스트해야 합니다.

비동기적으로 확인된 외래 키 확인하기

로컬 환경에서 비동기적 헬퍼를 사용하여 외래 키를 확인하는 변경사항을 테스트합니다:

  1. 레일즈 콘솔에서 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 콘솔을 열고 \d+ table_name 명령을 실행하여 외래 키가 유효한지 확인합니다. 성공적인 검증은 외래 키 정의에서 NOT VALID이 삭제됩니다. GDK 명령인 gdk psql을 사용하여 PostgreSQL 콘솔을 열고 명령을 실행합니다.