InfoGrab DocsInfoGrab Docs

배치 백그라운드 마이그레이션

요약

배치 백그라운드 마이그레이션은 마이그레이션이 가이드라인의 시간 제한을 초과하는 경우 데이터 마이그레이션을 수행하는 데 사용해야 합니다. 배치 백그라운드 마이그레이션은 레거시 백그라운드 마이그레이션 프레임워크를 대체했습니다.

배치 백그라운드 마이그레이션은 마이그레이션이 가이드라인의 시간 제한을 초과하는 경우 데이터 마이그레이션을 수행하는 데 사용해야 합니다. 예를 들어, 단일 JSON 칼럼에 저장된 데이터를 별도 테이블로 마이그레이션할 때 배치 백그라운드 마이그레이션을 사용할 수 있습니다.

배치 백그라운드 마이그레이션은 레거시 백그라운드 마이그레이션 프레임워크를 대체했습니다. 해당 프레임워크와 관련된 변경 사항은 해당 문서를 참조하세요.

배치 백그라운드 마이그레이션 프레임워크는 ChatOps를 지원합니다. GitLab 엔지니어는 ChatOps를 통해 시스템에 존재하는 배치 백그라운드 마이그레이션과 상호작용할 수 있습니다.

배치 백그라운드 마이그레이션을 언제 사용하는가#

일반 Rails 마이그레이션으로 수행할 경우 가이드라인의 시간 제한을 초과할 만큼 많은 행이 포함된 테이블에서 데이터를 마이그레이션할 때 배치 백그라운드 마이그레이션을 사용하세요.

  • 배치 백그라운드 마이그레이션은 트래픽이 많은 테이블에서 데이터를 마이그레이션할 때 사용해야 합니다.

  • 배치 백그라운드 마이그레이션은 대용량 데이터셋의 각 항목에 대해 여러 단일 행 쿼리를 실행할 때도 사용할 수 있습니다. 일반적으로 단일 레코드 패턴의 경우 런타임은 데이터셋 크기에 크게 의존합니다. 데이터셋을 적절히 분할하여 백그라운드 마이그레이션으로 처리하세요.

  • 스키마 마이그레이션을 수행하는 데는 배치 백그라운드 마이그레이션을 사용하지 마세요.

백그라운드 마이그레이션이 도움이 될 수 있는 경우:

  • 하나의 테이블에서 여러 개의 개별 테이블로 이벤트를 마이그레이션하는 경우.

  • 다른 칼럼에 저장된 JSON을 기반으로 하나의 칼럼을 채우는 경우.

  • 외부 서비스의 출력에 의존하는 데이터를 마이그레이션하는 경우. (예: API)

참고 사항#

  • 배치 백그라운드 마이그레이션이 중요한 업그레이드의 일부인 경우, 릴리즈 포스트에 공지해야 합니다. 마이그레이션이 이 범주에 해당하는지 확실하지 않다면 Project Manager와 협의하세요.

  • 자체 관리 및 Dedicated 고객이 업그레이드를 계획할 수 있도록 중요한 마이그레이션에 대해 업그레이드 참고 사항을 추가해야 합니다. 가이드라인을 따르세요.

  • 필요한 파일이 기본으로 생성되도록 제너레이터를 사용하여 배치 백그라운드 마이그레이션을 생성하세요.

배치 백그라운드 마이그레이션의 동작 방식#

배치 백그라운드 마이그레이션(BBM)은 perform 메서드를 정의하는 Gitlab::BackgroundMigration::BatchedMigrationJob의 서브클래스입니다. 첫 번째 단계로, 일반 마이그레이션이 BBM 클래스와 필요한 인수를 포함하는 batched_background_migrations 레코드를 생성합니다. 기본적으로 batched_background_migrations는 활성 상태이며, Sidekiq 워커가 실제 배치 마이그레이션을 실행하기 위해 이를 가져갑니다.

모든 마이그레이션 클래스는 네임스페이스 Gitlab::BackgroundMigration에 정의되어야 합니다. 파일은 lib/gitlab/background_migration/ 디렉터리에 배치하세요.

실행 메커니즘#

배치 백그라운드 마이그레이션은 큐에 추가된 순서대로 선택됩니다. 여러 마이그레이션이 활성 상태이고 같은 데이터베이스 테이블을 타깃으로 하지 않는 한, 병렬로 가져와 실행됩니다. 병렬 처리되는 기본 마이그레이션 수는 2이며, GitLab.com에서는 이 한도가 4로 설정되어 있습니다. 마이그레이션이 실행을 위해 선택되면 특정 배치에 대한 job이 생성됩니다. 각 job 실행 후, 마지막 20개 job의 성능을 기반으로 마이그레이션의 배치 크기가 증가하거나 감소할 수 있습니다.

@startuml hide empty description skinparam ConditionEndStyle hline left to right direction rectangle "Batched background migration queue" as migrations { rectangle "Migration N (active)" as migrationn rectangle "Migration 1 (completed)" as migration1 rectangle "Migration 2 (active)" as migration2 rectangle "Migration 3 (on hold)" as migration3 rectangle "Migration 4 (active)" as migration4 migration1 -[hidden]> migration2 migration2 -[hidden]> migration3 migration3 -[hidden]> migration4 migration4 -[hidden]> migrationn } rectangle "Execution Workers" as workers { rectangle "Execution Worker 1 (busy)" as worker1 rectangle "Execution Worker 2 (available)" as worker2 worker1 -[hidden]> worker2 } migration2 --> [Scheduling Worker] migration4 --> [Scheduling Worker] [Scheduling Worker] --> worker2 @enduml

워커가 사용 가능해지면 BBM이 러너에 의해 처리됩니다.

@startuml hide empty description start rectangle Runner { :Migration; if (Have reached batching bounds?) then (Yes) if (Have jobs to retry?) then (Yes) :Fetch the batched job; else (No) :Finish active migration; stop endif else (No) :Create a batched job; endif :Execute batched job; :Evaluate DB health; note right: Checks for table autovacuum, Patroni Apdex, Write-ahead logging if (Evaluation signs to stop?) then (Yes) :Put migration on hold; else (No) :Optimize migration; endif } @enduml

멱등성#

배치 백그라운드 마이그레이션은 Sidekiq 프로세스의 컨텍스트에서 실행됩니다. 일반적인 Sidekiq 규칙이 적용되며, 특히 job은 작고 멱등적이어야 한다는 규칙이 적용됩니다. 마이그레이션 job이 재시도될 경우 데이터 무결성이 보장되도록 하세요.

자세한 내용은 Sidekiq 모범 사례 가이드라인을 참조하세요.

마이그레이션 최적화#

각 job 실행 후, 마이그레이션을 최적화할 수 있는지 확인하는 검증이 이루어집니다. 최적화의 기본 메커니즘은 시간 효율성 개념을 기반으로 합니다. 마지막 N개 job의 시간 효율성의 지수 이동 평균을 계산하고 배치 백그라운드 마이그레이션의 배치 크기를 최적값으로 업데이트합니다.

그러나 이 메커니즘으로 인해 데이터베이스 마이그레이션 파이프라인을 사용할 때 마이그레이션의 총 실행 시간을 정확하게 추정하기 어렵습니다.

이 문제를 해결하는 방법은 이 이슈에서 논의 중입니다.

Job 재시도 메커니즘#

배치 백그라운드 마이그레이션 재시도 메커니즘은 실패 시 job이 다시 실행되도록 보장합니다. 다음 다이어그램은 재시도 메커니즘의 다양한 단계를 보여줍니다:

@startuml hide empty description note as N1 can_split?: the failure is due to a query timeout end note [] --> Running Running --> Failed note on link if number of retries Succeeded Failed --> Running note on link if number of retries > MAX_ATTEMPTS and can_split? == true then two jobs with smaller batch size will be created end note Failed --> [] Succeeded --> [*] @enduml

실패한 배치 백그라운드 마이그레이션#

다음 중 하나라도 해당하는 경우 전체 배치 백그라운드 마이그레이션이 failed로 표시됩니다 (/chatops gitlab run batched_background_migrations status MIGRATION_ID에서 마이그레이션이 failed로 표시됨):

배치 마이그레이션 제한#

배치 마이그레이션은 업데이트가 많고 데이터베이스가 성능 저하 상태일 때 이러한 마이그레이션의 부하로 인해 인시던트가 발생한 바 있어, 향후 인시던트를 완화하기 위한 제한 메커니즘이 존재합니다.

다음 데이터베이스 지표를 확인하여 마이그레이션을 제한합니다. 중지 신호를 받으면 마이그레이션이 설정된 시간(10분) 동안 일시 중지됩니다:

  • 임계값을 초과하는 WAL 큐 보류 중인 아카이브.

  • 마이그레이션이 작업 중인 테이블에서 활성 autovacuum 실행 (GitLab 18.0부터 기본적으로 활성화됨).

  • SLO 이하로 떨어지는 Patroni apdex SLI.

  • 임계값을 초과하는 WAL 속도.

데이터베이스 상태 점검 프레임워크를 더욱 강화하기 위해 더 많은 지표를 추가하는 작업이 진행 중입니다. 자세한 내용은 에픽 7594를 참조하세요.

테이블에서 autovacuum 지표를 비활성화/활성화하는 방법#

GitLab 18.0부터 이 상태 지표가 기본적으로 활성화됩니다. 비활성화하려면 rails 콘솔에서 다음 명령어를 실행하세요:

Feature.disable(:batched_migrations_health_status_autovacuum)

또는 다시 활성화하려면 rails 콘솔에서 다음 명령어를 실행하세요:

Feature.enable(:batched_migrations_health_status_autovacuum)

격리#

배치 백그라운드 마이그레이션은 격리되어야 하며 애플리케이션 코드를 사용할 수 없습니다 (예: ApplicationRecord 클래스를 제외한 app/models에 정의된 모델). 이러한 마이그레이션은 실행하는 데 오랜 시간이 걸릴 수 있으므로, 마이그레이션이 여전히 실행 중인 동안 새 버전이 배포될 수 있습니다.

마이그레이션된 데이터에 의존하기#

일반 마이그레이션이나 포스트 마이그레이션과 달리, 다음 릴리즈를 기다리는 것만으로는 데이터가 완전히 마이그레이션되었음을 보장하기에 충분하지 않습니다. 즉, BBM이 완료될 때까지 해당 데이터에 의존해서는 안 됩니다. 100%의 데이터 마이그레이션이 필요한 경우, ensure_batched_background_migration_is_finished 헬퍼를 사용하여 마이그레이션이 완료되고 데이터가 완전히 마이그레이션되었음을 보장할 수 있습니다. (예시 참조).

방법#

배치 백그라운드 마이그레이션 생성#

커스텀 제너레이터 batched_background_migration은 필요한 파일을 스캐폴딩하고 table_name, column_name, feature_category를 인수로 받습니다. column_name을 선택할 때 고유하게 반복할 수 있는 칼럼 타입을 사용하는지 확인하세요. 가급적이면 테이블의 기본 키를 사용하세요. 테이블은 여기에 정의된 칼럼을 기반으로 반복됩니다. 자세한 내용은 비고유 칼럼에서 배치 처리를 참조하세요.

사용법:

bundle exec rails g batched_background_migration my_batched_migration --table_name=<table-name> --column_name=<column-name> --feature_category=<feature-category>

이 명령어는 다음 파일들을 생성합니다:

  • db/post_migrate/20230214231008_queue_my_batched_migration.rb

  • spec/migrations/20230214231008_queue_my_batched_migration_spec.rb

  • lib/gitlab/background_migration/my_batched_migration.rb

  • spec/lib/gitlab/background_migration/my_batched_migration_spec.rb

커서 기반 반복 사용 (기본값)#

커서 기반 반복은 이제 배치 백그라운드 마이그레이션의 기본 권장 전략입니다. 레거시 기본 키 기반 방식에 비해 복합 기본 키 지원과 유지 관리 편의성을 제공합니다.

queue_batched_background_migration 헬퍼는 마이그레이션 job이 커서 전략을 사용하는지 자동으로 감지하고 그에 따라 마이그레이션을 구성합니다.

커서 전략 사용 방법#

마이그레이션 job 클래스에서 cursor DSL을 사용하여 커서 칼럼을 정의하세요:

module Gitlab
  module BackgroundMigration
    class MyBatchedMigration < BatchedMigrationJob
      # For single-column primary keys
      cursor :id

      operation_name :my_operation
      feature_category :database

      def perform
        each_sub_batch do |sub_batch|
          # Your migration logic here
        end
      end
    end
  end
end

복합 기본 키가 있는 테이블의 경우:

module Gitlab
  module BackgroundMigration
    class MyCompositePkMigration < BatchedMigrationJob
      cursor :deployment_id, :merge_request_id

      operation_name :backfill_project_id
      feature_category :continuous_delivery

      def perform
        each_sub_batch do |relation|
          # Your migration logic here
        end
      end
    end
  end
end

그런 다음 마이그레이션 파일에서 표준 헬퍼를 사용하세요:

queue_batched_background_migration(
  'MyBatchedMigration',
  :my_table,
  :id,
  job_interval: 2.minutes
)

헬퍼는 자동으로 다음을 수행합니다:

  • job이 커서 전략을 사용하는지 감지

  • 적절한 min_cursormax_cursor 값 계산

  • 커서 기반 반복을 사용하도록 마이그레이션 구성

레거시 기본 키 기반 반복#

레거시 기본 키 기반 반복 전략을 사용해야 하는 경우(새 마이그레이션에는 권장하지 않음), 마이그레이션 클래스에서 cursor 정의를 생략하세요. 마이그레이션이 이전 반복 방식으로 폴백됩니다.

배치 백그라운드 마이그레이션 큐잉#

배치 백그라운드 마이그레이션 큐잉은 포스트 배포 마이그레이션에서 수행해야 합니다. 다음 queue_batched_background_migration 예시를 사용하여 배치로 실행될 마이그레이션을 큐에 추가하세요. 클래스 이름과 인수를 마이그레이션의 값으로 교체하세요:

queue_batched_background_migration(
  JOB_CLASS_NAME,
  TABLE_NAME,
  JOB_ARGUMENTS
  )

이 헬퍼는 제공된 job 인수 수가 JOB_CLASS_NAME에 정의된 job 인수 수와 일치하지 않으면 오류를 발생시킵니다.

새로 생성된 데이터가 마이그레이션되거나 생성 시 이전 버전과 새 버전 모두에 저장되어야 합니다. 삭제의 경우 계단식 삭제가 있는 외래 키를 정의하여 처리할 수 있습니다.

배치 백그라운드 마이그레이션 완료#

배치 백그라운드 마이그레이션 완료는 ensure_batched_background_migration_is_finished를 호출하여 수행되지만, 마지막 필수 중단점 이전에 또는 그 시점에 마이그레이션이 추가된 경우에만 해당합니다. 이렇게 하면 GitLab Self-Managed 인스턴스의 원활한 업그레이드 프로세스가 보장됩니다.

가능한 한 빨리 모든 배치 백그라운드 마이그레이션을 완료하는 것이 중요합니다. 오래된 배치 백그라운드 마이그레이션을 방치하는 것은 테스트와 애플리케이션 동작에서 유지 관리해야 하는 기술 부채의 한 형태입니다.

완료되기 전에는 어떤 배치 백그라운드 마이그레이션에도 의존할 수 없습니다.

배치 백그라운드 마이그레이션은 다음 모든 조건이 충족된 후에 완료하는 것을 권장합니다:

ensure_batched_background_migration_is_finished 호출은 큐에 추가하는 데 사용된 마이그레이션과 정확히 일치해야 합니다. 다음 사항에 주의하세요:

  • job 인수: 정확히 일치해야 하며, 그렇지 않으면 큐에 추가된 마이그레이션을 찾지 못합니다

  • gitlab_schema: 정확히 일치해야 하며, 그렇지 않으면 큐에 추가된 마이그레이션을 찾지 못합니다. 그 사이에 테이블의 gitlab_schemagitlab_main에서 gitlab_main_org로 변경된 경우에도 배치 백그라운드 마이그레이션을 큐에 추가할 때 사용한 gitlab_main으로 완료해야 합니다.

배치 백그라운드 마이그레이션을 완료할 때는 해당 db/docs/batched_background_migrations 파일의 finalized_by도 업데이트해야 합니다. 값은 완료를 위해 추가한 마이그레이션의 타임스탬프/버전이어야 합니다.

실제 마이그레이션 코드에 대한 구체적인 내용은 아래 예시를 참조하세요.

필수 중단점 하나 이전에 마이그레이션이 완료되는 경우, 조기 완료 오류가 발생합니다. 필수 중단점 하나 이전에 마이그레이션을 완료해야 하는 경우, skip_early_finalization_validation: true 옵션을 사용하여 이 검사를 건너뛰세요.

배치 백그라운드 마이그레이션 코드 삭제#

배치 백그라운드 마이그레이션이 완료 및 확정되고 재큐잉되지 않았다면, lib/gitlab/background_migration/의 마이그레이션 코드와 관련 테스트는 완료 후 다음 필수 중단점 이후에 삭제할 수 있습니다.

예시 시나리오:

  • 17.3과 17.5가 필수 중단점입니다.

  • 17.1에서 배치 백그라운드 마이그레이션이 큐에 추가됩니다.

  • GitLab.com에서 완료된 경우, 17.4에서 마이그레이션이 완료될 수 있습니다.

  • 17.6에서 마이그레이션 관련 코드를 삭제할 수 있습니다.

배치 백그라운드 마이그레이션 코드를 삭제하는 두 가지 전략이 있습니다:

  • 마이그레이션 스쿼싱이 마이그레이션 관련 파일을 자동으로 삭제할 때까지 기다립니다.

  • 마이그레이션 관련 파일을 수동으로 삭제합니다.

마이그레이션 스쿼싱으로 배치 백그라운드 마이그레이션 정리#

