다중 데이터베이스 마이그레이션

이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션에 대한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 자세한 정보는 다중 데이터베이스를 참조하십시오.

다중 데이터베이스의 설계(Geo 데이터베이스 제외)는 모든 분해된 데이터베이스가 동일한 구조(예: 스키마)를 가지지만 각 데이터베이스의 데이터가 다르다고 가정합니다. 이는 일부 테이블이 각 데이터베이스에서 데이터를 포함하지 않음을 의미합니다.

작업

사용된 구조에 따라 마이그레이션을 다음과 같이 분류할 수 있습니다.

  1. 구조 변경(DDL - 데이터 정의 언어) (예: ALTER TABLE).
  2. 데이터 변경(DML - 데이터 조작 언어) (예: UPDATE).
  3. 기타 쿼리 실행 (예: SELECT)는 마이그레이션의 목적상 DML로 취급됩니다.

Gitlab::Database::Migration[2.0]의 사용은 마이그레이션이 항상 단일 목적으로 이루어져야 함을 요구합니다. 마이그레이션은 애플리케이션이 db/structure.sql로 설명된 것처럼 분해된 모든 데이터베이스에서 정확히 동일한 구조를 필요로 합니다.

데이터 정의 언어(DDL)

DDL 마이그레이션에는 다음이 모두 포함됩니다.

  1. 테이블 생성 또는 제거 (예: create_table).
  2. 인덱스 추가 또는 제거 (예: add_index, add_concurrent_index).
  3. 외래 키 추가 또는 제거 (예: add_foreign_key, add_concurrent_foreign_key).
  4. 기본값이 있는 컬럼 추가 또는 제거 (예: add_column).
  5. 트리거 함수 생성 또는 제거 (예: create_trigger_function).
  6. 테이블에서 트리거 부착 또는 분리 (예: track_record_deletions, untrack_record_deletions).
  7. 비동기식 인덱스 준비 또는 준비 취소 (예: prepare_async_index, unprepare_async_index_by_name).

따라서 DDL 마이그레이션에는 다음을 수행할 수 없습니다.

  1. SQL 문이나 ActiveRecord 모델을 통해 데이터를 읽거나 수정하는 것.
  2. 열 값을 업데이트하는 것 (예: update_column_in_batches).
  3. 백그라운드 마이그레이션을 예약하는 것 (예: queue_background_migration_jobs_by_range_at_intervals).
  4. main:에 저장됨으로 피처 플래그의 상태를 읽지 않는 것.
  5. 애플리케이션 설정을 읽지 않는 것(설정은 main:에 저장됨).

GitLab 코드베이스의 대부분의 마이그레이션은 DDL 유형이므로 이것이 또한 기본 작업 모드이며 마이그레이션 파일에 추가적인 변경이 필요하지 않습니다.

예: 모든 데이터베이스에서 DDL 수행

모든 구성된 데이터베이스에서 실행되는 구조 변경(DDL)으로 취급되는 동시 인덱스를 추가하는 예시 마이그레이션입니다.

class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'
  
  def up
    add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
  end
  
  def down
    remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
  end
end

예: 단일 데이터베이스에 저장할 새로운 테이블 추가

  1. db/docs/데이터베이스 사전에 테이블 추가:

    table_name: ssh_signatures
    description: Description example
    introduced_by_url: Merge request link
    milestone: Milestone example
    feature_categories:
     - Feature category example
    classes:
     - Class example
    gitlab_schema: gitlab_main
    
  2. 스키마 마이그레이션에서 테이블 생성:

    class CreateSshSignatures < Gitlab::Database::Migration[2.1]
      def change
        create_table :ssh_signatures do |t|
          t.timestamps_with_timezone null: false
          t.bigint :project_id, null: false, index: true
          t.bigint :key_id, null: false, index: true
          t.integer :verification_status, default: 0, null: false, limit: 2
          t.binary :commit_sha, null: false, index: { unique: true }
        end
      end
    end
    

데이터 조작 언어(DML)

DML 마이그레이션에는 다음이 모두 포함됩니다.

  1. SQL 문을 통해 데이터 읽기 (예: SELECT * FROM projects WHERE id=1).
  2. ActiveRecord 모델을 통해 데이터 읽기 (예: User < MigrationRecord).
  3. ActiveRecord 모델을 통해 데이터 생성, 업데이트 또는 제거 (예: User.create!(...)).
  4. SQL 문을 통해 데이터 생성, 업데이트 또는 제거 (예: DELETE FROM projects WHERE id=1).
  5. 일괄 업데이트로 열을 업데이트 (예: update_column_in_batches(:projects, :archived, true)).
  6. 백그라운드 마이그레이션 예약 (예: queue_background_migration_jobs_by_range_at_intervals).
  7. 애플리케이션 설정 액세스 (예:main: 데이터베이스에 실행되는 경우 ApplicationSetting.last).

