InfoGrab DocsInfoGrab Docs

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

요약

이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션을 위한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 다중 데이터베이스 설계는 Geo 데이터베이스를 제외하고 분해된 모든 데이터베이스가 같은 구조(예: 스키마)를 가지되 데이터는 데이터베이스마다 다르다고 전제합니다.

이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션을 위한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 자세한 내용은 다중 데이터베이스를 참고합니다.

다중 데이터베이스 설계는 Geo 데이터베이스를 제외하고 분해된 모든 데이터베이스가 같은 구조(예: 스키마)를 가지되 데이터는 데이터베이스마다 다르다고 전제합니다. 즉, 일부 테이블은 모든 데이터베이스에 데이터를 담고 있지는 않습니다.

작업 유형#

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

  1. 구조 변경(DDL - Data Definition Language)입니다(예: ALTER TABLE).
  2. 데이터 변경(DML - Data Manipulation Language)입니다(예: UPDATE).
  3. 마이그레이션 관점에서 DML로 취급되는 그 밖의 쿼리 수행입니다(예: SELECT).

Gitlab::Database::Migration[2.0]을 사용하면 마이그레이션은 항상 한 가지 목적만 가져야 합니다. 애플리케이션은 db/structure.sql에 기술된 구조가 분해된 모든 데이터베이스에서 완전히 동일해야 하므로, 마이그레이션에 DDL과 DML 변경을 섞을 수 없습니다.

Data Definition Language (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).
  8. 테이블을 잘라냅니다(예: truncate_tables! 헬퍼 메서드 사용).

따라서 DDL 마이그레이션에서는 다음이 금지됩니다.

  1. SQL 문이나 ActiveRecord 모델을 통해 어떤 형태로든 데이터를 읽거나 수정하는 일입니다.
  2. 칼럼 값 업데이트입니다(예: update_column_in_batches).
  3. 백그라운드 마이그레이션 예약입니다(예: queue_batched_background_migration).
  4. 기능 플래그 상태 읽기입니다. 기능 플래그는 main:의 features와 feature_gates에 저장됩니다.
  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_org
    
  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
    

Data Manipulation Language (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_batched_background_migration).
  7. 애플리케이션 설정에 접근합니다(예: main: 데이터베이스에서 실행되는 경우 ApplicationSetting.last).
  8. main: 데이터베이스에서 실행되는 경우 기능 플래그를 읽고 수정합니다.

DML 마이그레이션에서는 다음이 금지됩니다.

  1. DDL 변경입니다. 분해된 모든 데이터베이스에서 structure.sql을 일관되게 유지한다는 규칙이 깨지기 때문입니다.
  2. 다른 데이터베이스에서 데이터를 읽는 일입니다.

DML 마이그레이션 유형임을 나타내려면 마이그레이션 클래스에서 restrict_gitlab_migration gitlab_schema: 구문을 사용해야 합니다. 이 구문은 해당 마이그레이션을 DML로 표시하고 접근을 제한합니다.

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

다음은 projects의 archived 칼럼을 업데이트하는 마이그레이션 예시이며, gitlab_main 스키마를 포함하는 데이터베이스에서만 실행됩니다.

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  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_org

  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를 포함하는 데이터베이스 컨텍스트에서만 실행됩니다.

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

다음은 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
    # no-op
  end
end

예시: 지정된 gitlab_schema를 포함하는 데이터베이스에서만 DML gitlab_shared 실행#

다음은 db/docs/loose_foreign_keys_deleted_records.yml에서 gitlab_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
    # no-op
  end
end

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

건너뛰는 마이그레이션은 DML 변경을 수행하는 마이그레이션뿐입니다. DDL 마이그레이션은 언제나 조건 없이 실행됩니다.

구현된 해결책은 config/database.yml의 어떤 추가 데이터베이스 구성이 같은 기본 데이터베이스를 공유하는지 나타내기 위해 database_tasks:를 사용합니다. database_tasks: false로 표시된 데이터베이스 구성은 해당 구성에 대해 db:migrate를 실행하는 대상에서 제외됩니다.

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

  1. DDL 마이그레이션은 모든 데이터베이스에 모든 구조 변경을 적용합니다.
  2. DML 마이그레이션은 지정된 gitlab_schema:를 포함하는 데이터베이스 컨텍스트에서만 실행됩니다.
  3. DML 마이그레이션이 실행 대상이 아니면 건너뜁니다. 그래도 schema_migrations에는 실행된 것으로 표시됩니다. db:migrate를 실행하는 동안 건너뛴 마이그레이션은 Current migration is skipped since it modifies 'gitlab_ci' which is outside of 'gitlab_main, gitlab_shared를 출력합니다.