GitLab에는 완료된 배치 백그라운드 마이그레이션을 삭제하는 마이그레이션 스쿼싱 프로세스가 있습니다. GitLab.com의 경우 이 스크립트는 모든 필수 중단점 이후에 실행됩니다. 배치 백그라운드 마이그레이션이 완료된 경우, 단순히 다음 필수 중단점을 기다릴 수 있습니다. 배치 백그라운드 마이그레이션과 관련된 모든 파일이 그때 삭제됩니다.

배치 백그라운드 마이그레이션 코드 수동 삭제#

경우에 따라 완료 후 마이그레이션 스쿼싱이 실행되기 전에 배치 백그라운드 마이그레이션 코드를 삭제하고 싶을 수 있습니다. 예를 들어, 배치 백그라운드 마이그레이션이 GitLab.com만을 타깃으로 했고 마이그레이션에서 참조하는 일부 코드를 제거하려는 경우입니다.

이 경우 다음 파일들을 수동으로 삭제할 수 있습니다:

  • lib/의 배치 백그라운드 마이그레이션 클래스 파일.

  • 배치 백그라운드 마이그레이션 클래스에 해당하는 spec/ 파일.

  • db/docs/batched_background_migrations/의 배치 백그라운드 마이그레이션 YAML 파일.

  • 배치 백그라운드 마이그레이션을 큐에 추가하거나 완료한 db/post_migrate/의 모든 마이그레이션 파일.

  • 큐잉 또는 완료 마이그레이션에 해당하는 spec/ 파일.

  • 삭제된 db/post_migrate/ 마이그레이션에 해당하는 schema_migrations/의 모든 파일.

배치 백그라운드 마이그레이션 재큐잉#

배치 백그라운드 마이그레이션을 다시 실행해야 하는 몇 가지 이유가 있을 수 있습니다:

  • 마이그레이션에 버그가 있는 경우 (예시).

  • 마이그레이션이 데이터를 정리했지만 애플리케이션 로직의 우회로 인해 데이터가 다시 비정규화된 경우 (예시).

  • 원래 마이그레이션의 배치 크기로 인해 마이그레이션이 실패하는 경우 (예시).