따라서 DML 마이그레이션에는 다음을 수행할 수 없습니다.

  1. DDL에 대한 모든 변경을 수행하는 것(이는 모든 분해된 데이터베이스에서 structure.sql을 일관되게 유지하는 규칙을 어겼기 때문).
  2. 다른 데이터베이스에서 데이터를 읽는 것.

마이그레이션의 DML 유형을 나타내기 위해 마이그레이션은 restrict_gitlab_migration gitlab_schema: 구문을 사용해야 합니다. 이를 통해 지정된 마이그레이션을 DML로 표시하고 액세스를 제한합니다.

예: 지정된 gitlab_schema를 포함하는 데이터베이스 컨텍스트에서만 DML 수행

gitlab_main 스키마를 포함하는 데이터베이스에서만 실행되는 projectsarchived 열을 업데이트하는 예시 마이그레이션입니다.

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  restrict_gitlab_migration gitlab_schema: :gitlab_main
  
  def up
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end
  
  def down
    # no-op
  end
end

예: ActiveRecord 클래스 사용

데이터 조작을 수행하기 위해 ActiveRecord 클래스를 사용하는 마이그레이션은 MigrationRecord 클래스를 사용해야 합니다. 이 클래스는 주어진 마이그레이션 컨텍스트에서 올바른 연결을 제공함을 보장합니다.

MigrationRecord == ActiveRecord::Base로, db:migrate가 실행되면 ActiveRecord::Base.establish_connection :ci의 활성 연결이 변경됩니다. 혼란을 피하기 위해 ActiveRecord::Base 대신 MigrationRecord를 사용해야 합니다.

이를 의미하는 바는 DML 마이그레이션이 다른 데이터베이스에서 데이터를 읽는 것을 금지한다는 것입니다. 예를 들어 ci:의 컨텍스트에서 실행되는 마이그레이션에서 main:에서 피처 플래그를 읽는 것은 연결이 설정되어 있지 않기 때문에 허용되지 않습니다.

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  restrict_gitlab_migration gitlab_schema: :gitlab_main
  
  class Project < MigrationRecord
  end
  
  def up
    Project.where(archived: false).each_batch of |batch|
      batch.update_all(archived: true)
    end
  end
  
  def down
  end
end

gitlab_shared의 특별한 목적

gitlab_schema에 설명된 대로, gitlab_shared 테이블에는 모든 데이터베이스를 대상으로 하는 데이터가 포함될 수 있습니다. 이는 이러한 마이그레이션이 모든 데이터베이스를 대상으로 실행되어 구조(DDL)를 수정하거나 데이터(DML)를 수정해야 한다는 것을 의미합니다.

따라서 gitlab_shared에 액세스하는 이러한 마이그레이션은 restrict_gitlab_migration gitlab_schema:를 사용할 필요가 없으며, 제약 조건이 없는 마이그레이션은 모든 데이터베이스를 대상으로 실행되며 각각의 데이터베이스에서 데이터를 수정할 수 있습니다. restrict_gitlab_migration gitlab_schema:가 지정된 경우 DML 마이그레이션은 주어진 gitlab_schema를 포함하는 데이터베이스의 컨텍스트에서만 실행됩니다.

예: 모든 데이터베이스에서 gitlab_shared DML 마이그레이션 실행하기

lib/gitlab/database/gitlab_schemas.yml에서 gitlab_shared로 표시된 loose_foreign_keys_deleted_records 테이블을 업데이트하는 예시 마이그레이션입니다.

이 마이그레이션은 모든 구성된 데이터베이스에서 실행됩니다.

class DeleteAllLooseForeignKeyRecords < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  def up
    execute("DELETE FROM loose_foreign_keys_deleted_records")
  end
  
  def down
    # 작업 없음
  end
end

예: 주어진 gitlab_schema를 포함하는 데이터베이스에서만 gitlab_shared DML 실행하기

db/docs/loose_foreign_keys_deleted_records.ymlgitlab_shared로 표시된 loose_foreign_keys_deleted_records 테이블을 업데이트하는 예시 마이그레이션입니다.

