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

외부 키는 관련된 데이터베이스 테이블 간의 일관성을 보장합니다. 현재 데이터베이스 검토 프로세스는 다른 테이블에서 기록을 참조하는 테이블을 생성할 때 언제나 외부 키 추가를 장려합니다.

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는 이를 대신 처리하고 외부 키가 이미 존재하는지도 확인합니다.

경고: 원본 테이블과 대상 테이블이 동일하지 않은 한, 마이그레이션 파일 당 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)을 잠글 필요가 없습니다.

참고: 배치 백그라운드 마이그레이션을 사용하는 경우 외부 키 유효성 검사는 다음 GitLab 릴리스에서 수행되어야 합니다.

외부 키를 유효성 검사하는 마이그레이션 파일:

# 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에 미치는 영향을 최소화하기 위해, 주말 시간에 외래 키를 비동기적으로 유효성을 검사하는 프로세스가 있습니다. 일반적으로 트래픽이 적고 배포가 적기 때문에 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를 이슈나 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 run auto_deploy status <merge_sha> 명령을 사용하여 GitLab.com에서 포스트-배포 마이그레이션이 실행되었는지 확인하십시오. 출력이 db/gprd를 반환하면, 포스트-배포 마이그레이션이 프로덕션 데이터베이스에서 실행되었습니다. 자세한 정보는 GitLab.com에서 포스트-배포 마이그레이션이 실행되었는지 확인하는 방법을 참조하십시오.
  2. 다음 주가 되어 FK가 주말에 유효성 검사된 것을 기다리십시오.
  3. Database Lab을 사용하여 유효성 검사가 성공적으로 수행되었는지 확인하십시오. 출력이 외래 키가 NOT VALID로 표시되지 않도록 확인하십시오.

외래 키를 동기적으로 유효성 검사하는 마이그레이션 추가

프로덕션 데이터베이스에서 외래 키가 유요해진 후, 외래 키를 동기적으로 유효성을 검사하는 두 번째 병합 요청을 생성하십시오. 이 두 번째 병합 요청에서는 스키마 변경 사항이 업데이트되고 structure.sql에 커밋되어야 합니다. 동기적 마이그레이션은 GitLab.com에서는 무효이지만 다른 설치에는 기대했던 대로 마이그레이션을 추가해야 합니다. 아래 블록에서는 이전 비동기적 예시를 위한 두 번째 마이그레이션을 작성하는 방법을 보여줍니다.

경고: 동기적으로 외래 키를 유효성 검사하기 전에 프로덕션에서 외래 키가 유효한지 확인하십시오. 두 번째 마이그레이션이 실행되기 전에 유효성 검사가 실행되지 않았다면, 두 번째 마이그레이션 실행 시 동기적으로 외래 키가 유효성을 검사합니다.

# 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+ 테이블_이름 명령을 실행하여 외래 키가 유효한지 확인하십시오. 성공적인 유효성 검사는 외래 키 정의에서 NOT VALID를 제거합니다.