배치 백그라운드 마이그레이션을 재큐잉하려면:

  • 원래 마이그레이션 파일의 #up#down 메서드 내용을 no-op으로 만드세요. 그렇지 않으면 여러 패치 릴리즈를 한 번에 업그레이드하는 시스템에서 배치 백그라운드 마이그레이션이 생성되었다가 삭제된 후 다시 생성됩니다.

  • 배치 백그라운드 마이그레이션을 다시 실행하는 새 포스트 배포 마이그레이션을 추가하세요.

  • 새 포스트 배포 마이그레이션에서 #up 메서드 시작 부분에 delete_batched_background_migration 메서드를 사용하여 기존 배치 백그라운드 마이그레이션을 삭제하여 기존 실행이 정리되도록 하세요.

  • 원래 마이그레이션의 db/docs/batched_background_migration/*.yml 파일을 업데이트하여 재큐잉에 관한 정보를 포함하세요.

예시#

원래 마이그레이션:

# frozen_string_literal: true

class QueueResolveVulnerabilitiesForRemovedAnalyzers < Gitlab::Database::Migration[2.2]
  milestone '17.3'

  MIGRATION = "ResolveVulnerabilitiesForRemovedAnalyzers"

  def up
    # no-op because there was a bug in the original migration, which has been
    # fixed by
  end

  def down
    # no-op because there was a bug in the original migration, which has been
    # fixed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162527
  end
end

재큐잉된 마이그레이션:

# frozen_string_literal: true

class RequeueResolveVulnerabilitiesForRemovedAnalyzers < Gitlab::Database::Migration[2.2]
  milestone '17.4'

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  MIGRATION = "ResolveVulnerabilitiesForRemovedAnalyzers"
  BATCH_SIZE = 10_000
  SUB_BATCH_SIZE = 100

  def up
    # Clear previous background migration execution from QueueResolveVulnerabilitiesForRemovedAnalyzers
    delete_batched_background_migration(MIGRATION, :vulnerability_reads, :id, [])

    queue_batched_background_migration(
      MIGRATION,
      :vulnerability_reads,
      :id,
      batch_size: BATCH_SIZE,
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :vulnerability_reads, :id, [])
  end
end

배치 마이그레이션 딕셔너리:

milestonequeued_migration_version은 재큐잉된 마이그레이션의 것이어야 합니다 (이 예시에서는 RequeueResolveVulnerabilitiesForRemovedAnalyzers).

---
migration_job_name: ResolveVulnerabilitiesForRemovedAnalyzers
description: Resolves all detected vulnerabilities for removed analyzers.
feature_category: static_application_security_testing
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162691
milestone: '17.4'
queued_migration_version: 20240814085540
finalized_by: # version of the migration that finalized this BBM

배치 백그라운드 마이그레이션 중지 및 제거#

실행 중인 배치 백그라운드 마이그레이션을 중지하고 제거해야 할 수 있는 몇 가지 이유가 있습니다:

  • 제품 사용 사례가 변경되어 마이그레이션이 더 이상 관련이 없거나 필요하지 않은 경우.

  • 마이그레이션이 다른 로직의 다른 마이그레이션으로 대체되어야 하는 경우.

진행 중인 배치 백그라운드 마이그레이션을 중지하고 제거하려면:

  • 릴리즈 N에서 스케줄링 데이터베이스 마이그레이션의 #up#down 메서드 내용을 no-op으로 만드세요.
class BackfillNamespaceType < Gitlab::Database::Migration[2.1]
  # Reason why we don't need the BBM anymore. E.G: This BBM is no longer needed because it will be superseded by another BBM with different logic.
  def up; end

  def down; end
end
  • 릴리즈 N에서 기존 배치 마이그레이션을 삭제하는 일반 마이그레이션을 추가하세요. #up 메서드 시작 부분에 delete_batched_background_migration 메서드를 사용하여 기존 배치 백그라운드 마이그레이션을 삭제하여 기존 실행이 정리되도록 하세요.
class CleanupBackfillNamespaceType < Gitlab::Database::Migration[2.1]
  MIGRATION = "MyMigrationClass"

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    delete_batched_background_migration(MIGRATION, :vulnerabilities, :id, [])
  end

  def down; end
end
  • 릴리즈 N에서 마이그레이션 클래스 파일(lib/gitlab/background_migration/my_batched_migration.rb)과 해당 스펙도 삭제하세요.

위의 모든 단계는 단일 MR에서 구현할 수 있습니다.

Job 인수 사용#

BatchedMigrationJob은 job 클래스가 필요한 job 인수를 정의할 수 있도록 job_arguments 헬퍼 메서드를 제공합니다.

queue_batched_background_migration으로 스케줄된 배치 마이그레이션은 job 인수를 정의하는 데 반드시 헬퍼를 사용해야 합니다:

queue_batched_background_migration(
  'CopyColumnUsingBackgroundMigrationJob',
  TABLE_NAME,
  'name', 'name_convert_to_text'
)

정의된 job 인수 수가 마이그레이션 스케줄 시 제공된 job 인수 수와 일치하지 않으면 queue_batched_background_migration이 오류를 발생시킵니다.

이 예시에서 copy_fromname을 반환하고 copy_toname_convert_to_text를 반환합니다:

class CopyColumnUsingBackgroundMigrationJob < BatchedMigrationJob
  job_arguments :copy_from, :copy_to
  operation_name :update_all

  def perform
    from_column = connection.quote_column_name(copy_from)
    to_column = connection.quote_column_name(copy_to)

    assignment_clause = "#{to_column} = #{from_column}"

    each_sub_batch do |relation|
      relation.update_all(assignment_clause)
    end
  end
end

테이블의 하위 집합에 대해 마이그레이션 수행#

기본적으로 마이그레이션을 수행하는 백그라운드 job을 생성할 때 배치 백그라운드 마이그레이션은 지정된 테이블 전체를 반복합니다. 이 반복은 PrimaryKeyBatchingStrategy를 사용하여 수행됩니다. 테이블에 1000개의 레코드가 있고 배치 크기가 100이면 작업이 10개의 job으로 배치됩니다.

테이블의 하위 집합 마이그레이션은 scope_to 블록을 사용하거나 사용하지 않고 수행할 수 있습니다.

scope_to를 사용하여 선택 적용#

BBM은 scope_to 블록을 정의하는 옵션을 제공합니다. 각 배치의 최소 및 최대 범위를 결정하는 쿼리에 추가 한정자를 추가합니다.

기본적으로 배치 범위는 매우 효율적인 기본 키 인덱스를 사용하여 결정됩니다. 그러나 scope_to를 사용하면 쿼리가 주어진 조건과 일치하는 행만 고려해야 하므로 성능에 영향을 줄 수 있습니다.

범위 조건이 인덱싱되어 있고 배치 쿼리가 어떤 행도 필터링하지 않을 때만 사용해야 합니다.

적절한 인덱스의 강한 지표: 쿼리 계획에 추가 필터 없이 인덱스 전용 스캔이 있어야 합니다.

안전을 위해 Database/AvoidScopeTo cop을 사용하여 scope_to 사용을 방지합니다. 선택 쿼리가 성능적임을 확인한 후(적절한 인덱스 사용), cop을 비활성화하고 범위를 커버하는 인덱스 정의를 명시하세요:

module Gitlab
  module BackgroundMigration
    class ExpireOAuthTokens < ::Gitlab::BackgroundMigration::BatchedMigrationJob
      # rubocop:disable Database/AvoidScopeTo -- supporting index: index_oauth_access_tokens_on_id_where_expires_in_null ON oauth_access_tokens USING btree (id) WHERE (expires_in IS NULL)
      scope_to ->(relation) { relation.where(expires_in: nil) }
      operation_name :update_all

      def perform
        each_sub_batch do |sub_batch|
          sub_batch.update_all(expires_in: 2.hours)
        end
      end
      # rubocop:enable Database/AvoidScopeTo
    end
  end
end

scope_to 없이 선택 적용#

이 방식에서는 선택이 각 서브 배치로 푸시다운됩니다. 테이블 전체를 반복하는 기본 BBM 방식을 따릅니다. 배치가 기본 키에만 의존하므로 추가 인덱스가 필요하지 않습니다.

module Gitlab
  module BackgroundMigration
    class ExpireOAuthTokens < ::Gitlab::BackgroundMigration::BatchedMigrationJob
      operation_name :update_all

      def perform
        each_sub_batch do |sub_batch|
          sub_batch
            .where(expires_in: nil)
            .update_all(expires_in: 2.hours)
        end
      end
    end
  end
end

vacuum 확인을 위한 테이블 구성#

기본적으로 배치 백그라운드 마이그레이션은 반복 중인 테이블 (queue_batched_background_migration에 지정된 테이블)에서 autovacuum이 실행될 때 일시 중지됩니다. 그러나 백그라운드 마이그레이션이 반복하는 테이블에 항상 쓰는 것은 아닙니다. 이 경우 반복 테이블의 vacuum 활동으로 인해 마이그레이션을 일시 중지하는 것은 의미가 없습니다.

tables_to_check_for_vacuum 클래스 메서드를 사용하여 vacuum 활동을 확인해야 하는 테이블을 명시적으로 지정하세요. 지정된 테이블 중 하나에서 vacuum이 감지되면 마이그레이션이 일시 중지됩니다.

이 기능을 사용하는 시기#

다음의 경우에 tables_to_check_for_vacuum을 사용하세요:

  • 마이그레이션이 하나의 테이블을 반복하지만 다른 테이블에 쓰는 경우.

  • 수정되지 않는 테이블의 vacuum으로 인한 불필요한 일시 중지를 방지하려는 경우.

  • 여러 특정 테이블의 vacuum 활동을 모니터링해야 하는 경우.

예시#

merge_request_diff_files를 반복하지만 파티션 테이블 merge_request_diff_files_99208b8fac에 쓰는 마이그레이션을 고려하세요:

module Gitlab
  module BackgroundMigration
    class BackfillMergeRequestFileDiffsPartitionedTable < BackfillPartitionedTable
      operation_name :backfill
      feature_category :source_code_management

      cursor :merge_request_diff_id, :relative_order

      # Specify the actual table being written to
      tables_to_check_for_vacuum :merge_request_diff_files_99208b8fac

      def perform
        # Migration logic that writes to merge_request_diff_files_99208b8fac
        # but iterates over merge_request_diff_files
      end
    end
  end
end

이 예시에서:

  • 마이그레이션은 merge_request_diff_files를 반복합니다(queue_batched_background_migration에 지정됨).

  • 마이그레이션은 merge_request_diff_files_99208b8fac에 씁니다.

  • tables_to_check_for_vacuum :merge_request_diff_files_99208b8fac를 사용하면 반복 테이블이 아닌 파티션 테이블에서 vacuum이 실행될 때만 마이그레이션이 일시 중지됩니다.

여러 테이블 지정#

모니터링할 여러 테이블을 지정할 수 있습니다:

tables_to_check_for_vacuum :table_one, :table_two, :table_three

지정된 테이블 중 하나에서 vacuum이 실행 중이면 마이그레이션이 일시 중지됩니다.

기본 동작#

tables_to_check_for_vacuum이 지정되지 않은 경우, 마이그레이션은 기본적으로 반복 중인 테이블(queue_batched_background_migration에 지정된 테이블)의 vacuum 활동을 확인합니다.

여러 데이터베이스의 데이터 접근#

백그라운드 마이그레이션은 일반 마이그레이션과 달리 여러 데이터베이스에 접근할 수 있으며 이를 통해 데이터베이스 간에 효율적으로 데이터를 접근하고 업데이트하는 데 사용할 수 있습니다. 사용할 데이터베이스를 올바르게 지정하려면 마이그레이션 코드 내에 ActiveRecord 모델을 인라인으로 생성하는 것이 좋습니다. 이러한 모델은 테이블이 위치한 데이터베이스에 따라 올바른 ApplicationRecord를 사용해야 합니다. 따라서 ActiveRecord::Base 사용은 접근할 데이터베이스를 명시적으로 설명하지 않으므로 허용되지 않습니다.

# good
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ::ApplicationRecord
    self.table_name = 'projects'
  end

  class Build < ::Ci::ApplicationRecord
    self.table_name = 'ci_builds'
  end
end

# bad
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ActiveRecord::Base
    self.table_name = 'projects'
  end

  class Build < ActiveRecord::Base
    self.table_name = 'ci_builds'
  end
end

마찬가지로 ActiveRecord::Base.connection 사용은 허용되지 않으며 가급적 모델 connection을 사용하도록 교체해야 합니다.

# good
Project.connection.execute("SELECT * FROM projects")

# acceptable
ApplicationRecord.connection.execute("SELECT * FROM projects")

# bad
ActiveRecord::Base.connection.execute("SELECT * FROM projects")

비고유 칼럼에서 배치 처리#

기본 배치 전략은 기본 키 칼럼을 효율적으로 반복하는 방법을 제공합니다. 그러나 값이 고유하지 않은 칼럼을 반복해야 하는 경우 다른 배치 전략을 사용해야 합니다.

LooseIndexScanBatchingStrategy 배치 전략은 EachBatch의 특별 버전을 사용하여 고유 칼럼 값에 대해 효율적이고 안정적인 반복을 제공합니다.

이 예시는 issues.project_id 칼럼이 배치 칼럼으로 사용되는 배치 백그라운드 마이그레이션을 보여줍니다.

데이터베이스 포스트 마이그레이션:

class ProjectsWithIssuesMigration < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BatchProjectsWithIssues'
  BATCH_SIZE = 5000
  SUB_BATCH_SIZE = 500
  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  disable_ddl_transaction!
  def up
    queue_batched_background_migration(
      MIGRATION,
      :issues,
      :project_id,
      batch_size: BATCH_SIZE,
      batch_class_name: 'LooseIndexScanBatchingStrategy', # Override the default batching strategy
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :issues, :project_id, [])
  end
end

백그라운드 마이그레이션 클래스 구현:

module Gitlab
  module BackgroundMigration
    class BatchProjectsWithIssues < Gitlab::BackgroundMigration::BatchedMigrationJob
      include Gitlab::Database::DynamicModelHelpers

      operation_name :backfill_issues

      def perform
        distinct_each_batch do |batch|
          project_ids = batch.pluck(batch_column)
          # do something with the distinct project_ids
        end
      end
    end
  end
end

scope_to로 정의된 추가 필터LooseIndexScanBatchingStrategydistinct_each_batch에서 무시됩니다.

파티션 테이블#

파티션 테이블 작업 시 성능을 향상시키기 위해 마이그레이션을 병렬화할 수 있습니다. 여러 마이그레이션이 동시에 실행될 수 있으며(GitLab.com에서 최대 4개), 다른 파티션이나 파티션 범위를 병렬로 처리할 수 있습니다.

이 섹션에서 설명하는 패턴은 지금까지 GitLab.com에서만 사용되었으며, 특정 유형의 파티션 테이블(수동으로 관리되는 CI 슬라이딩 리스트 파티션)에서만 사용되었습니다. 다음과 같은 이유로 자체 관리 인스턴스에는 권장하지 않습니다:

  • 큐에 추가된 마이그레이션 세트가 데이터(파티션의 수와 ID)에 따라 다르므로 자체 관리 인스턴스마다 다른 마이그레이션 세트가 표시됩니다. 이는 이미 백그라운드 마이그레이션에 대한 좌절감을 느끼는 자체 관리 관리자에게 혼란을 줄 수 있습니다.

  • 뷰 기반 병렬화는 프로덕션 데이터를 기반으로 ID 범위를 미리 계산해야 하므로 자체 관리 배포에서는 일반적으로 실행하기 어렵습니다.

이러한 패턴을 사용하기 전에 데이터베이스 팀과 상의하여 해당 사용 사례에 트레이드오프가 허용 가능한지 확인하세요.

패턴 1: 파티션별 병렬화#

파티션마다 하나의 BBM을 큐에 추가하여 병렬로 실행되도록 합니다.

사용 시기: 테이블에 여러 파티션이 있고 각 파티션을 독립적으로 마이그레이션할 수 있는 경우. GitLab.com에서만 또는 안정적이고 잘 알려진 파티션 세트가 있는 테이블에서만 이 패턴을 사용하세요.

마이그레이션 큐잉 예시:

class QueueMyMigration < Gitlab::Database::Migration[2.3]
  MIGRATION = 'MyBatchedMigration'
  TABLE_NAME = :my_partitioned_table

  def up
    Gitlab::Database::PostgresPartitionedTable.each_partition(TABLE_NAME) do |partition|
      next if empty_partition?(partition)

      queue_batched_background_migration(MIGRATION, partition.identifier, :id)
    end
  end

  def down
    Gitlab::Database::PostgresPartitionedTable.each_partition(TABLE_NAME) do |partition|
      delete_batched_background_migration(MIGRATION, partition.identifier, :id, [])
    end
  end

  private

  def empty_partition?(partition)
    !connection.select_value("SELECT true FROM #{partition.identifier} LIMIT 1")
  end

  # Workaround to allow a single migration to enqueue multiple background migrations
  def assign_attributes_safely(migration, max_batch_size, batch_table_name, gitlab_schema, _queued_migration_version)
    super(migration, max_batch_size, batch_table_name, gitlab_schema, nil)
  end
end

완료: 파티션별 마이그레이션 완료 예시는 MR !223822를 참조하세요.

주요 고려사항:

  • 불필요한 마이그레이션을 방지하기 위해 빈 파티션은 건너뜁니다

  • 큐잉 후 생성된 새 파티션은 자동으로 포함되지 않습니다

  • 각 파티션의 마이그레이션은 독립적으로 실행되며 별도로 모니터링할 수 있습니다

  • 큐에 추가된 마이그레이션 세트는 큐잉 시점에 존재하는 파티션에 따라 다르므로 인스턴스마다 다를 수 있습니다. 이는 자체 관리 릴리즈에 적합하지 않습니다.

  • 파티션이 분리되어 삭제되는 경우(예: 매일 또는 매월 파티션이 만료됨) 마이그레이션이 시작되거나 완료되기 전에 마이그레이션이 실패하거나 완료되지 않습니다. 마이그레이션 전체 기간 동안 존재가 보장된 파티션에만 이 패턴을 사용하세요.

패턴 2: 뷰 기반 병렬화#

파티션을 여러 범위로 분할하는 데이터베이스 뷰를 생성한 다음 각 뷰에 대해 별도의 BBM을 큐에 추가합니다. 이 패턴은 단일 파티션이 너무 크거나 "테이블당 하나의 마이그레이션" 프레임워크 제한을 우회해야 할 때 유용합니다.

사용 시기:

  • 단일 파티션을 마이그레이션하는 데 너무 많은 개월/년이 필요한 경우

  • "활성 마이그레이션은 테이블당 하나" 프레임워크 제한을 우회해야 하는 경우

  • GitLab.com에서 마이그레이션이 실행되어 프로덕션 데이터에서 미리 뷰 경계를 계산할 수 있는 경우

이 패턴은 자체 관리 인스턴스에는 적합하지 않습니다. 뷰 경계는 실제 데이터 분포에서 미리 계산해야 하는데, 이는 자체 관리 설치 전반에 걸쳐 일반적으로 수행하기 어렵습니다.

뷰 생성 예시:

파티션을 분할하기 위해 ID 범위가 있는 뷰를 생성합니다. 미리 뷰 경계를 계산하여 특정 행 위치의 실제 ID 값을 찾습니다. 올바른 ID 범위를 찾는 것은 비용이 많이 들고 마이그레이션 중에 계산하면 타임아웃이 발생할 수 있습니다.

class CreatePartitionViews < Gitlab::Database::Migration[2.3]
  # Pre-calculate view boundaries using COUNT and OFFSET queries
  # For 4 views, divide total row count by 4 and find the ID at each boundary:
  # SELECT id FROM p_ci_builds WHERE partition_id = 100 ORDER BY id LIMIT 1 OFFSET 0;
  # SELECT id FROM p_ci_builds WHERE partition_id = 100 ORDER BY id LIMIT 1 OFFSET <total_rows/4>;
  # SELECT id FROM p_ci_builds WHERE partition_id = 100 ORDER BY id LIMIT 1 OFFSET <total_rows/2>;
  # etc.
  VIEW_BOUNDARIES = [1, 1500384395, 2951960143, 4355055910, 12168556334].freeze
  VIEW_PREFIX = 'gitlab_partitions_dynamic.ci_builds_views_100'

  def up
    view_ranges.each_with_index do |range, index|
      create_view(index + 1, range)
    end
  end

  def down
    view_ranges.each_with_index do |_, index|
      execute("DROP VIEW IF EXISTS #{VIEW_PREFIX}_#{index + 1};")
    end
  end

  private

  def view_ranges
    VIEW_BOUNDARIES.each_cons(2).map { |lower, upper| (lower..upper) }
  end

  def create_view(view_number, range)
    execute(<<~SQL.squish)
      CREATE OR REPLACE VIEW #{VIEW_PREFIX}_#{view_number} AS
      SELECT id, partition_id
      FROM p_ci_builds
      WHERE id >= #{range.min} AND id < #{range.max} AND partition_id = 100
    SQL
  end
end

마이그레이션 큐잉 예시:

class SplitMigration < Gitlab::Database::Migration[2.3]
  MIGRATION = 'MyBatchedMigration'
  VIEW_PREFIX = 'gitlab_partitions_dynamic.ci_builds_views_100'
  VIEW_BOUNDARIES = [1, 1500384395, 2951960143, 4355055910, 12168556334].freeze
  TOTAL_TUPLE_COUNT = 4774979600

  def up
    VIEW_BOUNDARIES.each_cons(2).map.with_index(1) do |range, view_number|
      queue_batched_background_migration(
        MIGRATION,
        "#{VIEW_PREFIX}_#{view_number}",
        :id,
        batch_min_value: range.first,
        batch_max_value: range.last
      )
    end

    # Update tuple count statistics for accurate progress reporting
    Gitlab::Database::BackgroundMigration::BatchedMigration
      .where(job_class_name: MIGRATION)
      .update_all(total_tuple_count: TOTAL_TUPLE_COUNT / (VIEW_BOUNDARIES.size - 1))
  end

  def down
    1.upto(VIEW_BOUNDARIES.size - 1) do |view_number|
      delete_batched_background_migration(MIGRATION, "#{VIEW_PREFIX}_#{view_number}", :id, [])
    end
  end

  private

  def assign_attributes_safely(migration, max_batch_size, batch_table_name, gitlab_schema, _queued_migration_version)
    super(migration, max_batch_size, batch_table_name, gitlab_schema, nil)
  end
end

이미 실행 중인 기존 마이그레이션을 처리하는 완전한 예시는 MR !221430을 참조하세요.

트레이드오프:

  • 뷰는 autovacuum 제한을 우회하므로(테이블 비대화 유발 가능) WAL 제한은 여전히 적용됩니다

  • 필요한 경우에만 사용하세요(예: 마이그레이션이 7개월 이상 걸리는 경우)

  • 사용 가능한 모든 워커를 활용하여 마이그레이션 시간을 개월에서 주 단위로 단축합니다

  • 동일한 기간 동안 같은 데이터베이스에 큐에 추가된 다른 마이그레이션이 지연될 수 있도록 하나의 테이블에 대한 워커 슬롯을 포화시킵니다

  • 프로덕션 데이터에서 미리 계산된 뷰 경계가 필요하여 자체 관리 배포에는 실행하기 어렵습니다

실제 사례: MR !221430 - MoveCiBuildsMetadata

  • 원래: 파티션 100에서 1개의 마이그레이션 (47억 행, ~7개월)

  • 이후: 뷰에서 4개의 마이그레이션 (병렬화, 총 ~2개월)

배치 백그라운드 마이그레이션의 전체 시간 추정 계산#

BBM이 완료되는 데 걸리는 시간을 추정하는 것이 가능합니다. GitLab은 이미 db:gitlabcom-database-testing 파이프라인을 통해 추정치를 제공합니다. 이 추정치는 테스트 환경에서 프로덕션 데이터를 샘플링하여 구축되며 마이그레이션이 걸릴 수 있는 최대 시간을 나타내고, 실제 마이그레이션 시간을 반드시 나타내지는 않습니다. 특정 시나리오에서 db:gitlabcom-database-testing 파이프라인이 제공하는 추정치만으로는 마이그레이션 중인 레코드와 관련된 모든 특수성을 계산하기에 충분하지 않아 추가 계산이 필요할 수 있습니다. 이 경우 interval * number of records / max batch size 공식을 사용하여 마이그레이션이 걸리는 시간의 대략적인 추정치를 결정할 수 있습니다. 여기서 intervalmax batch size는 job에 정의된 옵션을 참조하며, total tuple count는 마이그레이션할 레코드 수입니다.

추정치는 마이그레이션 최적화 메커니즘에 의해 영향을 받을 수 있습니다.

배치 백그라운드 마이그레이션 정리#

나머지 백그라운드 마이그레이션 정리는 메이저 또는 마이너 릴리즈에서만 수행해야 합니다. 패치 릴리즈에서는 수행해서는 안 됩니다.

백그라운드 마이그레이션은 시간이 오래 걸릴 수 있으므로 큐에 추가한 후 즉시 정리할 수 없습니다. 예를 들어, job이 실패할 것이므로 마이그레이션 프로세스에 사용된 칼럼을 삭제할 수 없습니다. 남은 job을 완료하기 위해 향후 릴리즈에서 별도의 포스트 배포 마이그레이션을 추가해야 합니다. (예: 칼럼 제거)

칼럼 foo(큰 JSON blob 포함)에서 칼럼 bar(문자열 포함)로 데이터를 마이그레이션하려면:

  • 릴리즈 A:

주어진 ID를 가진 행에 대해 마이그레이션을 수행하는 마이그레이션 클래스를 생성합니다.

  • 다음 기술 중 하나를 사용하여 새 행을 업데이트합니다:

애플리케이션 로직이 필요 없는 복사 작업에 대한 새 트리거를 생성합니다.

  • 레코드가 생성되거나 업데이트될 때 모델/서비스에서 이 작업을 처리합니다.

  • 레코드를 업데이트하는 새 커스텀 백그라운드 job을 생성합니다.

  • 포스트 배포 마이그레이션에서 모든 기존 행에 대한 배치 백그라운드 마이그레이션을 큐에 추가합니다.

  • 릴리즈 B:

배치 백그라운드 마이그레이션이 완료되었는지 확인하는 포스트 배포 마이그레이션을 추가합니다.

  • 애플리케이션이 새 칼럼을 사용하기 시작하고 새 레코드 업데이트를 중지하도록 코드를 배포합니다.

  • 이전 칼럼을 제거합니다.

이전 버전의 GitLab에서 프로젝트를 가져올 때 데이터가 새 형식이어야 하는 경우 가져오기/내보내기 버전을 업그레이드해야 할 수 있습니다.

배치 백그라운드 마이그레이션을 지원하기 위한 인덱스 추가#

배치 백그라운드 마이그레이션을 지원하기 위해 새 인덱스나 임시 인덱스를 추가해야 하는 경우가 있습니다. 이를 위해 백그라운드 마이그레이션을 큐에 추가하는 포스트 배포 마이그레이션보다 먼저 포스트 배포 마이그레이션에서 인덱스를 생성하세요.

인덱스가 생성 후 바로 사용될 수 있도록 하는 특별한 주의가 필요한 경우에 대한 추가 정보는 데이터베이스 인덱스 추가 문서를 참조하세요.

데이터베이스 테스팅 파이프라인에서 특정 배치 실행#

데이터베이스 메인테이너만 데이터베이스 테스팅 파이프라인 아티팩트를 볼 수 있습니다. 이 방법을 사용해야 하는 경우 도움을 요청하세요.

GitLab.com의 특정 배치에서 배치 백그라운드 마이그레이션이 실패했고 어떤 쿼리가 실패했는지, 그 이유가 무엇인지 파악하고 싶다고 가정해 봅시다. 현재는 쿼리 정보(특히 쿼리 파라미터)를 가져오는 좋은 방법이 없으며 더 많은 로깅으로 전체 마이그레이션을 재실행하는 것은 오랜 시간이 걸리는 프로세스입니다.

다행히도 데이터베이스 마이그레이션 파이프라인을 활용하여 추가 로깅 및/또는 수정으로 특정 배치를 재실행하여 문제가 해결되는지 확인할 수 있습니다.

예시는 Draft: Test PG::CardinalityViolation fix를 참조하되 반드시 전체 섹션을 읽으세요.

이를 위해 다음이 필요합니다:

배치 start_id와 end_id 찾기#

Kibana에서 찾을 수 있어야 합니다.

일반 마이그레이션 생성#

일반 마이그레이션의 up 블록에서 배치를 스케줄합니다:

def up
  instance = Gitlab::BackgroundMigration::YourBackgroundMigrationClass.new(
      start_id: <batch start_id>,
      end_id: <batch end_id>,
      batch_table: <table name>,
      batch_column: <batching column>,
      sub_batch_size: <sub batch size>,
      pause_ms: <milliseconds between batches>,
      job_arguments: <job arguments if any>,
      connection: connection
    )

    instance.perform
end

def down
  # no-op
end

마이그레이션 헬퍼에 대한 임시 해결책 적용 (선택사항)#

배치 백그라운드 마이그레이션이 restrict_gitlab_migration 헬퍼로 지정한 것과 다른 스키마의 테이블을 건드리는 경우 (예: 스케줄링 마이그레이션에 restrict_gitlab_migration gitlab_schema: :gitlab_main_org가 있지만 백그라운드 job이 :gitlab_ci 스키마의 테이블을 사용하는 경우) 마이그레이션이 실패합니다. 이를 방지하려면 데이터베이스 헬퍼를 몽키 패치하여 테스팅 파이프라인 job이 실패하지 않도록 해야 합니다:

diff --git a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
index b8d1d21a0d2d2a23d9e8c8a0a17db98ed1ed40b7..912e20659a6919f771045178c66828563cb5a4a1 100644
--- a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
+++ b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
@@ -55,7 +55,7 @@ def unmatched_schemas
         end

         def allowed_schemas_for_connection
-          Gitlab::Database.gitlab_schemas_for_connection(connection)
+          Gitlab::Database.gitlab_schemas_for_connection(connection) << :gitlab_ci
         end
       end
     end

diff --git a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
index 4ae3622479f0800c0553959e132143ec9051898e..d556ec7f55adae9d46a56665ce02de782cb09f2d 100644
--- a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
+++ b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
@@ -79,7 +79,7 @@ def restrict_to_dml_only(parsed)
             tables = self.dml_tables(parsed)
             schemas = self.dml_schemas(tables)

-            if (schemas - self.allowed_gitlab_schemas).any?
+            if (schemas - (self.allowed_gitlab_schemas << :gitlab_ci)).any?
               raise DMLAccessDeniedError, \
                 "Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
                 "which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'. " \

데이터베이스 마이그레이션 파이프라인 시작#

변경 사항으로 Draft 머지 리퀘스트를 생성하고 수동 db:gitlabcom-database-testing job을 트리거합니다.

의존성 설정#

일부 경우에 마이그레이션은 이전에 큐에 추가된 BBM의 완료에 의존합니다. BBM이 여전히 실행 중인 경우 의존 마이그레이션이 실패합니다. 예를 들어: 대규모 테이블에 고유 인덱스를 도입하는 것은 중복 레코드를 처리하기 위해 이전에 큐에 추가된 BBM에 의존할 수 있습니다.

다음 프로세스는 마이그레이션 작성 시 의존성을 더 명확하게 하도록 구성되었습니다.

  • BBM을 큐에 추가한 마이그레이션의 버전이 batched_background_migrations 테이블과 BBM 딕셔너리 파일에 저장됩니다.

  • DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS 상수가 각 마이그레이션 파일에 추가됩니다(기본으로 주석 처리됨). 의존성을 설정하려면 의존하는 BBM의 queued_migration_version을 추가하세요. 그렇지 않으면 주석 줄을 제거하세요.

  • Migration::UnfinishedDependencies cop은 의존하는 BBM이 아직 완료되지 않은 경우 불평합니다. BBM 딕셔너리에서 finalized_by 키를 조회하여 완료 여부를 결정합니다.

예시:

# db/post_migrate/20231113120650_queue_backfill_routes_namespace_id.rb
class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BackfillRouteNamespaceId'

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org
  ...
  ...

  def up
    queue_batched_background_migration(
      MIGRATION,
      ...
    )
  end
end
# This depends on the finalization of QueueBackfillRoutesNamespaceId BBM
class AddNotNullToRoutesNamespaceId < Gitlab::Database::Migration[2.1]
  DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = ["20231113120650"]

  def up
    add_not_null_constraint :routes, :namespace_id
  end

  def down
    remove_not_null_constraint :routes, :namespace_id
  end
end

고객을 위한 업그레이드 참고 사항 작성#

중요한 배치 백그라운드 마이그레이션의 경우, 자체 관리 및 Dedicated 고객이 업그레이드를 계획할 수 있도록 업그레이드 참고 사항을 추가해야 합니다. 이 참고 사항은 관련 버전의 업그레이드 문서에 추가해야 합니다 (예: GitLab 18 변경 사항).

잘 문서화된 업그레이드 참고 사항의 예시는 MR 214376을 참조하세요. 이 문서는 CI 빌드 메타데이터 마이그레이션을 기록합니다.

업그레이드 참고 사항을 추가하는 시기#

마이그레이션에 다음 중 하나라도 해당하는 경우 업그레이드 참고 사항을 추가하세요:

  • 마이그레이션이 완료하는 데 상당한 시간이 걸릴 수 있는 대규모 테이블에서 작동하는 경우.

  • 마이그레이션이 고객이 마이그레이션 범위를 제어할 수 있는 구성 옵션을 제공하는 경우.

  • 마이그레이션이 다른 마이그레이션이나 기능에 의존하는 경우.

업그레이드 참고 사항에 포함할 내용#

업그레이드 참고 사항에는 고객이 마이그레이션을 이해하고 준비할 수 있도록 다음 정보가 포함되어야 합니다:

  • 마이그레이션의 목적과 이점을 설명하여 고객이 그 영향을 이해할 수 있게 합니다.

  • 마이그레이션이 고객의 데이터에 무엇을 하는지, 어떤 테이블을 반복하는지 설명합니다.

  • 타임라인과 완료 일정을 문서화합니다. 완료 일정이 아직 알려지지 않은 경우 이슈로 링크합니다. 알려진 경우(향후 날짜에도) 실제 완료를 포함하는 릴리즈로 기존 업그레이드 참고 사항을 업데이트합니다.

  • 업그레이드 전 준비 단계를 설명합니다. 해당하는 경우 모범 사례와 적용할 설정을 권장합니다.

  • 마이그레이션 기간을 추정하는 도구를 제공합니다(SQL 쿼리 또는 Rails 콘솔 명령어).

  • 가능한 경우 마이그레이션 범위를 줄이는 제어 방법을 문서화합니다. 어떤 데이터가 영향을 받거나 받지 않는지 명확하게 알 수 있도록 최종 사용자 관점에서 제어 방법을 설명합니다.

관리#

BBM 관리는 GitLab 팀원에게만 제한된 chatops 통합을 통해 이루어집니다.

배치 백그라운드 마이그레이션 목록 조회#

시스템의 배치 백그라운드 마이그레이션 목록을 조회하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations list

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값).

  • ci: CI 데이터베이스를 사용합니다.

  • sec: 보안 데이터베이스를 사용합니다.

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

  • job 클래스로 필터

--job-class-name JOB_CLASS_NAME: 지정된 job 클래스의 job만 나열합니다.

  • 이것은 백그라운드 마이그레이션의 YAML 정의에서 migration_job_name입니다.

출력 예시:

[

](/19.1/development/database/img/list_v15_4.png)

ChatOps는 created_at (DESC) 기준으로 정렬된 20개의 배치 백그라운드 마이그레이션을 반환합니다.

배치 백그라운드 마이그레이션의 진행률과 상태 모니터링#

특정 배치 백그라운드 마이그레이션의 상태와 진행률을 확인하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations status MIGRATION_ID

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값)

  • ci: CI 데이터베이스를 사용합니다

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

출력 예시:

[

](/19.1/development/database/img/status_v15_4.png)

Progress는 완료된 백그라운드 마이그레이션의 백분율을 나타냅니다.

마이그레이션이 커서를 사용하는 경우 진행률 백분율이 올바르게 보고되지 않을 수 있습니다.

배치 백그라운드 마이그레이션 상태 정의:

  • Active: 다음 중 하나:

러너가 선택할 준비가 됨.

  • 배치 job 실행 중.

  • Finalizing: 배치 job 실행 중.

  • Failed: 배치 백그라운드 마이그레이션 실패.

  • Finished: 모든 job이 성공적으로 실행되어 배치 백그라운드 마이그레이션이 완료됨.

  • Paused: 러너에게 보이지 않음.

  • Finalized: 배치 마이그레이션이 ensure_batched_background_migration_is_finished로 검증되어 완료됨.

배치 백그라운드 마이그레이션 일시 중지#

배치 백그라운드 마이그레이션을 일시 중지하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations pause MIGRATION_ID

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값).

  • ci: CI 데이터베이스를 사용합니다.

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

출력 예시:

[

](/19.1/development/database/img/pause_v15_4.png)

active 상태의 배치 백그라운드 마이그레이션만 일시 중지할 수 있습니다.

배치 백그라운드 마이그레이션 재개#

배치 백그라운드 마이그레이션을 재개하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations resume MIGRATION_ID

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값).

  • ci: CI 데이터베이스를 사용합니다.

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

출력 예시:

[

](/19.1/development/database/img/resume_v15_4.png)

active 상태의 배치 백그라운드 마이그레이션만 재개할 수 있습니다.

백그라운드 마이그레이션 활성화 또는 비활성화#

극히 제한된 상황에서 GitLab 관리자는 기능 플래그를 비활성화할 수 있습니다:

  • execute_batched_migrations_on_schedule

이 플래그는 기본적으로 활성화되어 있습니다. 데이터베이스 호스트 유지 관리와 같은 특별한 상황에서 데이터베이스 작업을 제한하기 위한 최후의 수단으로만 비활성화하세요.

이 플래그를 비활성화할 때는 그 결과를 충분히 이해한 후에만 하세요. execute_batched_migrations_on_schedule 기능 플래그를 비활성화하면 GitLab 업그레이드가 실패하고 데이터 손실이 발생할 수 있습니다.

EE 전용 기능을 위한 배치 백그라운드 마이그레이션#

EE 전용 기능의 모든 백그라운드 마이그레이션 클래스는 GitLab FOSS에 있어야 합니다. 이를 위해 GitLab FOSS에 빈 클래스를 생성하고 Enterprise Edition 기능 구현 가이드라인에 설명된 대로 GitLab EE에서 이를 확장하세요.

job 인수를 사용하는 EE 전용 기능의 백그라운드 마이그레이션 클래스는 GitLab FOSS 클래스에서 정의해야 합니다. GitLab FOSS 컨텍스트에서 마이그레이션이 스케줄될 때 job 인수 검증이 실패하지 않도록 정의가 필요합니다.

제너레이터를 사용하여 새 배치 백그라운드 마이그레이션을 생성할 때 --ee-only 플래그를 전달하면 EE 전용 마이그레이션 스캐폴드를 생성할 수 있습니다.

디버그#

실패 오류 로그 보기#

두 가지 방법으로 실패를 확인할 수 있습니다:

GitLab 로그 통해:

배치 백그라운드 마이그레이션 실행 후 job이 실패하면 Kibana에서 로그를 봅니다. 프로덕션 Sidekiq 로그를 보고 다음으로 필터링합니다:

json.new_state: failed

  • json.job_class_name:

  • json.job_arguments:

json.exception_classjson.exception_message 값을 검토하여 job이 실패한 이유를 이해합니다.

재시도 메커니즘을 기억하세요. 실패가 있다고 해서 job이 실패한 것은 아닙니다. 항상 job의 마지막 상태를 확인하세요.

데이터베이스 통해:

배치 백그라운드 마이그레이션 CLASS_NAME을 가져옵니다.

PostgreSQL 콘솔에서 다음 쿼리를 실행합니다:

 SELECT migration.id, migration.job_class_name, transition_logs.exception_class, transition_logs.exception_message
 FROM batched_background_migrations as migration
 INNER JOIN batched_background_migration_jobs as jobs
 ON jobs.batched_background_migration_id = migration.id
 INNER JOIN batched_background_migration_job_transition_logs as transition_logs
 ON transition_logs.batched_background_migration_job_id = jobs.id
 WHERE transition_logs.next_status = '2' AND migration.job_class_name = "CLASS_NAME";

테스팅#

다음에 대한 테스트 작성이 필요합니다:

  • 배치 백그라운드 마이그레이션 큐잉 마이그레이션.

  • 배치 백그라운드 마이그레이션 자체.

  • 정리 마이그레이션.

beforeafter RSpec 훅은 데이터베이스를 다운 및 업 마이그레이션합니다. 이러한 훅은 다른 배치 백그라운드 마이그레이션이 호출되는 결과를 초래할 수 있습니다. it 블록에 정의된 기대가 RSpec 훅에서 호출되는 것과 충돌할 수 있으므로 일반 테스트 더블 대신 have_received와 함께 spy 테스트 더블을 사용하는 것을 권장합니다. 자세한 내용은 이슈 #35351을 참조하세요.

모범 사례#

처리하는 데이터의 양을 파악하세요.

배치 백그라운드 마이그레이션 job이 멱등적인지 확인하세요.

작성한 테스트가 거짓 양성이 아닌지 확인하세요.

마이그레이션하는 데이터가 중요하고 손실될 수 없는 경우, 정리 마이그레이션도 완료 전에 데이터의 최종 상태를 확인해야 합니다.

데이터베이스 전문가와 숫자를 논의하세요. 마이그레이션이 예상보다 DB에 더 많은 부담을 줄 수 있습니다. 스테이징에서 측정하거나 프로덕션에서 측정해 달라고 요청하세요.

배치 백그라운드 마이그레이션을 실행하는 데 필요한 시간을 파악하세요.

job 클래스 내에서 예외를 자동으로 처리할 때 주의하세요. 이로 인해 실패 시나리오에서도 job이 성공으로 표시될 수 있습니다.

# good
def perform
  each_sub_batch do |sub_batch|
    sub_batch.update_all(name: 'My Name')
  end
end

# acceptable
def perform
  each_sub_batch do |sub_batch|
    sub_batch.update_all(name: 'My Name')
  rescue Exception => error
    logger.error(message: error.message, class: error.class)

    raise
  end
end

# bad
def perform
  each_sub_batch do |sub_batch|
    sub_batch.update_all(name: 'My Name')
  rescue Exception => error
    logger.error(message: error.message, class: self.class.name)
  end
end

가능하면 각 모델을 개별적으로 업데이트하는 대신 단일 쿼리로 전체 서브 배치를 업데이트하세요. 그렇게 할 때는 항상 한도 가드를 포함하고 이를 구체화된 CTE로 추출하여 쿼리 계획 변경의 가능성을 제거하세요. 이는 시나리오에 따라 다양한 방법으로 달성할 수 있습니다.

UPDATE 쿼리를 생성하고 FROM을 사용하여 필요한 값을 제공하는 테이블을 조인합니다 (예시).

  • 미리 계산된 값을 전달하기 위해 UPDATE 쿼리를 생성하고 FROM(VALUES( ...))를 사용합니다 (예시).

  • 모든 키와 값을 ActiveRelation#update에 전달합니다.

# good
def perform
  each_sub_batch do |sub_batch|
    connection.execute <<~SQL
      WITH sub_batch_ids AS MATERIALIZED (
        #{sub_batch.select(:id).limit(sub_batch_size).to_sql}
      )
      UPDATE fork_networks
      SET organization_id = projects.organization_id
      FROM projects
      WHERE fork_networks.id IN (SELECT id FROM sub_batch_ids)
      AND fork_networks.root_project_id = projects.id
      AND fork_networks.organization_id IS NULL
    SQL
  end
end

# bad - uses pluck and does not use a limit
def perform
  each_sub_batch do |sub_batch|
    connection.execute <<~SQL
      UPDATE fork_networks
      SET organization_id = projects.organization_id
      FROM projects
      WHERE fork_networks.id IN (#{sub_batch.pluck(:id)})
      AND fork_networks.root_project_id = projects.id
      AND fork_networks.organization_id IS NULL
    SQL
  end
end

# bad
def perform
  each_sub_batch do |sub_batch|
    sub_batch.each |fork_network|
      fork_network.update!(organization_id: fork_network.root_project.organization_id)
    end
  end
end

예시#

Routes 사용 사례#

routes 테이블에는 다형성 관계에 사용되는 source_type 필드가 있습니다. 데이터베이스 재설계의 일환으로 다형성 관계를 제거하고 있습니다. 이 작업의 한 단계는 source_id 칼럼의 데이터를 새로운 단일 외래 키로 마이그레이션하는 것입니다. 나중에 이전 행을 삭제할 것이므로 백그라운드 마이그레이션의 일부로 업데이트할 필요가 없습니다.

제너레이터를 사용하여 배치 백그라운드 마이그레이션 파일을 생성하는 것으로 시작하세요:

bundle exec rails g batched_background_migration BackfillRouteNamespaceId --table_name=routes --column_name=id --feature_category=source_code_management

source_id 값을 namespace_id로 복사하도록 마이그레이션 job(BatchedMigrationJob의 서브클래스)을 업데이트하세요:

class Gitlab::BackgroundMigration::BackfillRouteNamespaceId < BatchedMigrationJob
  # For illustration purposes, if we were to use a local model we could
  # define it like below, using an `ApplicationRecord` as the base class
  # class Route < ::ApplicationRecord
  #   self.table_name = 'routes'
  # end

  operation_name :update_all
  feature_category :source_code_management

  def perform
    each_sub_batch(
      batching_scope: -> (relation) { relation.where("source_type <> 'UnusedType'") }
    ) do |sub_batch|
      sub_batch.update_all('namespace_id = source_id')
    end
  end
end

job 클래스는 배치 마이그레이션 프레임워크가 올바르게 처리되도록 BatchedMigrationJob에서 상속합니다. BatchedMigrationJob의 모든 서브클래스는 배치를 실행하는 데 필요한 인수와 추적 데이터베이스에 대한 연결로 초기화됩니다.

데이터베이스에 새 트리거를 추가하는 데이터베이스 마이그레이션을 생성하세요. 예시:

class AddTriggerToRoutesToCopySourceIdToNamespaceId < Gitlab::Database::Migration[2.1]
  FUNCTION_NAME = 'example_function'
  TRIGGER_NAME = 'example_trigger'

  def up
    execute(<<~SQL)
      CREATE OR REPLACE FUNCTION #{FUNCTION_NAME}() RETURNS trigger
      LANGUAGE plpgsql
      AS $
      BEGIN
        NEW."namespace_id" = NEW."source_id"
        RETURN NEW;
      END;
      $;

      CREATE TRIGGER #{TRIGGER_NAME}() AFTER INSERT OR UPDATE
      ON routes
      FOR EACH ROW EXECUTE FUNCTION #{FUNCTION_NAME}();
    SQL
  end

  def down
    drop_trigger(TRIGGER_NAME, :routes)
    drop_function(FUNCTION_NAME)
  end
end

필요한 배치 크기로 생성된 포스트 배포 마이그레이션을 업데이트하세요:

class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BackfillRouteNamespaceId'
  BATCH_SIZE = 1000
  SUB_BATCH_SIZE = 100

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    queue_batched_background_migration(
      MIGRATION,
      :routes,
      :id,
      batch_size: BATCH_SIZE,
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :routes, :id, [])
  end
end
 # db/docs/batched_background_migrations/backfill_route_namespace_id.yml
 ---
 migration_job_name: BackfillRouteNamespaceId
 description: Copies source_id values from routes to namespace_id
 feature_category: source_code_management
 introduced_by_url: "https://mr_url"
 milestone: 16.6
 queued_migration_version: 20231113120650
 finalized_by: # version of the migration that ensured this bbm

배치 백그라운드 마이그레이션을 큐에 추가할 때는 실제 변경을 수행하는 데이터베이스로 스키마를 제한해야 합니다. 이 경우 routes 레코드를 업데이트하므로 restrict_gitlab_migration gitlab_schema: :gitlab_main_org을 설정합니다. 그러나 CI 데이터 마이그레이션을 수행해야 하는 경우 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정합니다.

배포 후 애플리케이션은:

이전과 같이 데이터를 계속 사용합니다.

  • 기존 데이터와 새 데이터가 모두 마이그레이션되도록 합니다.

배치 백그라운드 마이그레이션이 완료되었는지 확인하는 새 포스트 배포 마이그레이션을 추가하세요. 또한 BBM 딕셔너리의 finalized_by 속성을 이 마이그레이션의 버전으로 업데이트하세요.

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

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    ensure_batched_background_migration_is_finished(
      job_class_name: 'BackfillRouteNamespaceId',
      table_name: :routes,
      column_name: :id,
      job_arguments: [],
      finalize: true
    )
  end

  def down
    # no-op
  end
end
 # db/docs/batched_background_migrations/backfill_route_namespace_id.yml
 ---
 migration_job_name: BackfillRouteNamespaceId
 description: Copies source_id values from routes to namespace_id
 feature_category: source_code_management
 introduced_by_url: "https://mr_url"
 milestone: 16.6
 queued_migration_version: 20231113120650
 finalized_by: 20231115120912

배치 백그라운드 마이그레이션이 완료되지 않은 경우, 시스템이 배치 백그라운드 마이그레이션을 인라인으로 실행합니다. 이 동작을 원하지 않으면 finalize: false를 전달해야 합니다.

애플리케이션이 데이터가 100% 마이그레이션되는 것에 의존하지 않는 경우 (예: 데이터가 권고적이고 미션 크리티컬하지 않은 경우), 이 마지막 단계를 건너뛸 수 있습니다. 이 단계는 마이그레이션이 완료되고 모든 행이 마이그레이션되었음을 확인합니다.

트리거를 제거하는 데이터베이스 마이그레이션을 추가하세요.

class RemoveNamepaceIdTriggerFromRoutes < Gitlab::Database::Migration[2.1]
  FUNCTION_NAME = 'example_function'
  TRIGGER_NAME = 'example_trigger'

  def up
    drop_trigger(TRIGGER_NAME, :routes)
    drop_function(FUNCTION_NAME)
  end

  def down
    # Should reverse the trigger and the function in the up method of the migration that added it
  end
end

배치 마이그레이션이 완료되면 routes.namespace_id의 데이터가 채워졌다고 안전하게 의존할 수 있습니다.

배치 백그라운드 마이그레이션

GitLab v19.1
원문 보기
요약

배치 백그라운드 마이그레이션은 마이그레이션이 가이드라인의 시간 제한을 초과하는 경우 데이터 마이그레이션을 수행하는 데 사용해야 합니다. 배치 백그라운드 마이그레이션은 레거시 백그라운드 마이그레이션 프레임워크를 대체했습니다.

배치 백그라운드 마이그레이션은 마이그레이션이 가이드라인의 시간 제한을 초과하는 경우 데이터 마이그레이션을 수행하는 데 사용해야 합니다. 예를 들어, 단일 JSON 칼럼에 저장된 데이터를 별도 테이블로 마이그레이션할 때 배치 백그라운드 마이그레이션을 사용할 수 있습니다.

배치 백그라운드 마이그레이션은 레거시 백그라운드 마이그레이션 프레임워크를 대체했습니다. 해당 프레임워크와 관련된 변경 사항은 해당 문서를 참조하세요.

배치 백그라운드 마이그레이션 프레임워크는 ChatOps를 지원합니다. GitLab 엔지니어는 ChatOps를 통해 시스템에 존재하는 배치 백그라운드 마이그레이션과 상호작용할 수 있습니다.

배치 백그라운드 마이그레이션을 언제 사용하는가#

일반 Rails 마이그레이션으로 수행할 경우 가이드라인의 시간 제한을 초과할 만큼 많은 행이 포함된 테이블에서 데이터를 마이그레이션할 때 배치 백그라운드 마이그레이션을 사용하세요.

  • 배치 백그라운드 마이그레이션은 트래픽이 많은 테이블에서 데이터를 마이그레이션할 때 사용해야 합니다.

  • 배치 백그라운드 마이그레이션은 대용량 데이터셋의 각 항목에 대해 여러 단일 행 쿼리를 실행할 때도 사용할 수 있습니다. 일반적으로 단일 레코드 패턴의 경우 런타임은 데이터셋 크기에 크게 의존합니다. 데이터셋을 적절히 분할하여 백그라운드 마이그레이션으로 처리하세요.

  • 스키마 마이그레이션을 수행하는 데는 배치 백그라운드 마이그레이션을 사용하지 마세요.

백그라운드 마이그레이션이 도움이 될 수 있는 경우:

  • 하나의 테이블에서 여러 개의 개별 테이블로 이벤트를 마이그레이션하는 경우.

  • 다른 칼럼에 저장된 JSON을 기반으로 하나의 칼럼을 채우는 경우.

  • 외부 서비스의 출력에 의존하는 데이터를 마이그레이션하는 경우. (예: API)

참고 사항#

  • 배치 백그라운드 마이그레이션이 중요한 업그레이드의 일부인 경우, 릴리즈 포스트에 공지해야 합니다. 마이그레이션이 이 범주에 해당하는지 확실하지 않다면 Project Manager와 협의하세요.

  • 자체 관리 및 Dedicated 고객이 업그레이드를 계획할 수 있도록 중요한 마이그레이션에 대해 업그레이드 참고 사항을 추가해야 합니다. 가이드라인을 따르세요.

  • 필요한 파일이 기본으로 생성되도록 제너레이터를 사용하여 배치 백그라운드 마이그레이션을 생성하세요.

배치 백그라운드 마이그레이션의 동작 방식#

배치 백그라운드 마이그레이션(BBM)은 perform 메서드를 정의하는 Gitlab::BackgroundMigration::BatchedMigrationJob의 서브클래스입니다. 첫 번째 단계로, 일반 마이그레이션이 BBM 클래스와 필요한 인수를 포함하는 batched_background_migrations 레코드를 생성합니다. 기본적으로 batched_background_migrations는 활성 상태이며, Sidekiq 워커가 실제 배치 마이그레이션을 실행하기 위해 이를 가져갑니다.

모든 마이그레이션 클래스는 네임스페이스 Gitlab::BackgroundMigration에 정의되어야 합니다. 파일은 lib/gitlab/background_migration/ 디렉터리에 배치하세요.

실행 메커니즘#

배치 백그라운드 마이그레이션은 큐에 추가된 순서대로 선택됩니다. 여러 마이그레이션이 활성 상태이고 같은 데이터베이스 테이블을 타깃으로 하지 않는 한, 병렬로 가져와 실행됩니다. 병렬 처리되는 기본 마이그레이션 수는 2이며, GitLab.com에서는 이 한도가 4로 설정되어 있습니다. 마이그레이션이 실행을 위해 선택되면 특정 배치에 대한 job이 생성됩니다. 각 job 실행 후, 마지막 20개 job의 성능을 기반으로 마이그레이션의 배치 크기가 증가하거나 감소할 수 있습니다.

@startuml hide empty description skinparam ConditionEndStyle hline left to right direction rectangle "Batched background migration queue" as migrations { rectangle "Migration N (active)" as migrationn rectangle "Migration 1 (completed)" as migration1 rectangle "Migration 2 (active)" as migration2 rectangle "Migration 3 (on hold)" as migration3 rectangle "Migration 4 (active)" as migration4 migration1 -[hidden]> migration2 migration2 -[hidden]> migration3 migration3 -[hidden]> migration4 migration4 -[hidden]> migrationn } rectangle "Execution Workers" as workers { rectangle "Execution Worker 1 (busy)" as worker1 rectangle "Execution Worker 2 (available)" as worker2 worker1 -[hidden]> worker2 } migration2 --> [Scheduling Worker] migration4 --> [Scheduling Worker] [Scheduling Worker] --> worker2 @enduml

워커가 사용 가능해지면 BBM이 러너에 의해 처리됩니다.

@startuml hide empty description start rectangle Runner { :Migration; if (Have reached batching bounds?) then (Yes) if (Have jobs to retry?) then (Yes) :Fetch the batched job; else (No) :Finish active migration; stop endif else (No) :Create a batched job; endif :Execute batched job; :Evaluate DB health; note right: Checks for table autovacuum, Patroni Apdex, Write-ahead logging if (Evaluation signs to stop?) then (Yes) :Put migration on hold; else (No) :Optimize migration; endif } @enduml

멱등성#

배치 백그라운드 마이그레이션은 Sidekiq 프로세스의 컨텍스트에서 실행됩니다. 일반적인 Sidekiq 규칙이 적용되며, 특히 job은 작고 멱등적이어야 한다는 규칙이 적용됩니다. 마이그레이션 job이 재시도될 경우 데이터 무결성이 보장되도록 하세요.

자세한 내용은 Sidekiq 모범 사례 가이드라인을 참조하세요.

마이그레이션 최적화#

각 job 실행 후, 마이그레이션을 최적화할 수 있는지 확인하는 검증이 이루어집니다. 최적화의 기본 메커니즘은 시간 효율성 개념을 기반으로 합니다. 마지막 N개 job의 시간 효율성의 지수 이동 평균을 계산하고 배치 백그라운드 마이그레이션의 배치 크기를 최적값으로 업데이트합니다.

그러나 이 메커니즘으로 인해 데이터베이스 마이그레이션 파이프라인을 사용할 때 마이그레이션의 총 실행 시간을 정확하게 추정하기 어렵습니다.

이 문제를 해결하는 방법은 이 이슈에서 논의 중입니다.

Job 재시도 메커니즘#

배치 백그라운드 마이그레이션 재시도 메커니즘은 실패 시 job이 다시 실행되도록 보장합니다. 다음 다이어그램은 재시도 메커니즘의 다양한 단계를 보여줍니다:

@startuml hide empty description note as N1 can_split?: the failure is due to a query timeout end note [] --> Running Running --> Failed note on link if number of retries Succeeded Failed --> Running note on link if number of retries > MAX_ATTEMPTS and can_split? == true then two jobs with smaller batch size will be created end note Failed --> [] Succeeded --> [*] @enduml

실패한 배치 백그라운드 마이그레이션#

다음 중 하나라도 해당하는 경우 전체 배치 백그라운드 마이그레이션이 failed로 표시됩니다 (/chatops gitlab run batched_background_migrations status MIGRATION_ID에서 마이그레이션이 failed로 표시됨):

배치 마이그레이션 제한#

배치 마이그레이션은 업데이트가 많고 데이터베이스가 성능 저하 상태일 때 이러한 마이그레이션의 부하로 인해 인시던트가 발생한 바 있어, 향후 인시던트를 완화하기 위한 제한 메커니즘이 존재합니다.

다음 데이터베이스 지표를 확인하여 마이그레이션을 제한합니다. 중지 신호를 받으면 마이그레이션이 설정된 시간(10분) 동안 일시 중지됩니다:

  • 임계값을 초과하는 WAL 큐 보류 중인 아카이브.

  • 마이그레이션이 작업 중인 테이블에서 활성 autovacuum 실행 (GitLab 18.0부터 기본적으로 활성화됨).

  • SLO 이하로 떨어지는 Patroni apdex SLI.

  • 임계값을 초과하는 WAL 속도.

데이터베이스 상태 점검 프레임워크를 더욱 강화하기 위해 더 많은 지표를 추가하는 작업이 진행 중입니다. 자세한 내용은 에픽 7594를 참조하세요.

테이블에서 autovacuum 지표를 비활성화/활성화하는 방법#

GitLab 18.0부터 이 상태 지표가 기본적으로 활성화됩니다. 비활성화하려면 rails 콘솔에서 다음 명령어를 실행하세요:

Feature.disable(:batched_migrations_health_status_autovacuum)

또는 다시 활성화하려면 rails 콘솔에서 다음 명령어를 실행하세요:

Feature.enable(:batched_migrations_health_status_autovacuum)

격리#

배치 백그라운드 마이그레이션은 격리되어야 하며 애플리케이션 코드를 사용할 수 없습니다 (예: ApplicationRecord 클래스를 제외한 app/models에 정의된 모델). 이러한 마이그레이션은 실행하는 데 오랜 시간이 걸릴 수 있으므로, 마이그레이션이 여전히 실행 중인 동안 새 버전이 배포될 수 있습니다.

마이그레이션된 데이터에 의존하기#

일반 마이그레이션이나 포스트 마이그레이션과 달리, 다음 릴리즈를 기다리는 것만으로는 데이터가 완전히 마이그레이션되었음을 보장하기에 충분하지 않습니다. 즉, BBM이 완료될 때까지 해당 데이터에 의존해서는 안 됩니다. 100%의 데이터 마이그레이션이 필요한 경우, ensure_batched_background_migration_is_finished 헬퍼를 사용하여 마이그레이션이 완료되고 데이터가 완전히 마이그레이션되었음을 보장할 수 있습니다. (예시 참조).

방법#

배치 백그라운드 마이그레이션 생성#

커스텀 제너레이터 batched_background_migration은 필요한 파일을 스캐폴딩하고 table_name, column_name, feature_category를 인수로 받습니다. column_name을 선택할 때 고유하게 반복할 수 있는 칼럼 타입을 사용하는지 확인하세요. 가급적이면 테이블의 기본 키를 사용하세요. 테이블은 여기에 정의된 칼럼을 기반으로 반복됩니다. 자세한 내용은 비고유 칼럼에서 배치 처리를 참조하세요.

사용법:

bundle exec rails g batched_background_migration my_batched_migration --table_name=<table-name> --column_name=<column-name> --feature_category=<feature-category>

이 명령어는 다음 파일들을 생성합니다:

  • db/post_migrate/20230214231008_queue_my_batched_migration.rb

  • spec/migrations/20230214231008_queue_my_batched_migration_spec.rb

  • lib/gitlab/background_migration/my_batched_migration.rb

  • spec/lib/gitlab/background_migration/my_batched_migration_spec.rb

커서 기반 반복 사용 (기본값)#

커서 기반 반복은 이제 배치 백그라운드 마이그레이션의 기본 권장 전략입니다. 레거시 기본 키 기반 방식에 비해 복합 기본 키 지원과 유지 관리 편의성을 제공합니다.

queue_batched_background_migration 헬퍼는 마이그레이션 job이 커서 전략을 사용하는지 자동으로 감지하고 그에 따라 마이그레이션을 구성합니다.

커서 전략 사용 방법#

마이그레이션 job 클래스에서 cursor DSL을 사용하여 커서 칼럼을 정의하세요:

module Gitlab
  module BackgroundMigration
    class MyBatchedMigration < BatchedMigrationJob
      # For single-column primary keys
      cursor :id

      operation_name :my_operation
      feature_category :database

      def perform
        each_sub_batch do |sub_batch|
          # Your migration logic here
        end
      end
    end
  end
end

복합 기본 키가 있는 테이블의 경우:

module Gitlab
  module BackgroundMigration
    class MyCompositePkMigration < BatchedMigrationJob
      cursor :deployment_id, :merge_request_id

      operation_name :backfill_project_id
      feature_category :continuous_delivery

      def perform
        each_sub_batch do |relation|
          # Your migration logic here
        end
      end
    end
  end
end

그런 다음 마이그레이션 파일에서 표준 헬퍼를 사용하세요:

queue_batched_background_migration(
  'MyBatchedMigration',
  :my_table,
  :id,
  job_interval: 2.minutes
)

헬퍼는 자동으로 다음을 수행합니다:

  • job이 커서 전략을 사용하는지 감지

  • 적절한 min_cursormax_cursor 값 계산

  • 커서 기반 반복을 사용하도록 마이그레이션 구성

레거시 기본 키 기반 반복#

레거시 기본 키 기반 반복 전략을 사용해야 하는 경우(새 마이그레이션에는 권장하지 않음), 마이그레이션 클래스에서 cursor 정의를 생략하세요. 마이그레이션이 이전 반복 방식으로 폴백됩니다.

배치 백그라운드 마이그레이션 큐잉#

배치 백그라운드 마이그레이션 큐잉은 포스트 배포 마이그레이션에서 수행해야 합니다. 다음 queue_batched_background_migration 예시를 사용하여 배치로 실행될 마이그레이션을 큐에 추가하세요. 클래스 이름과 인수를 마이그레이션의 값으로 교체하세요:

queue_batched_background_migration(
  JOB_CLASS_NAME,
  TABLE_NAME,
  JOB_ARGUMENTS
  )

이 헬퍼는 제공된 job 인수 수가 JOB_CLASS_NAME에 정의된 job 인수 수와 일치하지 않으면 오류를 발생시킵니다.

새로 생성된 데이터가 마이그레이션되거나 생성 시 이전 버전과 새 버전 모두에 저장되어야 합니다. 삭제의 경우 계단식 삭제가 있는 외래 키를 정의하여 처리할 수 있습니다.

배치 백그라운드 마이그레이션 완료#

배치 백그라운드 마이그레이션 완료는 ensure_batched_background_migration_is_finished를 호출하여 수행되지만, 마지막 필수 중단점 이전에 또는 그 시점에 마이그레이션이 추가된 경우에만 해당합니다. 이렇게 하면 GitLab Self-Managed 인스턴스의 원활한 업그레이드 프로세스가 보장됩니다.

가능한 한 빨리 모든 배치 백그라운드 마이그레이션을 완료하는 것이 중요합니다. 오래된 배치 백그라운드 마이그레이션을 방치하는 것은 테스트와 애플리케이션 동작에서 유지 관리해야 하는 기술 부채의 한 형태입니다.

완료되기 전에는 어떤 배치 백그라운드 마이그레이션에도 의존할 수 없습니다.

배치 백그라운드 마이그레이션은 다음 모든 조건이 충족된 후에 완료하는 것을 권장합니다:

ensure_batched_background_migration_is_finished 호출은 큐에 추가하는 데 사용된 마이그레이션과 정확히 일치해야 합니다. 다음 사항에 주의하세요:

  • job 인수: 정확히 일치해야 하며, 그렇지 않으면 큐에 추가된 마이그레이션을 찾지 못합니다

  • gitlab_schema: 정확히 일치해야 하며, 그렇지 않으면 큐에 추가된 마이그레이션을 찾지 못합니다. 그 사이에 테이블의 gitlab_schemagitlab_main에서 gitlab_main_org로 변경된 경우에도 배치 백그라운드 마이그레이션을 큐에 추가할 때 사용한 gitlab_main으로 완료해야 합니다.

배치 백그라운드 마이그레이션을 완료할 때는 해당 db/docs/batched_background_migrations 파일의 finalized_by도 업데이트해야 합니다. 값은 완료를 위해 추가한 마이그레이션의 타임스탬프/버전이어야 합니다.

실제 마이그레이션 코드에 대한 구체적인 내용은 아래 예시를 참조하세요.

필수 중단점 하나 이전에 마이그레이션이 완료되는 경우, 조기 완료 오류가 발생합니다. 필수 중단점 하나 이전에 마이그레이션을 완료해야 하는 경우, skip_early_finalization_validation: true 옵션을 사용하여 이 검사를 건너뛰세요.

배치 백그라운드 마이그레이션 코드 삭제#

배치 백그라운드 마이그레이션이 완료 및 확정되고 재큐잉되지 않았다면, lib/gitlab/background_migration/의 마이그레이션 코드와 관련 테스트는 완료 후 다음 필수 중단점 이후에 삭제할 수 있습니다.

예시 시나리오:

  • 17.3과 17.5가 필수 중단점입니다.

  • 17.1에서 배치 백그라운드 마이그레이션이 큐에 추가됩니다.

  • GitLab.com에서 완료된 경우, 17.4에서 마이그레이션이 완료될 수 있습니다.

  • 17.6에서 마이그레이션 관련 코드를 삭제할 수 있습니다.

배치 백그라운드 마이그레이션 코드를 삭제하는 두 가지 전략이 있습니다:

  • 마이그레이션 스쿼싱이 마이그레이션 관련 파일을 자동으로 삭제할 때까지 기다립니다.

  • 마이그레이션 관련 파일을 수동으로 삭제합니다.

마이그레이션 스쿼싱으로 배치 백그라운드 마이그레이션 정리#

GitLab에는 완료된 배치 백그라운드 마이그레이션을 삭제하는 마이그레이션 스쿼싱 프로세스가 있습니다. GitLab.com의 경우 이 스크립트는 모든 필수 중단점 이후에 실행됩니다. 배치 백그라운드 마이그레이션이 완료된 경우, 단순히 다음 필수 중단점을 기다릴 수 있습니다. 배치 백그라운드 마이그레이션과 관련된 모든 파일이 그때 삭제됩니다.

배치 백그라운드 마이그레이션 코드 수동 삭제#

경우에 따라 완료 후 마이그레이션 스쿼싱이 실행되기 전에 배치 백그라운드 마이그레이션 코드를 삭제하고 싶을 수 있습니다. 예를 들어, 배치 백그라운드 마이그레이션이 GitLab.com만을 타깃으로 했고 마이그레이션에서 참조하는 일부 코드를 제거하려는 경우입니다.

이 경우 다음 파일들을 수동으로 삭제할 수 있습니다:

  • lib/의 배치 백그라운드 마이그레이션 클래스 파일.

  • 배치 백그라운드 마이그레이션 클래스에 해당하는 spec/ 파일.

  • db/docs/batched_background_migrations/의 배치 백그라운드 마이그레이션 YAML 파일.

  • 배치 백그라운드 마이그레이션을 큐에 추가하거나 완료한 db/post_migrate/의 모든 마이그레이션 파일.

  • 큐잉 또는 완료 마이그레이션에 해당하는 spec/ 파일.

  • 삭제된 db/post_migrate/ 마이그레이션에 해당하는 schema_migrations/의 모든 파일.

배치 백그라운드 마이그레이션 재큐잉#

배치 백그라운드 마이그레이션을 다시 실행해야 하는 몇 가지 이유가 있을 수 있습니다:

  • 마이그레이션에 버그가 있는 경우 (예시).

  • 마이그레이션이 데이터를 정리했지만 애플리케이션 로직의 우회로 인해 데이터가 다시 비정규화된 경우 (예시).

  • 원래 마이그레이션의 배치 크기로 인해 마이그레이션이 실패하는 경우 (예시).

배치 백그라운드 마이그레이션을 재큐잉하려면:

  • 원래 마이그레이션 파일의 #up#down 메서드 내용을 no-op으로 만드세요. 그렇지 않으면 여러 패치 릴리즈를 한 번에 업그레이드하는 시스템에서 배치 백그라운드 마이그레이션이 생성되었다가 삭제된 후 다시 생성됩니다.

  • 배치 백그라운드 마이그레이션을 다시 실행하는 새 포스트 배포 마이그레이션을 추가하세요.

  • 새 포스트 배포 마이그레이션에서 #up 메서드 시작 부분에 delete_batched_background_migration 메서드를 사용하여 기존 배치 백그라운드 마이그레이션을 삭제하여 기존 실행이 정리되도록 하세요.

  • 원래 마이그레이션의 db/docs/batched_background_migration/*.yml 파일을 업데이트하여 재큐잉에 관한 정보를 포함하세요.

예시#

원래 마이그레이션:

# frozen_string_literal: true

class QueueResolveVulnerabilitiesForRemovedAnalyzers < Gitlab::Database::Migration[2.2]
  milestone '17.3'

  MIGRATION = "ResolveVulnerabilitiesForRemovedAnalyzers"

  def up
    # no-op because there was a bug in the original migration, which has been
    # fixed by
  end

  def down
    # no-op because there was a bug in the original migration, which has been
    # fixed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162527
  end
end

재큐잉된 마이그레이션:

# frozen_string_literal: true

class RequeueResolveVulnerabilitiesForRemovedAnalyzers < Gitlab::Database::Migration[2.2]
  milestone '17.4'

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  MIGRATION = "ResolveVulnerabilitiesForRemovedAnalyzers"
  BATCH_SIZE = 10_000
  SUB_BATCH_SIZE = 100

  def up
    # Clear previous background migration execution from QueueResolveVulnerabilitiesForRemovedAnalyzers
    delete_batched_background_migration(MIGRATION, :vulnerability_reads, :id, [])

    queue_batched_background_migration(
      MIGRATION,
      :vulnerability_reads,
      :id,
      batch_size: BATCH_SIZE,
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :vulnerability_reads, :id, [])
  end
end

배치 마이그레이션 딕셔너리:

milestonequeued_migration_version은 재큐잉된 마이그레이션의 것이어야 합니다 (이 예시에서는 RequeueResolveVulnerabilitiesForRemovedAnalyzers).

---
migration_job_name: ResolveVulnerabilitiesForRemovedAnalyzers
description: Resolves all detected vulnerabilities for removed analyzers.
feature_category: static_application_security_testing
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162691
milestone: '17.4'
queued_migration_version: 20240814085540
finalized_by: # version of the migration that finalized this BBM

배치 백그라운드 마이그레이션 중지 및 제거#

실행 중인 배치 백그라운드 마이그레이션을 중지하고 제거해야 할 수 있는 몇 가지 이유가 있습니다:

  • 제품 사용 사례가 변경되어 마이그레이션이 더 이상 관련이 없거나 필요하지 않은 경우.

  • 마이그레이션이 다른 로직의 다른 마이그레이션으로 대체되어야 하는 경우.

진행 중인 배치 백그라운드 마이그레이션을 중지하고 제거하려면:

  • 릴리즈 N에서 스케줄링 데이터베이스 마이그레이션의 #up#down 메서드 내용을 no-op으로 만드세요.
class BackfillNamespaceType < Gitlab::Database::Migration[2.1]
  # Reason why we don't need the BBM anymore. E.G: This BBM is no longer needed because it will be superseded by another BBM with different logic.
  def up; end

  def down; end
end
  • 릴리즈 N에서 기존 배치 마이그레이션을 삭제하는 일반 마이그레이션을 추가하세요. #up 메서드 시작 부분에 delete_batched_background_migration 메서드를 사용하여 기존 배치 백그라운드 마이그레이션을 삭제하여 기존 실행이 정리되도록 하세요.
class CleanupBackfillNamespaceType < Gitlab::Database::Migration[2.1]
  MIGRATION = "MyMigrationClass"

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    delete_batched_background_migration(MIGRATION, :vulnerabilities, :id, [])
  end

  def down; end
end
  • 릴리즈 N에서 마이그레이션 클래스 파일(lib/gitlab/background_migration/my_batched_migration.rb)과 해당 스펙도 삭제하세요.

위의 모든 단계는 단일 MR에서 구현할 수 있습니다.

Job 인수 사용#

BatchedMigrationJob은 job 클래스가 필요한 job 인수를 정의할 수 있도록 job_arguments 헬퍼 메서드를 제공합니다.

queue_batched_background_migration으로 스케줄된 배치 마이그레이션은 job 인수를 정의하는 데 반드시 헬퍼를 사용해야 합니다:

queue_batched_background_migration(
  'CopyColumnUsingBackgroundMigrationJob',
  TABLE_NAME,
  'name', 'name_convert_to_text'
)

정의된 job 인수 수가 마이그레이션 스케줄 시 제공된 job 인수 수와 일치하지 않으면 queue_batched_background_migration이 오류를 발생시킵니다.

이 예시에서 copy_fromname을 반환하고 copy_toname_convert_to_text를 반환합니다:

class CopyColumnUsingBackgroundMigrationJob < BatchedMigrationJob
  job_arguments :copy_from, :copy_to
  operation_name :update_all

  def perform
    from_column = connection.quote_column_name(copy_from)
    to_column = connection.quote_column_name(copy_to)

    assignment_clause = "#{to_column} = #{from_column}"

    each_sub_batch do |relation|
      relation.update_all(assignment_clause)
    end
  end
end

테이블의 하위 집합에 대해 마이그레이션 수행#

기본적으로 마이그레이션을 수행하는 백그라운드 job을 생성할 때 배치 백그라운드 마이그레이션은 지정된 테이블 전체를 반복합니다. 이 반복은 PrimaryKeyBatchingStrategy를 사용하여 수행됩니다. 테이블에 1000개의 레코드가 있고 배치 크기가 100이면 작업이 10개의 job으로 배치됩니다.

테이블의 하위 집합 마이그레이션은 scope_to 블록을 사용하거나 사용하지 않고 수행할 수 있습니다.

scope_to를 사용하여 선택 적용#

BBM은 scope_to 블록을 정의하는 옵션을 제공합니다. 각 배치의 최소 및 최대 범위를 결정하는 쿼리에 추가 한정자를 추가합니다.

기본적으로 배치 범위는 매우 효율적인 기본 키 인덱스를 사용하여 결정됩니다. 그러나 scope_to를 사용하면 쿼리가 주어진 조건과 일치하는 행만 고려해야 하므로 성능에 영향을 줄 수 있습니다.

범위 조건이 인덱싱되어 있고 배치 쿼리가 어떤 행도 필터링하지 않을 때만 사용해야 합니다.

적절한 인덱스의 강한 지표: 쿼리 계획에 추가 필터 없이 인덱스 전용 스캔이 있어야 합니다.

안전을 위해 Database/AvoidScopeTo cop을 사용하여 scope_to 사용을 방지합니다. 선택 쿼리가 성능적임을 확인한 후(적절한 인덱스 사용), cop을 비활성화하고 범위를 커버하는 인덱스 정의를 명시하세요:

module Gitlab
  module BackgroundMigration
    class ExpireOAuthTokens < ::Gitlab::BackgroundMigration::BatchedMigrationJob
      # rubocop:disable Database/AvoidScopeTo -- supporting index: index_oauth_access_tokens_on_id_where_expires_in_null ON oauth_access_tokens USING btree (id) WHERE (expires_in IS NULL)
      scope_to ->(relation) { relation.where(expires_in: nil) }
      operation_name :update_all

      def perform
        each_sub_batch do |sub_batch|
          sub_batch.update_all(expires_in: 2.hours)
        end
      end
      # rubocop:enable Database/AvoidScopeTo
    end
  end
end

scope_to 없이 선택 적용#

이 방식에서는 선택이 각 서브 배치로 푸시다운됩니다. 테이블 전체를 반복하는 기본 BBM 방식을 따릅니다. 배치가 기본 키에만 의존하므로 추가 인덱스가 필요하지 않습니다.

module Gitlab
  module BackgroundMigration
    class ExpireOAuthTokens < ::Gitlab::BackgroundMigration::BatchedMigrationJob
      operation_name :update_all

      def perform
        each_sub_batch do |sub_batch|
          sub_batch
            .where(expires_in: nil)
            .update_all(expires_in: 2.hours)
        end
      end
    end
  end
end

vacuum 확인을 위한 테이블 구성#

기본적으로 배치 백그라운드 마이그레이션은 반복 중인 테이블 (queue_batched_background_migration에 지정된 테이블)에서 autovacuum이 실행될 때 일시 중지됩니다. 그러나 백그라운드 마이그레이션이 반복하는 테이블에 항상 쓰는 것은 아닙니다. 이 경우 반복 테이블의 vacuum 활동으로 인해 마이그레이션을 일시 중지하는 것은 의미가 없습니다.

tables_to_check_for_vacuum 클래스 메서드를 사용하여 vacuum 활동을 확인해야 하는 테이블을 명시적으로 지정하세요. 지정된 테이블 중 하나에서 vacuum이 감지되면 마이그레이션이 일시 중지됩니다.

이 기능을 사용하는 시기#

다음의 경우에 tables_to_check_for_vacuum을 사용하세요:

  • 마이그레이션이 하나의 테이블을 반복하지만 다른 테이블에 쓰는 경우.

  • 수정되지 않는 테이블의 vacuum으로 인한 불필요한 일시 중지를 방지하려는 경우.

  • 여러 특정 테이블의 vacuum 활동을 모니터링해야 하는 경우.

예시#

merge_request_diff_files를 반복하지만 파티션 테이블 merge_request_diff_files_99208b8fac에 쓰는 마이그레이션을 고려하세요:

module Gitlab
  module BackgroundMigration
    class BackfillMergeRequestFileDiffsPartitionedTable < BackfillPartitionedTable
      operation_name :backfill
      feature_category :source_code_management

      cursor :merge_request_diff_id, :relative_order

      # Specify the actual table being written to
      tables_to_check_for_vacuum :merge_request_diff_files_99208b8fac

      def perform
        # Migration logic that writes to merge_request_diff_files_99208b8fac
        # but iterates over merge_request_diff_files
      end
    end
  end
end

이 예시에서:

  • 마이그레이션은 merge_request_diff_files를 반복합니다(queue_batched_background_migration에 지정됨).

  • 마이그레이션은 merge_request_diff_files_99208b8fac에 씁니다.

  • tables_to_check_for_vacuum :merge_request_diff_files_99208b8fac를 사용하면 반복 테이블이 아닌 파티션 테이블에서 vacuum이 실행될 때만 마이그레이션이 일시 중지됩니다.

여러 테이블 지정#

모니터링할 여러 테이블을 지정할 수 있습니다:

tables_to_check_for_vacuum :table_one, :table_two, :table_three

지정된 테이블 중 하나에서 vacuum이 실행 중이면 마이그레이션이 일시 중지됩니다.

기본 동작#

tables_to_check_for_vacuum이 지정되지 않은 경우, 마이그레이션은 기본적으로 반복 중인 테이블(queue_batched_background_migration에 지정된 테이블)의 vacuum 활동을 확인합니다.

여러 데이터베이스의 데이터 접근#

백그라운드 마이그레이션은 일반 마이그레이션과 달리 여러 데이터베이스에 접근할 수 있으며 이를 통해 데이터베이스 간에 효율적으로 데이터를 접근하고 업데이트하는 데 사용할 수 있습니다. 사용할 데이터베이스를 올바르게 지정하려면 마이그레이션 코드 내에 ActiveRecord 모델을 인라인으로 생성하는 것이 좋습니다. 이러한 모델은 테이블이 위치한 데이터베이스에 따라 올바른 ApplicationRecord를 사용해야 합니다. 따라서 ActiveRecord::Base 사용은 접근할 데이터베이스를 명시적으로 설명하지 않으므로 허용되지 않습니다.

# good
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ::ApplicationRecord
    self.table_name = 'projects'
  end

  class Build < ::Ci::ApplicationRecord
    self.table_name = 'ci_builds'
  end
end

# bad
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ActiveRecord::Base
    self.table_name = 'projects'
  end

  class Build < ActiveRecord::Base
    self.table_name = 'ci_builds'
  end
end

마찬가지로 ActiveRecord::Base.connection 사용은 허용되지 않으며 가급적 모델 connection을 사용하도록 교체해야 합니다.

# good
Project.connection.execute("SELECT * FROM projects")

# acceptable
ApplicationRecord.connection.execute("SELECT * FROM projects")

# bad
ActiveRecord::Base.connection.execute("SELECT * FROM projects")

비고유 칼럼에서 배치 처리#

기본 배치 전략은 기본 키 칼럼을 효율적으로 반복하는 방법을 제공합니다. 그러나 값이 고유하지 않은 칼럼을 반복해야 하는 경우 다른 배치 전략을 사용해야 합니다.

LooseIndexScanBatchingStrategy 배치 전략은 EachBatch의 특별 버전을 사용하여 고유 칼럼 값에 대해 효율적이고 안정적인 반복을 제공합니다.

이 예시는 issues.project_id 칼럼이 배치 칼럼으로 사용되는 배치 백그라운드 마이그레이션을 보여줍니다.

데이터베이스 포스트 마이그레이션:

class ProjectsWithIssuesMigration < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BatchProjectsWithIssues'
  BATCH_SIZE = 5000
  SUB_BATCH_SIZE = 500
  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  disable_ddl_transaction!
  def up
    queue_batched_background_migration(
      MIGRATION,
      :issues,
      :project_id,
      batch_size: BATCH_SIZE,
      batch_class_name: 'LooseIndexScanBatchingStrategy', # Override the default batching strategy
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :issues, :project_id, [])
  end
end

백그라운드 마이그레이션 클래스 구현:

module Gitlab
  module BackgroundMigration
    class BatchProjectsWithIssues < Gitlab::BackgroundMigration::BatchedMigrationJob
      include Gitlab::Database::DynamicModelHelpers

      operation_name :backfill_issues

      def perform
        distinct_each_batch do |batch|
          project_ids = batch.pluck(batch_column)
          # do something with the distinct project_ids
        end
      end
    end
  end
end

scope_to로 정의된 추가 필터LooseIndexScanBatchingStrategydistinct_each_batch에서 무시됩니다.

파티션 테이블#

파티션 테이블 작업 시 성능을 향상시키기 위해 마이그레이션을 병렬화할 수 있습니다. 여러 마이그레이션이 동시에 실행될 수 있으며(GitLab.com에서 최대 4개), 다른 파티션이나 파티션 범위를 병렬로 처리할 수 있습니다.

이 섹션에서 설명하는 패턴은 지금까지 GitLab.com에서만 사용되었으며, 특정 유형의 파티션 테이블(수동으로 관리되는 CI 슬라이딩 리스트 파티션)에서만 사용되었습니다. 다음과 같은 이유로 자체 관리 인스턴스에는 권장하지 않습니다:

  • 큐에 추가된 마이그레이션 세트가 데이터(파티션의 수와 ID)에 따라 다르므로 자체 관리 인스턴스마다 다른 마이그레이션 세트가 표시됩니다. 이는 이미 백그라운드 마이그레이션에 대한 좌절감을 느끼는 자체 관리 관리자에게 혼란을 줄 수 있습니다.

  • 뷰 기반 병렬화는 프로덕션 데이터를 기반으로 ID 범위를 미리 계산해야 하므로 자체 관리 배포에서는 일반적으로 실행하기 어렵습니다.

이러한 패턴을 사용하기 전에 데이터베이스 팀과 상의하여 해당 사용 사례에 트레이드오프가 허용 가능한지 확인하세요.

패턴 1: 파티션별 병렬화#

파티션마다 하나의 BBM을 큐에 추가하여 병렬로 실행되도록 합니다.

사용 시기: 테이블에 여러 파티션이 있고 각 파티션을 독립적으로 마이그레이션할 수 있는 경우. GitLab.com에서만 또는 안정적이고 잘 알려진 파티션 세트가 있는 테이블에서만 이 패턴을 사용하세요.

마이그레이션 큐잉 예시:

class QueueMyMigration < Gitlab::Database::Migration[2.3]
  MIGRATION = 'MyBatchedMigration'
  TABLE_NAME = :my_partitioned_table

  def up
    Gitlab::Database::PostgresPartitionedTable.each_partition(TABLE_NAME) do |partition|
      next if empty_partition?(partition)

      queue_batched_background_migration(MIGRATION, partition.identifier, :id)
    end
  end

  def down
    Gitlab::Database::PostgresPartitionedTable.each_partition(TABLE_NAME) do |partition|
      delete_batched_background_migration(MIGRATION, partition.identifier, :id, [])
    end
  end

  private

  def empty_partition?(partition)
    !connection.select_value("SELECT true FROM #{partition.identifier} LIMIT 1")
  end

  # Workaround to allow a single migration to enqueue multiple background migrations
  def assign_attributes_safely(migration, max_batch_size, batch_table_name, gitlab_schema, _queued_migration_version)
    super(migration, max_batch_size, batch_table_name, gitlab_schema, nil)
  end
end

완료: 파티션별 마이그레이션 완료 예시는 MR !223822를 참조하세요.

주요 고려사항:

  • 불필요한 마이그레이션을 방지하기 위해 빈 파티션은 건너뜁니다

  • 큐잉 후 생성된 새 파티션은 자동으로 포함되지 않습니다

  • 각 파티션의 마이그레이션은 독립적으로 실행되며 별도로 모니터링할 수 있습니다

  • 큐에 추가된 마이그레이션 세트는 큐잉 시점에 존재하는 파티션에 따라 다르므로 인스턴스마다 다를 수 있습니다. 이는 자체 관리 릴리즈에 적합하지 않습니다.

  • 파티션이 분리되어 삭제되는 경우(예: 매일 또는 매월 파티션이 만료됨) 마이그레이션이 시작되거나 완료되기 전에 마이그레이션이 실패하거나 완료되지 않습니다. 마이그레이션 전체 기간 동안 존재가 보장된 파티션에만 이 패턴을 사용하세요.

패턴 2: 뷰 기반 병렬화#

파티션을 여러 범위로 분할하는 데이터베이스 뷰를 생성한 다음 각 뷰에 대해 별도의 BBM을 큐에 추가합니다. 이 패턴은 단일 파티션이 너무 크거나 "테이블당 하나의 마이그레이션" 프레임워크 제한을 우회해야 할 때 유용합니다.

사용 시기:

  • 단일 파티션을 마이그레이션하는 데 너무 많은 개월/년이 필요한 경우

  • "활성 마이그레이션은 테이블당 하나" 프레임워크 제한을 우회해야 하는 경우

  • GitLab.com에서 마이그레이션이 실행되어 프로덕션 데이터에서 미리 뷰 경계를 계산할 수 있는 경우

이 패턴은 자체 관리 인스턴스에는 적합하지 않습니다. 뷰 경계는 실제 데이터 분포에서 미리 계산해야 하는데, 이는 자체 관리 설치 전반에 걸쳐 일반적으로 수행하기 어렵습니다.

뷰 생성 예시:

파티션을 분할하기 위해 ID 범위가 있는 뷰를 생성합니다. 미리 뷰 경계를 계산하여 특정 행 위치의 실제 ID 값을 찾습니다. 올바른 ID 범위를 찾는 것은 비용이 많이 들고 마이그레이션 중에 계산하면 타임아웃이 발생할 수 있습니다.

class CreatePartitionViews < Gitlab::Database::Migration[2.3]
  # Pre-calculate view boundaries using COUNT and OFFSET queries
  # For 4 views, divide total row count by 4 and find the ID at each boundary:
  # SELECT id FROM p_ci_builds WHERE partition_id = 100 ORDER BY id LIMIT 1 OFFSET 0;
  # SELECT id FROM p_ci_builds WHERE partition_id = 100 ORDER BY id LIMIT 1 OFFSET <total_rows/4>;
  # SELECT id FROM p_ci_builds WHERE partition_id = 100 ORDER BY id LIMIT 1 OFFSET <total_rows/2>;
  # etc.
  VIEW_BOUNDARIES = [1, 1500384395, 2951960143, 4355055910, 12168556334].freeze
  VIEW_PREFIX = 'gitlab_partitions_dynamic.ci_builds_views_100'

  def up
    view_ranges.each_with_index do |range, index|
      create_view(index + 1, range)
    end
  end

  def down
    view_ranges.each_with_index do |_, index|
      execute("DROP VIEW IF EXISTS #{VIEW_PREFIX}_#{index + 1};")
    end
  end

  private

  def view_ranges
    VIEW_BOUNDARIES.each_cons(2).map { |lower, upper| (lower..upper) }
  end

  def create_view(view_number, range)
    execute(<<~SQL.squish)
      CREATE OR REPLACE VIEW #{VIEW_PREFIX}_#{view_number} AS
      SELECT id, partition_id
      FROM p_ci_builds
      WHERE id >= #{range.min} AND id < #{range.max} AND partition_id = 100
    SQL
  end
end

마이그레이션 큐잉 예시:

class SplitMigration < Gitlab::Database::Migration[2.3]
  MIGRATION = 'MyBatchedMigration'
  VIEW_PREFIX = 'gitlab_partitions_dynamic.ci_builds_views_100'
  VIEW_BOUNDARIES = [1, 1500384395, 2951960143, 4355055910, 12168556334].freeze
  TOTAL_TUPLE_COUNT = 4774979600

  def up
    VIEW_BOUNDARIES.each_cons(2).map.with_index(1) do |range, view_number|
      queue_batched_background_migration(
        MIGRATION,
        "#{VIEW_PREFIX}_#{view_number}",
        :id,
        batch_min_value: range.first,
        batch_max_value: range.last
      )
    end

    # Update tuple count statistics for accurate progress reporting
    Gitlab::Database::BackgroundMigration::BatchedMigration
      .where(job_class_name: MIGRATION)
      .update_all(total_tuple_count: TOTAL_TUPLE_COUNT / (VIEW_BOUNDARIES.size - 1))
  end

  def down
    1.upto(VIEW_BOUNDARIES.size - 1) do |view_number|
      delete_batched_background_migration(MIGRATION, "#{VIEW_PREFIX}_#{view_number}", :id, [])
    end
  end

  private

  def assign_attributes_safely(migration, max_batch_size, batch_table_name, gitlab_schema, _queued_migration_version)
    super(migration, max_batch_size, batch_table_name, gitlab_schema, nil)
  end
end

이미 실행 중인 기존 마이그레이션을 처리하는 완전한 예시는 MR !221430을 참조하세요.

트레이드오프:

  • 뷰는 autovacuum 제한을 우회하므로(테이블 비대화 유발 가능) WAL 제한은 여전히 적용됩니다

  • 필요한 경우에만 사용하세요(예: 마이그레이션이 7개월 이상 걸리는 경우)

  • 사용 가능한 모든 워커를 활용하여 마이그레이션 시간을 개월에서 주 단위로 단축합니다

  • 동일한 기간 동안 같은 데이터베이스에 큐에 추가된 다른 마이그레이션이 지연될 수 있도록 하나의 테이블에 대한 워커 슬롯을 포화시킵니다

  • 프로덕션 데이터에서 미리 계산된 뷰 경계가 필요하여 자체 관리 배포에는 실행하기 어렵습니다

실제 사례: MR !221430 - MoveCiBuildsMetadata

  • 원래: 파티션 100에서 1개의 마이그레이션 (47억 행, ~7개월)

  • 이후: 뷰에서 4개의 마이그레이션 (병렬화, 총 ~2개월)

배치 백그라운드 마이그레이션의 전체 시간 추정 계산#

BBM이 완료되는 데 걸리는 시간을 추정하는 것이 가능합니다. GitLab은 이미 db:gitlabcom-database-testing 파이프라인을 통해 추정치를 제공합니다. 이 추정치는 테스트 환경에서 프로덕션 데이터를 샘플링하여 구축되며 마이그레이션이 걸릴 수 있는 최대 시간을 나타내고, 실제 마이그레이션 시간을 반드시 나타내지는 않습니다. 특정 시나리오에서 db:gitlabcom-database-testing 파이프라인이 제공하는 추정치만으로는 마이그레이션 중인 레코드와 관련된 모든 특수성을 계산하기에 충분하지 않아 추가 계산이 필요할 수 있습니다. 이 경우 interval * number of records / max batch size 공식을 사용하여 마이그레이션이 걸리는 시간의 대략적인 추정치를 결정할 수 있습니다. 여기서 intervalmax batch size는 job에 정의된 옵션을 참조하며, total tuple count는 마이그레이션할 레코드 수입니다.

추정치는 마이그레이션 최적화 메커니즘에 의해 영향을 받을 수 있습니다.

배치 백그라운드 마이그레이션 정리#

나머지 백그라운드 마이그레이션 정리는 메이저 또는 마이너 릴리즈에서만 수행해야 합니다. 패치 릴리즈에서는 수행해서는 안 됩니다.

백그라운드 마이그레이션은 시간이 오래 걸릴 수 있으므로 큐에 추가한 후 즉시 정리할 수 없습니다. 예를 들어, job이 실패할 것이므로 마이그레이션 프로세스에 사용된 칼럼을 삭제할 수 없습니다. 남은 job을 완료하기 위해 향후 릴리즈에서 별도의 포스트 배포 마이그레이션을 추가해야 합니다. (예: 칼럼 제거)

칼럼 foo(큰 JSON blob 포함)에서 칼럼 bar(문자열 포함)로 데이터를 마이그레이션하려면:

  • 릴리즈 A:

주어진 ID를 가진 행에 대해 마이그레이션을 수행하는 마이그레이션 클래스를 생성합니다.

  • 다음 기술 중 하나를 사용하여 새 행을 업데이트합니다:

애플리케이션 로직이 필요 없는 복사 작업에 대한 새 트리거를 생성합니다.

  • 레코드가 생성되거나 업데이트될 때 모델/서비스에서 이 작업을 처리합니다.

  • 레코드를 업데이트하는 새 커스텀 백그라운드 job을 생성합니다.

  • 포스트 배포 마이그레이션에서 모든 기존 행에 대한 배치 백그라운드 마이그레이션을 큐에 추가합니다.

  • 릴리즈 B:

배치 백그라운드 마이그레이션이 완료되었는지 확인하는 포스트 배포 마이그레이션을 추가합니다.

  • 애플리케이션이 새 칼럼을 사용하기 시작하고 새 레코드 업데이트를 중지하도록 코드를 배포합니다.

  • 이전 칼럼을 제거합니다.

이전 버전의 GitLab에서 프로젝트를 가져올 때 데이터가 새 형식이어야 하는 경우 가져오기/내보내기 버전을 업그레이드해야 할 수 있습니다.

배치 백그라운드 마이그레이션을 지원하기 위한 인덱스 추가#

배치 백그라운드 마이그레이션을 지원하기 위해 새 인덱스나 임시 인덱스를 추가해야 하는 경우가 있습니다. 이를 위해 백그라운드 마이그레이션을 큐에 추가하는 포스트 배포 마이그레이션보다 먼저 포스트 배포 마이그레이션에서 인덱스를 생성하세요.

인덱스가 생성 후 바로 사용될 수 있도록 하는 특별한 주의가 필요한 경우에 대한 추가 정보는 데이터베이스 인덱스 추가 문서를 참조하세요.

데이터베이스 테스팅 파이프라인에서 특정 배치 실행#

데이터베이스 메인테이너만 데이터베이스 테스팅 파이프라인 아티팩트를 볼 수 있습니다. 이 방법을 사용해야 하는 경우 도움을 요청하세요.

GitLab.com의 특정 배치에서 배치 백그라운드 마이그레이션이 실패했고 어떤 쿼리가 실패했는지, 그 이유가 무엇인지 파악하고 싶다고 가정해 봅시다. 현재는 쿼리 정보(특히 쿼리 파라미터)를 가져오는 좋은 방법이 없으며 더 많은 로깅으로 전체 마이그레이션을 재실행하는 것은 오랜 시간이 걸리는 프로세스입니다.

다행히도 데이터베이스 마이그레이션 파이프라인을 활용하여 추가 로깅 및/또는 수정으로 특정 배치를 재실행하여 문제가 해결되는지 확인할 수 있습니다.

예시는 Draft: Test PG::CardinalityViolation fix를 참조하되 반드시 전체 섹션을 읽으세요.

이를 위해 다음이 필요합니다:

배치 start_id와 end_id 찾기#

Kibana에서 찾을 수 있어야 합니다.

일반 마이그레이션 생성#

일반 마이그레이션의 up 블록에서 배치를 스케줄합니다:

def up
  instance = Gitlab::BackgroundMigration::YourBackgroundMigrationClass.new(
      start_id: <batch start_id>,
      end_id: <batch end_id>,
      batch_table: <table name>,
      batch_column: <batching column>,
      sub_batch_size: <sub batch size>,
      pause_ms: <milliseconds between batches>,
      job_arguments: <job arguments if any>,
      connection: connection
    )

    instance.perform
end

def down
  # no-op
end

마이그레이션 헬퍼에 대한 임시 해결책 적용 (선택사항)#

배치 백그라운드 마이그레이션이 restrict_gitlab_migration 헬퍼로 지정한 것과 다른 스키마의 테이블을 건드리는 경우 (예: 스케줄링 마이그레이션에 restrict_gitlab_migration gitlab_schema: :gitlab_main_org가 있지만 백그라운드 job이 :gitlab_ci 스키마의 테이블을 사용하는 경우) 마이그레이션이 실패합니다. 이를 방지하려면 데이터베이스 헬퍼를 몽키 패치하여 테스팅 파이프라인 job이 실패하지 않도록 해야 합니다:

diff --git a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
index b8d1d21a0d2d2a23d9e8c8a0a17db98ed1ed40b7..912e20659a6919f771045178c66828563cb5a4a1 100644
--- a/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
+++ b/lib/gitlab/database/migration_helpers/restrict_gitlab_schema.rb
@@ -55,7 +55,7 @@ def unmatched_schemas
         end

         def allowed_schemas_for_connection
-          Gitlab::Database.gitlab_schemas_for_connection(connection)
+          Gitlab::Database.gitlab_schemas_for_connection(connection) << :gitlab_ci
         end
       end
     end

diff --git a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
index 4ae3622479f0800c0553959e132143ec9051898e..d556ec7f55adae9d46a56665ce02de782cb09f2d 100644
--- a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
+++ b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
@@ -79,7 +79,7 @@ def restrict_to_dml_only(parsed)
             tables = self.dml_tables(parsed)
             schemas = self.dml_schemas(tables)

-            if (schemas - self.allowed_gitlab_schemas).any?
+            if (schemas - (self.allowed_gitlab_schemas << :gitlab_ci)).any?
               raise DMLAccessDeniedError, \
                 "Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
                 "which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'. " \

데이터베이스 마이그레이션 파이프라인 시작#

변경 사항으로 Draft 머지 리퀘스트를 생성하고 수동 db:gitlabcom-database-testing job을 트리거합니다.

의존성 설정#

일부 경우에 마이그레이션은 이전에 큐에 추가된 BBM의 완료에 의존합니다. BBM이 여전히 실행 중인 경우 의존 마이그레이션이 실패합니다. 예를 들어: 대규모 테이블에 고유 인덱스를 도입하는 것은 중복 레코드를 처리하기 위해 이전에 큐에 추가된 BBM에 의존할 수 있습니다.

다음 프로세스는 마이그레이션 작성 시 의존성을 더 명확하게 하도록 구성되었습니다.

  • BBM을 큐에 추가한 마이그레이션의 버전이 batched_background_migrations 테이블과 BBM 딕셔너리 파일에 저장됩니다.

  • DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS 상수가 각 마이그레이션 파일에 추가됩니다(기본으로 주석 처리됨). 의존성을 설정하려면 의존하는 BBM의 queued_migration_version을 추가하세요. 그렇지 않으면 주석 줄을 제거하세요.

  • Migration::UnfinishedDependencies cop은 의존하는 BBM이 아직 완료되지 않은 경우 불평합니다. BBM 딕셔너리에서 finalized_by 키를 조회하여 완료 여부를 결정합니다.

예시:

# db/post_migrate/20231113120650_queue_backfill_routes_namespace_id.rb
class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BackfillRouteNamespaceId'

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org
  ...
  ...

  def up
    queue_batched_background_migration(
      MIGRATION,
      ...
    )
  end
end
# This depends on the finalization of QueueBackfillRoutesNamespaceId BBM
class AddNotNullToRoutesNamespaceId < Gitlab::Database::Migration[2.1]
  DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS = ["20231113120650"]

  def up
    add_not_null_constraint :routes, :namespace_id
  end

  def down
    remove_not_null_constraint :routes, :namespace_id
  end
end

고객을 위한 업그레이드 참고 사항 작성#

중요한 배치 백그라운드 마이그레이션의 경우, 자체 관리 및 Dedicated 고객이 업그레이드를 계획할 수 있도록 업그레이드 참고 사항을 추가해야 합니다. 이 참고 사항은 관련 버전의 업그레이드 문서에 추가해야 합니다 (예: GitLab 18 변경 사항).

잘 문서화된 업그레이드 참고 사항의 예시는 MR 214376을 참조하세요. 이 문서는 CI 빌드 메타데이터 마이그레이션을 기록합니다.

업그레이드 참고 사항을 추가하는 시기#

마이그레이션에 다음 중 하나라도 해당하는 경우 업그레이드 참고 사항을 추가하세요:

  • 마이그레이션이 완료하는 데 상당한 시간이 걸릴 수 있는 대규모 테이블에서 작동하는 경우.

  • 마이그레이션이 고객이 마이그레이션 범위를 제어할 수 있는 구성 옵션을 제공하는 경우.

  • 마이그레이션이 다른 마이그레이션이나 기능에 의존하는 경우.

업그레이드 참고 사항에 포함할 내용#

업그레이드 참고 사항에는 고객이 마이그레이션을 이해하고 준비할 수 있도록 다음 정보가 포함되어야 합니다:

  • 마이그레이션의 목적과 이점을 설명하여 고객이 그 영향을 이해할 수 있게 합니다.

  • 마이그레이션이 고객의 데이터에 무엇을 하는지, 어떤 테이블을 반복하는지 설명합니다.

  • 타임라인과 완료 일정을 문서화합니다. 완료 일정이 아직 알려지지 않은 경우 이슈로 링크합니다. 알려진 경우(향후 날짜에도) 실제 완료를 포함하는 릴리즈로 기존 업그레이드 참고 사항을 업데이트합니다.

  • 업그레이드 전 준비 단계를 설명합니다. 해당하는 경우 모범 사례와 적용할 설정을 권장합니다.

  • 마이그레이션 기간을 추정하는 도구를 제공합니다(SQL 쿼리 또는 Rails 콘솔 명령어).

  • 가능한 경우 마이그레이션 범위를 줄이는 제어 방법을 문서화합니다. 어떤 데이터가 영향을 받거나 받지 않는지 명확하게 알 수 있도록 최종 사용자 관점에서 제어 방법을 설명합니다.

관리#

BBM 관리는 GitLab 팀원에게만 제한된 chatops 통합을 통해 이루어집니다.

배치 백그라운드 마이그레이션 목록 조회#

시스템의 배치 백그라운드 마이그레이션 목록을 조회하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations list

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값).

  • ci: CI 데이터베이스를 사용합니다.

  • sec: 보안 데이터베이스를 사용합니다.

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

  • job 클래스로 필터

--job-class-name JOB_CLASS_NAME: 지정된 job 클래스의 job만 나열합니다.

  • 이것은 백그라운드 마이그레이션의 YAML 정의에서 migration_job_name입니다.

출력 예시:

[

](/19.1/development/database/img/list_v15_4.png)

ChatOps는 created_at (DESC) 기준으로 정렬된 20개의 배치 백그라운드 마이그레이션을 반환합니다.

배치 백그라운드 마이그레이션의 진행률과 상태 모니터링#

특정 배치 백그라운드 마이그레이션의 상태와 진행률을 확인하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations status MIGRATION_ID

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값)

  • ci: CI 데이터베이스를 사용합니다

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

출력 예시:

[

](/19.1/development/database/img/status_v15_4.png)

Progress는 완료된 백그라운드 마이그레이션의 백분율을 나타냅니다.

마이그레이션이 커서를 사용하는 경우 진행률 백분율이 올바르게 보고되지 않을 수 있습니다.

배치 백그라운드 마이그레이션 상태 정의:

  • Active: 다음 중 하나:

러너가 선택할 준비가 됨.

  • 배치 job 실행 중.

  • Finalizing: 배치 job 실행 중.

  • Failed: 배치 백그라운드 마이그레이션 실패.

  • Finished: 모든 job이 성공적으로 실행되어 배치 백그라운드 마이그레이션이 완료됨.

  • Paused: 러너에게 보이지 않음.

  • Finalized: 배치 마이그레이션이 ensure_batched_background_migration_is_finished로 검증되어 완료됨.

배치 백그라운드 마이그레이션 일시 중지#

배치 백그라운드 마이그레이션을 일시 중지하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations pause MIGRATION_ID

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값).

  • ci: CI 데이터베이스를 사용합니다.

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

출력 예시:

[

](/19.1/development/database/img/pause_v15_4.png)

active 상태의 배치 백그라운드 마이그레이션만 일시 중지할 수 있습니다.

배치 백그라운드 마이그레이션 재개#

배치 백그라운드 마이그레이션을 재개하려면 다음 명령어를 실행하세요:

/chatops gitlab run batched_background_migrations resume MIGRATION_ID

이 명령어는 다음 옵션을 지원합니다:

  • 데이터베이스 선택:

--database DATABASE_NAME: 지정된 데이터베이스에 연결합니다:

main: 메인 데이터베이스를 사용합니다(기본값).

  • ci: CI 데이터베이스를 사용합니다.

  • 환경 선택:

--dev: dev 환경을 사용합니다.

  • --staging: staging 환경을 사용합니다.

  • --staging_ref: staging_ref 환경을 사용합니다.

  • --production: production 환경을 사용합니다(기본값).

출력 예시:

[

](/19.1/development/database/img/resume_v15_4.png)

active 상태의 배치 백그라운드 마이그레이션만 재개할 수 있습니다.

백그라운드 마이그레이션 활성화 또는 비활성화#

극히 제한된 상황에서 GitLab 관리자는 기능 플래그를 비활성화할 수 있습니다:

  • execute_batched_migrations_on_schedule

이 플래그는 기본적으로 활성화되어 있습니다. 데이터베이스 호스트 유지 관리와 같은 특별한 상황에서 데이터베이스 작업을 제한하기 위한 최후의 수단으로만 비활성화하세요.

이 플래그를 비활성화할 때는 그 결과를 충분히 이해한 후에만 하세요. execute_batched_migrations_on_schedule 기능 플래그를 비활성화하면 GitLab 업그레이드가 실패하고 데이터 손실이 발생할 수 있습니다.

EE 전용 기능을 위한 배치 백그라운드 마이그레이션#

EE 전용 기능의 모든 백그라운드 마이그레이션 클래스는 GitLab FOSS에 있어야 합니다. 이를 위해 GitLab FOSS에 빈 클래스를 생성하고 Enterprise Edition 기능 구현 가이드라인에 설명된 대로 GitLab EE에서 이를 확장하세요.

job 인수를 사용하는 EE 전용 기능의 백그라운드 마이그레이션 클래스는 GitLab FOSS 클래스에서 정의해야 합니다. GitLab FOSS 컨텍스트에서 마이그레이션이 스케줄될 때 job 인수 검증이 실패하지 않도록 정의가 필요합니다.

제너레이터를 사용하여 새 배치 백그라운드 마이그레이션을 생성할 때 --ee-only 플래그를 전달하면 EE 전용 마이그레이션 스캐폴드를 생성할 수 있습니다.

디버그#

실패 오류 로그 보기#

두 가지 방법으로 실패를 확인할 수 있습니다:

GitLab 로그 통해:

배치 백그라운드 마이그레이션 실행 후 job이 실패하면 Kibana에서 로그를 봅니다. 프로덕션 Sidekiq 로그를 보고 다음으로 필터링합니다:

json.new_state: failed

  • json.job_class_name:

  • json.job_arguments:

json.exception_classjson.exception_message 값을 검토하여 job이 실패한 이유를 이해합니다.

재시도 메커니즘을 기억하세요. 실패가 있다고 해서 job이 실패한 것은 아닙니다. 항상 job의 마지막 상태를 확인하세요.

데이터베이스 통해:

배치 백그라운드 마이그레이션 CLASS_NAME을 가져옵니다.

PostgreSQL 콘솔에서 다음 쿼리를 실행합니다:

 SELECT migration.id, migration.job_class_name, transition_logs.exception_class, transition_logs.exception_message
 FROM batched_background_migrations as migration
 INNER JOIN batched_background_migration_jobs as jobs
 ON jobs.batched_background_migration_id = migration.id
 INNER JOIN batched_background_migration_job_transition_logs as transition_logs
 ON transition_logs.batched_background_migration_job_id = jobs.id
 WHERE transition_logs.next_status = '2' AND migration.job_class_name = "CLASS_NAME";

테스팅#

다음에 대한 테스트 작성이 필요합니다:

  • 배치 백그라운드 마이그레이션 큐잉 마이그레이션.

  • 배치 백그라운드 마이그레이션 자체.

  • 정리 마이그레이션.

beforeafter RSpec 훅은 데이터베이스를 다운 및 업 마이그레이션합니다. 이러한 훅은 다른 배치 백그라운드 마이그레이션이 호출되는 결과를 초래할 수 있습니다. it 블록에 정의된 기대가 RSpec 훅에서 호출되는 것과 충돌할 수 있으므로 일반 테스트 더블 대신 have_received와 함께 spy 테스트 더블을 사용하는 것을 권장합니다. 자세한 내용은 이슈 #35351을 참조하세요.

모범 사례#

처리하는 데이터의 양을 파악하세요.

배치 백그라운드 마이그레이션 job이 멱등적인지 확인하세요.

작성한 테스트가 거짓 양성이 아닌지 확인하세요.

마이그레이션하는 데이터가 중요하고 손실될 수 없는 경우, 정리 마이그레이션도 완료 전에 데이터의 최종 상태를 확인해야 합니다.

데이터베이스 전문가와 숫자를 논의하세요. 마이그레이션이 예상보다 DB에 더 많은 부담을 줄 수 있습니다. 스테이징에서 측정하거나 프로덕션에서 측정해 달라고 요청하세요.

배치 백그라운드 마이그레이션을 실행하는 데 필요한 시간을 파악하세요.

job 클래스 내에서 예외를 자동으로 처리할 때 주의하세요. 이로 인해 실패 시나리오에서도 job이 성공으로 표시될 수 있습니다.

# good
def perform
  each_sub_batch do |sub_batch|
    sub_batch.update_all(name: 'My Name')
  end
end

# acceptable
def perform
  each_sub_batch do |sub_batch|
    sub_batch.update_all(name: 'My Name')
  rescue Exception => error
    logger.error(message: error.message, class: error.class)

    raise
  end
end

# bad
def perform
  each_sub_batch do |sub_batch|
    sub_batch.update_all(name: 'My Name')
  rescue Exception => error
    logger.error(message: error.message, class: self.class.name)
  end
end

가능하면 각 모델을 개별적으로 업데이트하는 대신 단일 쿼리로 전체 서브 배치를 업데이트하세요. 그렇게 할 때는 항상 한도 가드를 포함하고 이를 구체화된 CTE로 추출하여 쿼리 계획 변경의 가능성을 제거하세요. 이는 시나리오에 따라 다양한 방법으로 달성할 수 있습니다.

UPDATE 쿼리를 생성하고 FROM을 사용하여 필요한 값을 제공하는 테이블을 조인합니다 (예시).

  • 미리 계산된 값을 전달하기 위해 UPDATE 쿼리를 생성하고 FROM(VALUES( ...))를 사용합니다 (예시).

  • 모든 키와 값을 ActiveRelation#update에 전달합니다.

# good
def perform
  each_sub_batch do |sub_batch|
    connection.execute <<~SQL
      WITH sub_batch_ids AS MATERIALIZED (
        #{sub_batch.select(:id).limit(sub_batch_size).to_sql}
      )
      UPDATE fork_networks
      SET organization_id = projects.organization_id
      FROM projects
      WHERE fork_networks.id IN (SELECT id FROM sub_batch_ids)
      AND fork_networks.root_project_id = projects.id
      AND fork_networks.organization_id IS NULL
    SQL
  end
end

# bad - uses pluck and does not use a limit
def perform
  each_sub_batch do |sub_batch|
    connection.execute <<~SQL
      UPDATE fork_networks
      SET organization_id = projects.organization_id
      FROM projects
      WHERE fork_networks.id IN (#{sub_batch.pluck(:id)})
      AND fork_networks.root_project_id = projects.id
      AND fork_networks.organization_id IS NULL
    SQL
  end
end

# bad
def perform
  each_sub_batch do |sub_batch|
    sub_batch.each |fork_network|
      fork_network.update!(organization_id: fork_network.root_project.organization_id)
    end
  end
end

예시#

Routes 사용 사례#

routes 테이블에는 다형성 관계에 사용되는 source_type 필드가 있습니다. 데이터베이스 재설계의 일환으로 다형성 관계를 제거하고 있습니다. 이 작업의 한 단계는 source_id 칼럼의 데이터를 새로운 단일 외래 키로 마이그레이션하는 것입니다. 나중에 이전 행을 삭제할 것이므로 백그라운드 마이그레이션의 일부로 업데이트할 필요가 없습니다.

제너레이터를 사용하여 배치 백그라운드 마이그레이션 파일을 생성하는 것으로 시작하세요:

bundle exec rails g batched_background_migration BackfillRouteNamespaceId --table_name=routes --column_name=id --feature_category=source_code_management

source_id 값을 namespace_id로 복사하도록 마이그레이션 job(BatchedMigrationJob의 서브클래스)을 업데이트하세요:

class Gitlab::BackgroundMigration::BackfillRouteNamespaceId < BatchedMigrationJob
  # For illustration purposes, if we were to use a local model we could
  # define it like below, using an `ApplicationRecord` as the base class
  # class Route < ::ApplicationRecord
  #   self.table_name = 'routes'
  # end

  operation_name :update_all
  feature_category :source_code_management

  def perform
    each_sub_batch(
      batching_scope: -> (relation) { relation.where("source_type <> 'UnusedType'") }
    ) do |sub_batch|
      sub_batch.update_all('namespace_id = source_id')
    end
  end
end

job 클래스는 배치 마이그레이션 프레임워크가 올바르게 처리되도록 BatchedMigrationJob에서 상속합니다. BatchedMigrationJob의 모든 서브클래스는 배치를 실행하는 데 필요한 인수와 추적 데이터베이스에 대한 연결로 초기화됩니다.

데이터베이스에 새 트리거를 추가하는 데이터베이스 마이그레이션을 생성하세요. 예시:

class AddTriggerToRoutesToCopySourceIdToNamespaceId < Gitlab::Database::Migration[2.1]
  FUNCTION_NAME = 'example_function'
  TRIGGER_NAME = 'example_trigger'

  def up
    execute(<<~SQL)
      CREATE OR REPLACE FUNCTION #{FUNCTION_NAME}() RETURNS trigger
      LANGUAGE plpgsql
      AS $
      BEGIN
        NEW."namespace_id" = NEW."source_id"
        RETURN NEW;
      END;
      $;

      CREATE TRIGGER #{TRIGGER_NAME}() AFTER INSERT OR UPDATE
      ON routes
      FOR EACH ROW EXECUTE FUNCTION #{FUNCTION_NAME}();
    SQL
  end

  def down
    drop_trigger(TRIGGER_NAME, :routes)
    drop_function(FUNCTION_NAME)
  end
end

필요한 배치 크기로 생성된 포스트 배포 마이그레이션을 업데이트하세요:

class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BackfillRouteNamespaceId'
  BATCH_SIZE = 1000
  SUB_BATCH_SIZE = 100

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    queue_batched_background_migration(
      MIGRATION,
      :routes,
      :id,
      batch_size: BATCH_SIZE,
      sub_batch_size: SUB_BATCH_SIZE
    )
  end

  def down
    delete_batched_background_migration(MIGRATION, :routes, :id, [])
  end
end
 # db/docs/batched_background_migrations/backfill_route_namespace_id.yml
 ---
 migration_job_name: BackfillRouteNamespaceId
 description: Copies source_id values from routes to namespace_id
 feature_category: source_code_management
 introduced_by_url: "https://mr_url"
 milestone: 16.6
 queued_migration_version: 20231113120650
 finalized_by: # version of the migration that ensured this bbm

배치 백그라운드 마이그레이션을 큐에 추가할 때는 실제 변경을 수행하는 데이터베이스로 스키마를 제한해야 합니다. 이 경우 routes 레코드를 업데이트하므로 restrict_gitlab_migration gitlab_schema: :gitlab_main_org을 설정합니다. 그러나 CI 데이터 마이그레이션을 수행해야 하는 경우 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정합니다.

배포 후 애플리케이션은:

이전과 같이 데이터를 계속 사용합니다.

  • 기존 데이터와 새 데이터가 모두 마이그레이션되도록 합니다.

배치 백그라운드 마이그레이션이 완료되었는지 확인하는 새 포스트 배포 마이그레이션을 추가하세요. 또한 BBM 딕셔너리의 finalized_by 속성을 이 마이그레이션의 버전으로 업데이트하세요.

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

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    ensure_batched_background_migration_is_finished(
      job_class_name: 'BackfillRouteNamespaceId',
      table_name: :routes,
      column_name: :id,
      job_arguments: [],
      finalize: true
    )
  end

  def down
    # no-op
  end
end
 # db/docs/batched_background_migrations/backfill_route_namespace_id.yml
 ---
 migration_job_name: BackfillRouteNamespaceId
 description: Copies source_id values from routes to namespace_id
 feature_category: source_code_management
 introduced_by_url: "https://mr_url"
 milestone: 16.6
 queued_migration_version: 20231113120650
 finalized_by: 20231115120912

배치 백그라운드 마이그레이션이 완료되지 않은 경우, 시스템이 배치 백그라운드 마이그레이션을 인라인으로 실행합니다. 이 동작을 원하지 않으면 finalize: false를 전달해야 합니다.

애플리케이션이 데이터가 100% 마이그레이션되는 것에 의존하지 않는 경우 (예: 데이터가 권고적이고 미션 크리티컬하지 않은 경우), 이 마지막 단계를 건너뛸 수 있습니다. 이 단계는 마이그레이션이 완료되고 모든 행이 마이그레이션되었음을 확인합니다.

트리거를 제거하는 데이터베이스 마이그레이션을 추가하세요.

class RemoveNamepaceIdTriggerFromRoutes < Gitlab::Database::Migration[2.1]
  FUNCTION_NAME = 'example_function'
  TRIGGER_NAME = 'example_trigger'

  def up
    drop_trigger(TRIGGER_NAME, :routes)
    drop_function(FUNCTION_NAME)
  end

  def down
    # Should reverse the trigger and the function in the up method of the migration that added it
  end
end

배치 마이그레이션이 완료되면 routes.namespace_id의 데이터가 채워졌다고 안전하게 의존할 수 있습니다.