database_tasks: false로 구성했을 때 마이그레이션이 누락되는 것을 막기 위해 전용 Rake 태스크 gitlab:db:validate_config를 사용합니다. gitlab:db:validate_config는 각 데이터베이스 구성의 데이터베이스 식별자를 확인해 database_tasks:가 올바른지 검증합니다. 데이터베이스를 공유하는 구성은 database_tasks: false로 설정되어 있어야 합니다. gitlab:db:validate_config는 항상 db:migrate 앞에 실행됩니다.

유효성 검사#

유효성 검사는 요약하면 pg_query로 각 쿼리를 분석하고 db/docs/의 정보로 테이블을 분류합니다. 지정된 gitlab_schema가 해당 데이터베이스 연결이 관리하는 스키마 목록 (Gitlab::Database::gitlab_schemas_for_connection) 밖에 있으면 마이그레이션을 건너뜁니다.

Gitlab::Database::Migration[2.0]은 #migrate 메서드를 확장하는 Gitlab::Database::MigrationHelpers::RestrictGitlabSchema를 포함합니다. 마이그레이션이 실행되는 동안에는 restrict_gitlab_migration:으로 정의된 허용 스키마 목록을 받는 전용 쿼리 분석기 Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas가 설치됩니다. 실행된 쿼리가 허용 스키마 밖에 있으면 예외가 발생합니다.

예외#

restrict_gitlab_migration을 잘못 사용하거나 빠뜨리면 마이그레이션 실행 중에 여러 예외가 발생해 마이그레이션이 완료되지 못할 수 있습니다.

예외 1: DDL 모드에서 실행되는 마이그레이션이 DML select를 수행하는 경우#

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # Missing:
  # restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  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
Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode
Modifying of 'projects' (gitlab_main) with 'SELECT * FROM projects...

이 마이그레이션은 restrict_gitlab_migration을 사용하지 않습니다. 이는 DDL 모드로 실행되는 마이그레이션임을 뜻하지만, 실행되는 내용은 projects에서 데이터를 읽는 것으로 보입니다.

해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 추가하는 것입니다.

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

class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # restrict_gitlab_migration if defined indicates DML, it should be removed
  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  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 queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode.
Modifying of 'merge_request_reviewers' with 'CREATE INDEX...

이 마이그레이션은 restrict_gitlab_migration을 사용합니다. 이는 DML 모드를 뜻하지만, 실행되는 내용은 구조 변경(DDL)을 수행하는 것으로 보입니다.

해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 제거하는 것입니다.

예외 3: DML 모드에서 실행되는 마이그레이션이 다른 스키마의 테이블 데이터에 접근하는 경우#

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # Since it modifies `projects` it should use `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
    # no-op
  end
end
Select/DML queries (SELECT/UPDATE/DELETE) do access 'projects' (gitlab_main) " \
which is outside of list of allowed schemas: 'gitlab_ci'

이 마이그레이션은 대상을 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_org, gitlab_env: :gitlab_com

백그라운드 마이그레이션#

다음을 사용하는 경우입니다.

  • track_jobs가 true로 설정된 백그라운드 마이그레이션
  • 배치 백그라운드 마이그레이션

이 경우 마이그레이션은 jobs 테이블에 써야 합니다. 백그라운드 마이그레이션이 사용하는 jobs 테이블은 모두 gitlab_shared로 표시되어 있습니다. 따라서 어떤 데이터베이스의 테이블을 마이그레이션할 때도 이러한 마이그레이션을 사용할 수 있습니다.

다만 배치를 큐에 넣을 때는 순회하는 테이블을 기준으로 restrict_gitlab_migration을 설정해야 합니다. 예를 들어 모든 projects를 업데이트한다면 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 설정합니다. 모든 ci_pipelines를 업데이트한다면 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정합니다.

모든 DML 마이그레이션과 마찬가지로 restrict_gitlab_migration 이나 gitlab_shared 밖의 다른 데이터베이스를 쿼리할 수 없습니다. 다른 데이터베이스를 쿼리해야 한다면 마이그레이션을 분리합니다.

백그라운드 마이그레이션의 실제 마이그레이션 로직은 큐에 넣는 단계가 아니라 Sidekiq 워커에서 실행되므로, 일반 Sidekiq 워커와 마찬가지로 어떤 데이터베이스의 테이블에도 DML 쿼리를 수행할 수 있습니다.

주어진 테이블의 gitlab_schema 결정 방법#

데이터베이스 딕셔너리를 참고합니다.

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