이 마이그레이션은 gitlab_ci를 제약 조건으로 구성했기 때문에 gitlab_ci 스키마를 포함하는 데이터베이스의 컨텍스트에서만 실행됩니다.

class DeleteCiBuildsLooseForeignKeyRecords < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  restrict_gitlab_migration gitlab_schema: :gitlab_ci
  
  def up
    execute("DELETE FROM loose_foreign_keys_deleted_records WHERE fully_qualified_table_name='ci_builds'")
  end
  
  def down
    # 작업 없음
  end
end

마이그레이션 건너뛰기 동작

건너뛰는 유일한 마이그레이션은 DML 변경을 수행하는 마이그레이션입니다. DDL 마이그레이션은 항상 조건없이 실행됩니다.

구현된 솔루션database_tasks:를 사용하여 기본 데이터베이스를 공유하는 추가 데이터베이스 구성을 나타내는 방식으로 사용됩니다 (config/database.yml에서). database_tasks: false로 표시된 데이터베이스 구성은 해당 데이터베이스 구성에 대해 db:migrate를 실행하지 않도록 설정됩니다.

데이터베이스 구성이 데이터베이스를 공유하지 않는 경우 (database_tasks: true가 모두 있는 경우), 각 마이그레이션은 각 데이터베이스 구성에 대해 실행됩니다.

  1. DDL 마이그레이션은 모든 데이터베이스에 대해 모든 구조 변경을 적용합니다.
  2. DML 마이그레이션은 주어진 gitlab_schema:를 포함하는 데이터베이스의 컨텍스트에서만 실행됩니다.
  3. DML 마이그레이션이 실행 가능하지 않은 경우 건너뛰어집니다. 여전히 schema_migrations에서 실행으로 표시됩니다. 건너뛰어진 마이그레이션은 ‘gitlab_main, gitlab_shared의 외부에 있는 ‘gitlab_ci’를 수정하기 때문에 현재 마이그레이션이 건너뜁니다’를 출력합니다.

database_tasks: false가 구성된 경우 마이그레이션의 손실을 방지하기 위해 전용 Rake 작업인 gitlab:db:validate_config가 사용됩니다(gitlab-org/gitlab/-/merge_requests/83118참조). gitlab:db:validate_config는 각 기본 데이터베이스 구성의 데이터베이스 식별자를 확인하여 database_tasks:의 정확성을 검증합니다. 데이터베이스를 공유하는 것으로 확인된 데이터베이스는 database_tasks: false로 설정해야 합니다. gitlab:db:validate_config은 항상 db:migrate 전에 실행됩니다.

유효성 검사

유효성 검사는 각 쿼리를 분석하고 db/docs/의 정보를 사용하여 테이블을 분류하기 위해 pg_query를 사용합니다. 지정된 gitlab_schema가 주어진 데이터베이스 연결의 관리하는 스키마 디렉터리의 외부에 있는 경우 마이그레이션은 건너뜁니다(Gitlab::Database::gitlab_schemas_for_connection 참조).

Gitlab::Database::Migration[2.0]Gitlab::Database::MigrationHelpers::RestrictGitlabSchema를 포함하며 #migrate 메서드를 확장합니다. 마이그레이션의 기간 동안에는 Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas가 설치되며 restrict_gitlab_migration:에 의해 정의된 허용된 스키마 디렉터리을 수락합니다. 실행된 쿼리가 허용된 스키마 외부에 있는 경우 예외를 발생시킵니다.

예외

restrict_gitlab_migration의 오용이나 누락에 따라 마이그레이션 실행 중에 다양한 예외가 발생할 수 있으며 마이그레이션의 완료를 방해할 수 있습니다.

예외 1: DDL 모드에서 DML 셀렉트를 수행하는 마이그레이션 실행

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  # 부재:
  # restrict_gitlab_migration gitlab_schema: :gitlab_main
  
  def up
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end
  
  def down
    # 작업 없음
  end
end
선택/DML 쿼리 (SELECT/UPDATE/DELETE)는 DDL(구조) 모드에서 허용되지 않습니다
'projects'의 변경 ('SELECT * FROM projects...')

현재 마이그레이션은 restrict_gitlab_migration을 사용하지 않습니다. 부재는 마이그레이션이 DDL 모드에서 실행되고 있음을 나타냅니다. 그러나 실행된 페이로드는 projects에서 데이터를 읽고 있음을 보여줍니다.

해결책restrict_gitlab_migration gitlab_schema: :gitlab_main을 추가하는 것입니다.

예외 2: DML 모드에서 구조를 변경하는 마이그레이션 실행

class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  # 정의된 경우 DML을 나타내므로 제거해야 함
  restrict_gitlab_migration gitlab_schema: :gitlab_main
  
  INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'
  
  def up
    add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
  end
  
  def down
    remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
  end
end
DDL 쿼리(구조)는 선택/DML(SELECT/UPDATE/DELETE) 모드에서 허용되지 않습니다
'merge_request_reviewers' 변경 ('CREATE INDEX...')

현재 마이그레이션은 restrict_gitlab_migration을 사용합니다. 존재는 DML 모드임을 나타내지만, 실행된 페이로드는 구조 변경(DDL)을 수행하고 있음을 보여줍니다.

해결책restrict_gitlab_migration gitlab_schema: :gitlab_main을 제거하는 것입니다.

예외 3: DML 모드에서 다른 스키마의 테이블에 액세스하는 마이그레이션 실행

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  # 'projects'를 수정하므로 'gitlab_main'을 사용해야 함
  restrict_gitlab_migration gitlab_schema: :gitlab_ci
  
  def up
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end
  
  def down
    # 작업 없음
  end
end
선택/DML 쿼리 (SELECT/UPDATE/DELETE)는 'gitlab_ci' 디렉터리의 허용되는 스키마 외부의 'projects'에 액세스함

현재 마이그레이션은 gitlab_ci로 마이그레이션을 제한하지만, gitlab_main에서 데이터를 수정하는 것으로 보입니다.

해결책restrict_gitlab_migration gitlab_schema: :gitlab_ci를 수정하는 것입니다.

예외 4: DDL 및 DML 모드 혼합

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!
  
  # This migration is invalid regardless of specification
  # as it cannot modify structure and data at the same time
  restrict_gitlab_migration gitlab_schema: :gitlab_ci
  
  def up
    add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: 'index_on_merge_request_reviewers'
    update_column_in_batches(:projects, :archived, true) do |table, query|
      query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
    end
  end
  
  def down
    # no-op
  end
end

DDL 및 DML을 혼합하는 마이그레이션이 연산 순서에 따라 하나의 예외를 발생시킵니다.

다중 데이터베이스 마이그레이션에 대한 예정 변경 사항

gitlab_schema:를 사용하는 restrict_gitlab_migration은 컨텍스트에 따라 마이그레이션을 선택적으로 실행하는 기능의 첫 번째 반복이라고 간주됩니다. 현재 상태에서 구조 일관성은 유지될 가능성이 높으므로 DML 전용 마이그레이션에 추가 제약 사항을 추가하여 실행 시점을 제한할 수 있습니다.

가능한 확장으로는 DML 마이그레이션을 특정 환경에만 실행되도록 제한하는 것이 있습니다.

restrict_gitlab_migration gitlab_schema: :gitlab_main, gitlab_env: :gitlab_com

배경 마이그레이션

다음을 사용하는 경우:

  • ‘track_jobs’를 ‘true’로 설정한 배경 마이그레이션 또는
  • 배치 배경 마이그레이션

마이그레이션은 작업 테이블에 기록해야 합니다. 배경 마이그레이션이 사용하는 모든 작업 테이블은 gitlab_shared로 표시됩니다. 이러한 마이그레이션은 모든 데이터베이스의 테이블을 마이그레이션할 때 사용할 수 있습니다.

그러나 일괄 처리를 예약할 때는 반복하는 테이블을 기준으로 restrict_gitlab_migration를 설정해야 합니다. 예를 들어 모든 projects를 업데이트하는 경우에는 restrict_gitlab_migration gitlab_schema: :gitlab_main을 설정해야 합니다. 그러나 모든 ci_pipelines를 업데이트하는 경우에는 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정해야 합니다.

모든 DML 마이그레이션과 마찬가지로 다른 데이터베이스를 조회할 수 없습니다. 다른 데이터베이스를 조회해야 하는 경우, 마이그레이션을 분리하세요.

배경 마이그레이션의 실제 마이그레이션 로직(대기 단계가 아님)은 Sidekiq worker에서 실행되므로, 로직은 일반적인 Sidekiq worker와 마찬가지로 어떤 데이터베이스의 테이블에 대해 DML 쿼리를 수행할 수 있습니다.

주어진 테이블에 대한 gitlab_schema 결정 방법

데이터베이스 사전을 참조하세요.