GitLab v19.4
원문 보기

요약

이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션을 위한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 다중 데이터베이스 설계는 Geo 데이터베이스를 제외하고 분해된 모든 데이터베이스가 같은 구조(예: 스키마)를 가지되 데이터는 데이터베이스마다 다르다고 전제합니다.

이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션을 위한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 자세한 내용은 다중 데이터베이스를 참고합니다.

다중 데이터베이스 설계는 Geo 데이터베이스를 제외하고 분해된 모든 데이터베이스가 같은 구조(예: 스키마)를 가지되 데이터는 데이터베이스마다 다르다고 전제합니다. 즉, 일부 테이블은 모든 데이터베이스에 데이터를 담고 있지는 않습니다.

작업 유형#

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

  1. 구조 변경(DDL - Data Definition Language)입니다(예: ALTER TABLE).
  2. 데이터 변경(DML - Data Manipulation Language)입니다(예: UPDATE).
  3. 마이그레이션 관점에서 DML로 취급되는 그 밖의 쿼리 수행입니다(예: SELECT).

Gitlab::Database::Migration[2.0]을 사용하면 마이그레이션은 항상 한 가지 목적만 가져야 합니다. 애플리케이션은 db/structure.sql에 기술된 구조가 분해된 모든 데이터베이스에서 완전히 동일해야 하므로, 마이그레이션에 DDL과 DML 변경을 섞을 수 없습니다.

Data Definition Language (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).
  8. 테이블을 잘라냅니다(예: truncate_tables! 헬퍼 메서드 사용).

따라서 DDL 마이그레이션에서는 다음이 금지됩니다.

  1. SQL 문이나 ActiveRecord 모델을 통해 어떤 형태로든 데이터를 읽거나 수정하는 일입니다.
  2. 칼럼 값 업데이트입니다(예: update_column_in_batches).
  3. 백그라운드 마이그레이션 예약입니다(예: queue_batched_background_migration).
  4. 기능 플래그 상태 읽기입니다. 기능 플래그는 main:의 features와 feature_gates에 저장됩니다.
  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_org
    
  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
    

Data Manipulation Language (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_batched_background_migration).
  7. 애플리케이션 설정에 접근합니다(예: main: 데이터베이스에서 실행되는 경우 ApplicationSetting.last).
  8. main: 데이터베이스에서 실행되는 경우 기능 플래그를 읽고 수정합니다.

DML 마이그레이션에서는 다음이 금지됩니다.

  1. DDL 변경입니다. 분해된 모든 데이터베이스에서 structure.sql을 일관되게 유지한다는 규칙이 깨지기 때문입니다.
  2. 다른 데이터베이스에서 데이터를 읽는 일입니다.

DML 마이그레이션 유형임을 나타내려면 마이그레이션 클래스에서 restrict_gitlab_migration gitlab_schema: 구문을 사용해야 합니다. 이 구문은 해당 마이그레이션을 DML로 표시하고 접근을 제한합니다.

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

다음은 projects의 archived 칼럼을 업데이트하는 마이그레이션 예시이며, gitlab_main 스키마를 포함하는 데이터베이스에서만 실행됩니다.

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  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_org

  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를 포함하는 데이터베이스 컨텍스트에서만 실행됩니다.

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

다음은 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
    # no-op
  end
end

예시: 지정된 gitlab_schema를 포함하는 데이터베이스에서만 DML gitlab_shared 실행#

다음은 db/docs/loose_foreign_keys_deleted_records.yml에서 gitlab_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
    # no-op
  end
end

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

건너뛰는 마이그레이션은 DML 변경을 수행하는 마이그레이션뿐입니다. DDL 마이그레이션은 언제나 조건 없이 실행됩니다.

구현된 해결책은 config/database.yml의 어떤 추가 데이터베이스 구성이 같은 기본 데이터베이스를 공유하는지 나타내기 위해 database_tasks:를 사용합니다. database_tasks: false로 표시된 데이터베이스 구성은 해당 구성에 대해 db:migrate를 실행하는 대상에서 제외됩니다.

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

  1. DDL 마이그레이션은 모든 데이터베이스에 모든 구조 변경을 적용합니다.
  2. DML 마이그레이션은 지정된 gitlab_schema:를 포함하는 데이터베이스 컨텍스트에서만 실행됩니다.
  3. DML 마이그레이션이 실행 대상이 아니면 건너뜁니다. 그래도 schema_migrations에는 실행된 것으로 표시됩니다. db:migrate를 실행하는 동안 건너뛴 마이그레이션은 Current migration is skipped since it modifies 'gitlab_ci' which is outside of 'gitlab_main, gitlab_shared를 출력합니다.

database_tasks: false로 구성했을 때 마이그레이션이 누락되는 것을 막기 위해 전용 Rake 태스크 gitlab:db:validate_config를 사용합니다. gitlab:db:validate_config는 각 데이터베이스 구성의 데이터베이스 식별자를 확인해 database_tasks:가 올바른지 검증합니다. 데이터베이스를 공유하는 구성은 database_tasks: false로 설정되어 있어야 합니다. gitlab:db:validate_config는 항상 db:migrate 앞에 실행됩니다.

유효성 검사#

유효성 검사는 요약하면 pg_query로 각 쿼리를 분석하고 db/docs/의 정보로 테이블을 분류합니다. 지정된 gitlab_schema가 해당 데이터베이스 연결이 관리하는 스키마 목록 (Gitlab::Database::gitlab_schemas_for_connection) 밖에 있으면 마이그레이션을 건너뜁니다.

Gitlab::Database::Migration[2.0]은 #migrate 메서드를 확장하는 Gitlab::Database::MigrationHelpers::RestrictGitlabSchema를 포함합니다. 마이그레이션이 실행되는 동안에는 restrict_gitlab_migration:으로 정의된 허용 스키마 목록을 받는 전용 쿼리 분석기 Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas가 설치됩니다. 실행된 쿼리가 허용 스키마 밖에 있으면 예외가 발생합니다.

예외#

restrict_gitlab_migration을 잘못 사용하거나 빠뜨리면 마이그레이션 실행 중에 여러 예외가 발생해 마이그레이션이 완료되지 못할 수 있습니다.

예외 1: DDL 모드에서 실행되는 마이그레이션이 DML select를 수행하는 경우#

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # Missing:
  # restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  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
Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode
Modifying of 'projects' (gitlab_main) with 'SELECT * FROM projects...

이 마이그레이션은 restrict_gitlab_migration을 사용하지 않습니다. 이는 DDL 모드로 실행되는 마이그레이션임을 뜻하지만, 실행되는 내용은 projects에서 데이터를 읽는 것으로 보입니다.

해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 추가하는 것입니다.

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

class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # restrict_gitlab_migration if defined indicates DML, it should be removed
  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  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 queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode.
Modifying of 'merge_request_reviewers' with 'CREATE INDEX...

이 마이그레이션은 restrict_gitlab_migration을 사용합니다. 이는 DML 모드를 뜻하지만, 실행되는 내용은 구조 변경(DDL)을 수행하는 것으로 보입니다.

해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 제거하는 것입니다.

예외 3: DML 모드에서 실행되는 마이그레이션이 다른 스키마의 테이블 데이터에 접근하는 경우#

class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
  disable_ddl_transaction!

  # Since it modifies `projects` it should use `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
    # no-op
  end
end
Select/DML queries (SELECT/UPDATE/DELETE) do access 'projects' (gitlab_main) " \
which is outside of list of allowed schemas: 'gitlab_ci'

이 마이그레이션은 대상을 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_org, gitlab_env: :gitlab_com

백그라운드 마이그레이션#

다음을 사용하는 경우입니다.

  • track_jobs가 true로 설정된 백그라운드 마이그레이션
  • 배치 백그라운드 마이그레이션

이 경우 마이그레이션은 jobs 테이블에 써야 합니다. 백그라운드 마이그레이션이 사용하는 jobs 테이블은 모두 gitlab_shared로 표시되어 있습니다. 따라서 어떤 데이터베이스의 테이블을 마이그레이션할 때도 이러한 마이그레이션을 사용할 수 있습니다.

다만 배치를 큐에 넣을 때는 순회하는 테이블을 기준으로 restrict_gitlab_migration을 설정해야 합니다. 예를 들어 모든 projects를 업데이트한다면 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 설정합니다. 모든 ci_pipelines를 업데이트한다면 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정합니다.

모든 DML 마이그레이션과 마찬가지로 restrict_gitlab_migration 이나 gitlab_shared 밖의 다른 데이터베이스를 쿼리할 수 없습니다. 다른 데이터베이스를 쿼리해야 한다면 마이그레이션을 분리합니다.

백그라운드 마이그레이션의 실제 마이그레이션 로직은 큐에 넣는 단계가 아니라 Sidekiq 워커에서 실행되므로, 일반 Sidekiq 워커와 마찬가지로 어떤 데이터베이스의 테이블에도 DML 쿼리를 수행할 수 있습니다.

주어진 테이블의 gitlab_schema 결정 방법#

데이터베이스 딕셔너리를 참고합니다.