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

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

note
배치된 백그라운드 마이그레이션은 레거시 백그라운드 마이그레이션 프레임워크를 대체했습니다. 해당 프레임워크와 관련된 변경 사항에 대한 설명은 문서를 확인하세요.
note
배치된 백그라운드 마이그레이션 프레임워크에는 ChatOps 지원이 있습니다. ChatOps를 사용하면 GitLab 엔지니어는 시스템에 있는 배치된 백그라운드 마이그레이션과 상호 작용할 수 있습니다.

배치된 백그라운드 마이그레이션 사용 시기

배치된 백그라운드 마이그레이션(BBM)은 일반적인 Rails 마이그레이션을 사용하여 수행하는 경우 시간 제한을 초과할 것으로 예상되는 많은 행을 포함하는 테이블에서 데이터를 마이그레이션할 때 사용해야 합니다.

  • 배치된 백그라운드 마이그레이션은 데이터 마이그레이션 시 가이드라인에서 제시하는 시간 제한을 초과하는 고트래픽 테이블에서 데이터를 마이그레이션하는 경우에 사용해야 합니다.
  • 또한, 대규모 데이터 집합의 각 항목에 대해 많은 단일 행 쿼리를 실행할 때에도 배치된 백그라운드 마이그레이션을 사용할 수 있습니다. 일반적으로, 단일 레코드 패턴의 경우 런타임은 데이터 집합의 크기에 크게 의존합니다. 데이터 집합을 적절하게 분할하고 백그라운드 마이그레이션에 포함시킵니다.
  • 스키마 마이그레이션을 수행하는 데 배치된 백그라운드 마이그레이션을 사용해서는 안됩니다.

배경 마이그레이션은 다음과 같은 경우에 도움이 됩니다:

  • 한 테이블에서 여러 개의 별도 테이블로 이벤트를 마이그레이션하는 경우
  • 다른 열에 저장된 JSON을 기반으로 한 열의 값을 채우는 경우
  • 외부 서비스의 출력에 따라 데이터를 마이그레이션하는 경우(예: API)

참고 사항

  • 배치된 백그라운드 마이그레이션이 중요한 업그레이드의 일환인 경우 릴리스 게시물에 공지해야 합니다. 마이그레이션이 이 범주에 속하는지 확신이 들 경우 프로젝트 매니저와 상의하세요.
  • 필요한 파일이 기본적으로 생성되도록 생성기를 사용하여 배치된 백그라운드 마이그레이션을 생성해야 합니다.

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

배치된 백그라운드 마이그레이션(BBM)은 Gitlab::BackgroundMigration::BatchedMigrationJob의 서브클래스로, perform 메서드를 정의합니다. 첫 번째 단계로 정규 마이그레이션이 BBM 클래스와 필요한 인수를 사용하여 batched_background_migrations 레코드를 생성합니다. 기본적으로 batched_background_migrations은 활성 상태이며, 실제 배치된 마이그레이션을 실행하기 위해 Sidekiq 워커에서 선택됩니다.

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

실행 메커니즘

배치된 백그라운드 마이그레이션은 대기열에서 인큐된 순서대로 선택됩니다. 여러 마이그레이션이 활성 상태이며 동시에 실행되는 평행 처리가 이루어집니다. 기본적으로 병렬로 처리되는 마이그레이션 수는 2개이며, GitLab.com의 경우 이 제한은 4개로 구성됩니다. 마이그레이션이 실행되도록 선택되면 특정 배치를 위해 작업이 만들어집니다. 각 작업 실행 후에 마이그레이션의 배치 크기는 마지막 20개 작업의 성능에 따라 증가 또는 감소될 수 있습니다.

한 번 워커가 사용 가능해지면 BBM은 실행됩니다.

동일성

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

자세한 내용은 Sidekiq 최선의 사례 지침을 참조하세요.

마이그레이션 최적화

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

작업 재시도 메커니즘

배치된 백그라운드 마이그레이션 재시도 메커니즘은 작업이 실패한 경우 작업이 다시 실행되도록 보장합니다. 다음 다이어그램은 우리의 재시도 메커니즘의 여러 단계를 보여줍니다.

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

모든 작업을 소비하고 실패한 작업이 있는 경우, 배치된 백그라운드 마이그레이션이 실패로 표시됩니다(/chatops run batched_background_migrations status MIGRATION_ID를 사용하여 마이그레이션을 failed로 표시합니다). 배경 마이그레이션이 시작된 이후 작업의 절반 이상이 실패한 경우도 실패로 표시됩니다.

배치 마이그레이션 쓰로틀링

배치 마이그레이션이 업데이트 중심임과 이전에 데이터베이스가 성능을 제대로 발휘하지 못해 발생한 몇 가지 사건 때문에 마이그레이션 부하를 완화하기 위한 쓰로틀링 메커니즘이 있습니다.

데이터베이스 지표가 마이그레이션을 제한하는지 확인하기 위한 메트릭 지표가 있습니다. 정지 신호를 수신하면 일정 시간(10분) 동안 마이그레이션이 일시 중지됩니다:

  • 임계값을 초과하는 WAL 큐 대기 아카이브
  • 마이그레이션이 작동하는 테이블의 활성 autovacuum
  • Patroni apdex SLI가 SLO 아래로 떨어짐
  • 임계값을 초과하는 WAL 속도

데이터베이스 건강 검사 프레임워크를 추가하기 위한 계속적인 노력이 이루어지고 있습니다. 자세한 내용은 epic 7594를 참조하세요.

고립

일괄 배경 마이그레이션이 고립되어 있어야 하며 애플리케이션 코드 (예: app/models에 정의된 모델들, 단, ApplicationRecord 클래스 제외)를 사용할 수 없습니다. 이러한 마이그레이션은 실행에 오랜 시간이 걸릴 수 있기 때문에, 마이그레이션이 계속 실행되는 동안 새 버전이 배포될 수 있습니다.

마이그레이션 된 데이터에 따라

일반적이거나 마이그레이션 이후와는 달리, 다음 릴리스를 기다리는 것만으로는 데이터가 완전히 마이그레이션된 것을 보장하기에는 부족합니다. 즉, 일괄 배경 마이그레이션이 완료될 때까지는 데이터에 의존해서는 안 됩니다. 데이터의 100%가 마이그레이션되어야 하는 요구 사항이 있다면, ensure_batched_background_migration_is_finished 도우미를 사용하여 마이그레이션이 완료되고 데이터가 완전히 마이그레이션된 것을 보장할 수 있습니다. (예시 보기).

방법

일괄 배경 마이그레이션 생성

사용자 정의 생성기 batched_background_migration은 필요한 파일을 만들고 table_name, column_name, 그리고 feature_category를 인수로 받습니다. 사용법:

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을 사용하여 마이그레이션을 일괄로 실행하도록 대기열에 넣습니다. 클래스 이름과 인수를 해당 마이그레이션에 사용된 값으로 바꿉니다:

queue_batched_background_migration(
  JOB_CLASS_NAME,
  TABLE_NAME,
  JOB_ARGUMENTS,
  JOB_INTERVAL
  )
note
queue_batched_background_migration은 제공된 작업 인수의 수가 JOB_CLASS_NAME에서 정의된 작업 인수의 수와 일치하지 않으면 오류를 발생시킵니다.

새롭게 생성된 데이터가 마이그레이션이 완료되거나, 생성 시 이전 버전과 새 버전 모두에 저장되었는지 확인해야 합니다. 해당 사항은 외래 키를 정의하여 제거될 수 있습니다.

일괄 배경 마이그레이션 완료

일괄 배경 마이그레이션을 완료하기 위해서는 ensure_batched_background_migration_is_finished을 호출합니다.

이것은 안전하게 수행할 수 있다고 판단할 때 모든 일괄 배경 마이그레이션을 완료하는 것이 중요합니다. 기존의 일괄 배경 마이그레이션을 그대로 두면 테스트와 응용 프로그램 동작에서 유지 관리해야 하는 기술적 부채의 한 형태입니다. 어떤 일괄 배경 마이그레이션이 완료되었는지에 대해 의존할 수 없다는 사실을 꼭 기억해야 합니다.

일괄 배경 마이그레이션을 완료할 때는 다음 조건이 모두 충족될 때 권장합니다:

  • 일괄 배경 마이그레이션이 GitLab.com에서 완료되었습니다.
  • 일괄 배경 마이그레이션이 마지막 필수 중단에서 추가되었습니다.

ensure_batched_background_migration_is_finished 호출은 대기열에 들어간 마이그레이션과 정확히 일치해야 합니다. 다음 사항을 주의깊게 확인하세요:

  • 작업 인수: 대기열에 들어간 마이그레이션을 찾지 못하게 하려면 명시된 작업 인수와 정확히 일치해야 합니다.
  • gitlab_schema: 대기열에 들어간 마이그레이션을 찾지 못하게 하려면 해당 테이블의 gitlab_schema가 정확히 일치해야 합니다. 중간에 테이블의 gitlab_schemagitlab_main에서 gitlab_main_cell로 변경되었다 하더라도 대기열에 들어간 마이그레이션을 할 때 gitlab_main으로 완료해야 합니다.

일괄 배경 마이그레이션을 완료할 때는 해당 일괄 배경 마이그레이션을 추가하고 완료한 마이그레이션의 타임스탬프/버전을 finalized_by에 업데이트해야 합니다.

실제 마이그레이션 코드에 대한 구체적인 세부 정보는 아래 예시를 참조하세요.

작업 인수 사용

BatchedMigrationJob은 작업 클래스가 필요로 하는 작업 인수를 정의하는 데 사용하는 job_arguments 도우미 메서드를 제공합니다.

queue_batched_background_migration으로 예약된 일괄 마이그레이션은 작업 인수를 정의하는 데 반드시 도우미를 사용해야 합니다:

queue_batched_background_migration(
  'CopyColumnUsingBackgroundMigrationJob',
  TABLE_NAME,
  'name', 'name_convert_to_text',
  job_interval: DELAY_INTERVAL
)
note
일정한 마이그레이션을 스케줄하는 경우에, 정의된 작업 인수의 수가 마이그레이션 예약 시 제공된 작업 인수의 수와 일치하지 않으면 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

필터 사용

기본적으로 마이그레이션을 수행하기 위해 백그라운드 작업을 만들 때, 일괄 배경 마이그레이션은 지정된 테이블에 대해 전체 이터레이션을 수행합니다. 이 이터레이션은 PrimaryKeyBatchingStrategy을 사용하여 수행됩니다. 테이블에 1000개의 레코드가 있고 배치 크기가 100이면, 작업은 10개의 작업으로 나누어집니다. 설명을 위해 EachBatch을 사용하는 것은 다음과 같습니다:

# PrimaryKeyBatchingStrategy
Namespace.each_batch(of: 100) do |relation|
  relation.where(type: nil).update_all(type: 'User') # 이것이 각 백그라운드 작업에서 수행됩니다
end

일부 경우에는 레코드의 일부만 검사해야 하는 경우가 있습니다. 1000개의 레코드 중 10%만 검사해야 할 경우, 작업을 생성할 때 초기 관련에 필터를 적용하세요.

첫 번째 예시에서는 각 배치에서 업데이트할 레코드 수를 알 수 없습니다. 두 번째 (필터링된) 예시에서는 각 배치에서 정확히 100개의 레코드가 업데이트됩니다.

BatchedMigrationJob은 추가 필터를 적용하고 이러한 목적을 달성하기 위해 scope_to 도우미 메서드를 제공합니다:

  1. BatchedMigrationJob에서 BatchedMigrationJob를 상속받은 새 마이그레이션 작업 클래스를 생성하고 추가 필터를 정의합니다:

    class BackfillNamespaceType < BatchedMigrationJob
      scope_to ->(relation) { relation.where(type: nil) }
      operation_name :update_all
      feature_category :source_code_management
         
      def perform
        each_sub_batch do |sub_batch|
          sub_batch.update_all(type: 'User')
        end
      end
    end
    
    note
    scope_to를 정의하는 EE 마이그레이션의 경우, 모듈이 ActiveSupport::Concern을 확장하는지 확인하세요. 그렇지 않으면 영향 범위를 고려하지 않고 레코드가 처리됩니다.
  2. 배포 후 마이그레이션에서 일괄 배경 마이그레이션을 대기열에 넣습니다:

    class BackfillNamespaceType < Gitlab::Database::Migration[2.1]
      MIGRATION = 'BackfillNamespaceType'
      DELAY_INTERVAL = 2.minutes
         
      restrict_gitlab_migration gitlab_schema: :gitlab_main
         
      def up
        queue_batched_background_migration(
          MIGRATION,
          :namespaces,
          :id,
          job_interval: DELAY_INTERVAL
        )
      end
         
      def down
        delete_batched_background_migration(MIGRATION, :namespaces, :id, [])
      end
    end
    
note
추가 필터를 적용할 때는, 해당 필터가 성능을 최적화하기 위해 적절한 인덱스로 충분히 커버되어 있는지 확인하는 것이 중요합니다. 위 예시에서, 필터에 대한 (type, id) 인덱스가 필요합니다. 자세한 정보는 각 배치에 대한 문서를 참조하세요.

다중 데이터베이스에 대한 데이터 액세스

일반 마이그레이션과는 다르게 백그라운드 마이그레이션은 여러 개의 데이터베이스에 액세스할 수 있으며, 효율적으로 데이터에 액세스하고 업데이트하는 데 사용할 수 있습니다. 사용할 데이터베이스를 명확히 지정하기 위해 마이그레이션 코드 내에서 ActiveRecord 모델을 만드는 것이 좋습니다. 이러한 모델은 테이블이 위치한 데이터베이스에 따라 올바른 ApplicationRecord을 사용해야 합니다. 따라서 ActiveRecord::Base의 사용은 해당 테이블에 액세스하기 위해 명시적으로 데이터베이스를 설명하지 않기 때문에 허용되지 않습니다.

# 좋은 예
class Gitlab::BackgroundMigration::ExtractIntegrationsUrl
  class Project < ::ApplicationRecord
    self.table_name = 'projects'
  end
  
  class Build < ::Ci::ApplicationRecord
    self.table_name = 'ci_builds'
  end
end

# 나쁜 예
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의 사용은 허용되지 않으며, 가능한 경우 모델 연결을 사용하여 대체해야 합니다.

# 좋은 예
Project.connection.execute("SELECT * FROM projects")

# 적절한 예
ApplicationRecord.connection.execute("SELECT * FROM projects")

# 나쁜 예
ActiveRecord::Base.connection.execute("SELECT * FROM projects")

일괄 배경 마이그레이션 다시 대기열에 넣기

일괄 배경 마이그레이션은 여러 가지 이유로 다시 실행해야 할 수 있습니다.

  • 마이그레이션에 버그가 포함되어 있을 때 (예시).
  • 마이그레이션이 데이터를 정리했지만 응용 프로그램 로직을 우회하여 데이터가 다시 정규화되었을 때 (예시).
  • 원래 마이그레이션의 일괄 크기로 인해 마이그레이션이 실패할 때 (예시).

일괄 배경 마이그레이션을 다시 대기열에 넣으려면 다음을 수행해야 합니다:

  • 원래 마이그레이션 파일의 #up#down 메서드의 내용을 No-op으로 처리해야 합니다. 그렇지 않으면 일괄 배경 마이그레이션이 여러 패치 릴리스를 동시에 업그레이드하는 시스템에서 만들어지고, 삭제된 후에 다시 생성됩니다.
  • 새로운 배포 후 마이그레이션을 추가하여 일괄 배경 마이그레이션을 다시 실행해야 합니다.
  • 새로운 배포 후 마이그레이션에서 기존의 일괄 배경 마이그레이션 실행이 정리되도록 #up 메서드 시작 시 delete_batched_background_migration 메서드를 사용해야 합니다.
  • 원래 마이그레이션의 db/docs/batched_background_migration/*.yml 파일을 업데이트하여 다시 대기열에 대한 정보를 포함해야 합니다.

중복되지 않는 열을 위한 일괄 배경 처리

기본 일괄 처리 전략은 기본 키 열을 효율적으로 반복하는 방법을 제공합니다. 그러나 고유하지 않은 값의 열을 반복해야 하는 경우 다른 일괄 처리 전략을 사용해야 합니다.

LooseIndexScanBatchingStrategy 일괄 처리 전략은 고유한 열 값에 대해 효율적이고 안정적으로 반복하기 위해 특별한 버전의 EachBatch를 사용합니다.

이 예에서는 issues.project_id 열을 배치 열로 사용하는 일괄 배경 마이그레이션을 보여줍니다.

데이터베이스 후 마이그레이션:

class ProjectsWithIssuesMigration < Gitlab::Database::Migration[2.1]
  MIGRATION = 'BatchProjectsWithIssues'
  INTERVAL = 2.minutes
  BATCH_SIZE = 5000
  SUB_BATCH_SIZE = 500
  restrict_gitlab_migration gitlab_schema: :gitlab_main
  
  disable_ddl_transaction!
  def up
    queue_batched_background_migration(
      MIGRATION,
      :issues,
      :project_id,
      job_interval: INTERVAL,
      batch_size: BATCH_SIZE,
      batch_class_name: 'LooseIndexScanBatchingStrategy', # 기본 일괄 처리 전략 재정의
      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)
          # 고유한 프로젝트 ID로 무언가 수행
        end
      end
    end
  end
end
note
scope_to로 정의된 추가 필터LooseIndexScanBatchingStrategydistinct_each_batch에서 무시됩니다.

일괄 배경 마이그레이션의 전체 소요 시간 계산

일괄 배경 마이그레이션이 완료되는 데 얼마나 오래 걸리는지 추정할 수 있습니다. GitLab은 이미 db:gitlabcom-database-testing 파이프라인을 통해 추정을 제공합니다. 이 추정은 테스트 환경에서 프로덕션 데이터를 샘플링하여 마이그레이션이 소요될 수 있는 최대 시간을 나타내며 실제 마이그레이션이 소요되는 시간이 아닐 수 있습니다. 특정 시나리오에서 db:gitlabcom-database-testing 파이프라인에서 제공하는 추정이 레코드 주변의 모든 특이성을 계산하는 데 충분하지 않을 수 있으므로 추가 계산이 필요할 수 있습니다. 필요한 경우 interval * number of records / max batch size 공식을 사용하여 마이그레이션이 소요되는 대략적인 추정을 결정하는 데 사용할 수 있습니다. 여기서 intervalmax batch size는 작업에 정의된 옵션을 나타내며 total tuple count는 마이그레이션할 레코드 수입니다.

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

일괄 배경 마이그레이션 정리

note
남은 배경 마이그레이션을 정리해야 하는 경우 주요 패치 릴리스 또는 마이너 릴리스에서 수행해야 합니다. 패치 릴리스에서는 절대로 수행하면 안 됩니다.

배경 마이그레이션은 오랜 시간이 걸릴 수 있기 때문에 바로 정리할 수는 없습니다. 예를 들어, 마이그레이션 처리에 사용되는 열을 삭제할 수 없으며 작업이 실패할 수 있습니다. 테이블에서 사용된 기존 데이터를 처리하기 전에 미래 릴리스에서 별도의 배포 후 마이그레이션을 추가하여 나머지를 모두 처리한 후 정리해야 합니다. (예: 열 제거)

foo (큰 JSON 블롭을 포함)의 데이터를 bar (문자열을 포함)로 마이그레이션하는 경우 다음을 수행해야 합니다:

  1. 릴리스 A:
    1. 특정 ID를 가진 행에 대해 마이그레이션을 수행하는 마이그레이션 클래스를 작성합니다.
    2. 다음 기술 중 하나를 사용하여 새로운 행을 업데이트합니다:
      • 응용 프로그램 로직이 필요하지 않은 복사 작업을 위한 새로운 트리거 생성
      • 레코드가 생성되거나 업데이트될 때 모델/서비스에서 이 작업 처리
      • 레코드를 업데이트하는 사용자 정의 백그라운드 작업 생성
    3. 기존 행의 모든 일괄 배경 마이그레이션에 대해 대기열에 넣습니다.
  2. 릴리스 B:
    1. 일괄 배경 마이그레이션이 완료되었는지 확인하는 post-deployment 마이그레이션 추가
    2. 응용 프로그램이 새 열을 사용하여 새로운 레코드를 업데이트하지 않도록 코드를 배포합니다.
    3. 이전 열을 제거합니다.

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

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

GitLab.com에서 배치된 백그라운드 마이그레이션이 특정 배치에서 실패했고 어떤 쿼리가 실패했는지 그 이유를 알고 싶다고 가정해 봅시다. 현재로서는 쿼리 정보(특히 쿼리 매개변수)를 검색할 수 있는 좋은 방법이 없으며, 더 많은 로깅을 사용하여 전체 마이그레이션을 다시 실행하는 것은 시간이 오래 걸릴 것입니다.

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

예시는 Draft: Test PG::CardinalityViolation fix를 참고하되 전체 섹션을 읽도록 합니다.

그러려면 다음을 수행해야 합니다.

  1. 배치 start_idend_id 찾기
  2. 일반적인 마이그레이션 생성
  3. 마이그레이션 헬퍼에 대한 회피 적용 (옵션) (옵션)
  4. 데이터베이스 마이그레이션 파이프라인 시작

배치 start_idend_id 찾기

Kibana에서 해당 정보를 찾을 수 있어야 합니다.

일반적인 마이그레이션 생성

일반적인 마이그레이션의 up 블록에서 배치를 예약하세요.

def up
  instance = Gitlab::BackgroundMigration::YourBackgroundMigrationClass.new(
      start_id: <배치 start_id>,
      end_id: <배치 end_id>,
      batch_table: <테이블 이름>,
      batch_column: <배칭 >,
      sub_batch_size: <서브 배치 크기>,
      pause_ms: <배치  밀리초>,
      job_arguments: <작업 매개변수(있는 경우)>,
      connection: connection
    )
    
    instance.perform
end

def down
  # no-op
end

마이그레이션 헬퍼에 대한 회피 적용 (옵션)

만약 배치된 백그라운드 마이그레이션이 restrict_gitlab_migration 헬퍼를 사용하여 지정한 스키마 외의 스키마의 테이블에 접근하는 경우(예: 스케줄링 마이그레이션이 restrict_gitlab_migration gitlab_schema: :gitlab_main을 사용하지만 백그라운드 작업에서 :gitlab_ci 스키마의 테이블을 사용하는 경우) 마이그레이션이 실패합니다. 이를 방지하려면 데이터베이스 헬퍼를 수정하여 테스트 파이프라인 작업이 실패하지 않도록 해야 합니다.

  1. RestrictGitlabSchema에 스키마 이름 추가
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
  1. RestrictAllowedSchemas에 스키마 이름 추가
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}'. " \

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

변경 사항을 반영한 드래프트 Merge Request을 만들고 매뉴얼으로 db:gitlabcom-database-testing 작업을 트리거하세요.

의존성 설정

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

다음 프로세스는 마이그레이션 작성 시 의존성을 보다 명확하게 만들기 위해 구성되었습니다.

  • BBM을 인큐한 마이그레이션의 버전은 batched_background_migrations 테이블과 BBM 사전 파일에 저장됩니다.
  • DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS 상수는 각 마이그레이션 파일에 추가됩니다(기본적으로 주석 처리됨). 의존성을 설정하려면, 의존하는 BBM의 queued_migration_version을 추가하세요. 그렇지 않으면 주석 처리된 줄을 제거하세요.
  • Migration::UnfinishedDependencies 코프는 의존하는 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
  ...
  ...
  
  def up
    queue_batched_background_migration(
      MIGRATION,
      ...
    )
  end
end
# 이 마이그레이션은 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

참고

  • BackgroundMigration::DictionaryFile 코프는 BBM 사전에 finalize_afterintroduced_by_url 키가 있는지 확인합니다.
    • finalize_after: BBM의 예상 완료일(대략적)을 기록합니다.
    • introduced_by_url: finalize_after 날짜 이후에는 introduced_by_url의 레이블과 작성자를 사용하여 이슈가 생성됩니다.
      • 작성 시점(2023-08-11)에서 이슈 #424886는 아직 열려 있습니다.

관리

note
BBM(배치된 백그라운드 마이그레이션) 관리는 GitLab 팀 멤버만 가능한 chatops 통합을 통해 이루어집니다.

배치된 백그라운드 마이그레이션 디렉터리

시스템의 배치된 백그라운드 마이그레이션을 나열하려면 다음 명령을 실행하세요:

/chatops run batched_background_migrations list

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

  • 데이터베이스 선택:
    • --database DATABASE_NAME: 주어진 데이터베이스에 연결합니다:
      • main: 기본으로 메인 데이터베이스를 사용합니다.
      • ci: CI 데이터베이스를 사용합니다.
  • 환경 선택:
    • --dev: dev 환경을 사용합니다.
    • --staging: staging 환경을 사용합니다.
    • --staging_ref: staging_ref 환경을 사용합니다.
    • --production : 기본으로 production 환경을 사용합니다.

출력 예시:

List command

note
chatops는 생성일을 기준으로 20개의 배치된 백그라운드 마이그레이션을 반환합니다(created_at를 기준으로 내림차순).

배치된 백그라운드 마이그레이션의 진행 상황 및 상태 모니터링

특정 배치된 백그라운드 마이그레이션의 상태와 진행 상황을 보려면 다음 명령을 실행하세요:

/chatops run batched_background_migrations status MIGRATION_ID

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

  • 데이터베이스 선택:
    • --database DATABASE_NAME: 주어진 데이터베이스에 연결합니다:
      • main: 기본으로 메인 데이터베이스를 사용합니다.
      • ci: CI 데이터베이스를 사용합니다.
  • 환경 선택:
    • --dev: dev 환경을 사용합니다.
    • --staging: staging 환경을 사용합니다.
    • --staging_ref: staging_ref 환경을 사용합니다.
    • --production : 기본으로 production 환경을 사용합니다.

출력 예시:

Status command

진행률은 완료된 백그라운드 마이그레이션의 퍼센트를 나타냅니다.

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

  • 활성:
    • 러너에 의해 선택될 준비가 됨.
    • 배치된 작업을 실행 중.
  • 완료 중: 배치된 작업을 실행 중.
  • 실패: 실패한 배치된 백그라운드 마이그레이션.
  • 완료: 완료된 배치된 백그라운드 마이그레이션.
  • 일시 중지: 러너에게 보이지 않음.

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

배치된 백그라운드 마이그레이션을 일시 중지하려면 다음 명령을 실행해야 합니다:

/chatops run batched_background_migrations pause MIGRATION_ID

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

  • 데이터베이스 선택:
    • --database DATABASE_NAME: 주어진 데이터베이스에 연결합니다:
      • main: 기본으로 메인 데이터베이스를 사용합니다.
      • ci: CI 데이터베이스를 사용합니다.
  • 환경 선택:
    • --dev: dev 환경을 사용합니다.
    • --staging: staging 환경을 사용합니다.
    • --staging_ref: staging_ref 환경을 사용합니다.
    • --production : 기본으로 production 환경을 사용합니다.

출력 예시:

Pause command

note
활성인 배치된 백그라운드 마이그레이션만 일시 중지할 수 있습니다.

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

배치된 백그라운드 마이그레이션을 재개하려면 다음 명령을 실행해야 합니다:

/chatops run batched_background_migrations resume MIGRATION_ID

이 몹래은 다음 옵션을 지원합니다:

  • 데이터베이스 선택:
    • --database DATABASE_NAME: 주어진 데이터베이스에 연결합니다:
      • main: 기본으로 메인 데이터베이스를 사용합니다.
      • ci: CI 데이터베이스를 사용합니다.
  • 환경 선택:
    • --dev: dev 환경을 사용합니다.
    • --staging: staging 환경을 사용합니다.
    • --staging_ref: staging_ref 환경을 사용합니다.
    • --production : 기본으로 production 환경을 사용합니다.

출력 예시:

Resume command

note
활성인 배치된 백그라운드 마이그레이션만 재개할 수 있습니다.

배경 마이그레이션 활성화 또는 비활성화

극히 제한된 상황에서 GitLab 관리자는 다음 피처 플래그 중 하나 이상을 비활성화할 수 있습니다:

  • execute_background_migrations
  • execute_batched_migrations_on_schedule

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

caution
플래그 중 하나라도 비활성화하지 마십시오. 해당 플래그를 완전히 이해하지 못하는 경우에만 사용하세요. execute_background_migrations 또는 execute_batched_migrations_on_schedule 피처 플래그를 비활성화하면 GitLab 업데이트가 실패할 수 있고 데이터 손실이 발생할 수 있습니다.

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

EE-전용 기능에 대한 모든 배경 마이그레이션 클래스는 GitLab FOSS에 존재해야 합니다. 이를 위해 GitLab FOSS용으로 빈 클래스를 만들고 GitLab EE에서 확장하세요. Enterprise Edition 기능 구현 지침에 설명된대로 진행하세요.

note
작업 인수를 사용하는 EE 전용 기능을 위한 배경 마이그레이션 클래스는 GitLab FOSS 클래스에 정의해야 합니다. Migration이 GitLab FOSS 컨텍스트에서 예약되었을 때 작업 인수 유효성 검증이 실패하지 않도록 정의가 필요합니다.

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

디버그

실패 오류 로그 보기

두 가지 방법으로 실패 내용을 볼 수 있습니다:

  • GitLab 로그를 통해:
    1. 배치된 백그라운드 마이그레이션을 실행한 후 작업이 실패한 경우 Kibana에서 로그를 확인하세요. 프로덕션 Sidekiq 로그를 보고 다음을 필터링하세요:
      • json.new_state: failed
      • json.job_class_name: <Batched Background Migration job class name>
      • json.job_arguments: <Batched Background Migration job class arguments>
    2. 작업이 왜 실패했는지 이해하는 데 도움이 되도록 json.exception_classjson.exception_message 값을 확인하세요.

    3. 재시도 메커니즘을 기억하세요. 실패는 작업이 실패했다는 것을 의미하지 않습니다. 항상 작업의 최종 상태를 확인하세요.
  • 데이터베이스를 통해:

    1. 배치된 백그라운드 마이그레이션 CLASS_NAME을 가져옵니다.
    2. 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";
      

테스트

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

  • 배치된 백그라운드 마이그레이션의 대기 중인 마이그레이션.
  • 배치된 백그라운드 마이그레이션 자체.
  • 정리 마이그레이션.

:migrationschema: :latest RSpec 태그는 백그라운드 마이그레이션 스펙에 대해 자동으로 설정됩니다. Rails 마이그레이션 테스트 스타일 가이드를 참조하세요.

beforeafter RSpec 훅은 데이터베이스를 업 및 다운 마이그레이션하므로 주의하세요. 이러한 훅은 다른 배치된 백그라운드 마이그레이션을 호출할 수 있습니다. spy 테스트 더블 사용 및 have_received를 통해 기대하는 것이 RSpec 훅에서 호출되는 것과 충돌하지 않도록 하세요. 자세한 내용은 issue #35351을 참조하세요.

최선의 방법

  1. 다루고 있는 데이터의 양을 파악하세요.
  2. 배치된 백그라운드 마이그레이션 작업이 멱등성을 보장하는지 확인하세요.
  3. 작성한 테스트가 거짓 긍정 결과를 내지 않는지 확인하세요.
  4. 마이그레이션하는 데이터가 중요하고 손실할 수 없는 경우, 마무리되기 전에 정리 마이그레이션은 데이터의 최종 상태를 확인해야 합니다.
  5. 숫자를 데이터베이스 전문가와 함께 검토하세요. 마이그레이션은 예상 이상으로 DB에 압력을 주거나 예상 이하로 DB에 압력을 주지 않는지 확인하세요. 스테이징에서 메트릭하거나, 또는 프로덕션에서 메트릭할 수 있도록 구성하세요.
  6. 배치된 백그라운드 마이그레이션 실행에 필요한 시간을 파악하세요.
  7. 작업 클래스 내에서 예외를 조용히 구해야 할 때 주의하세요. 이렇게 하면 실패 시나리오에서도 작업이 성공으로 표시될 수 있습니다.

    # 좋음
    def perform
      each_sub_batch do |sub_batch|
        sub_batch.update_all(name: 'My Name')
      end
    end
       
    # 허용됨
    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
       
    # 나쁨
    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
    

예제

라우트 사용 사례

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

  1. 제너레이터를 사용하여 배치된 백그라운드 마이그레이션 파일을 생성하세요:

    bundle exec rails g batched_background_migration BackfillRouteNamespaceId --table_name=routes --column_name=id --feature_category=source_code_management
    
  2. 마이그레이션 작업을 업데이트하여 (BatchedMigrationJob의 서브클래스) source_id 값을 namespace_id로 복사하세요:

    class Gitlab::BackgroundMigration::BackfillRouteNamespaceId < BatchedMigrationJob
      # 설명을 위해 지역 모델을 사용한다면, 아래처럼 `ApplicationRecord`를 베이스 클래스로 사용하여 정의할 수 있습니다.
      # 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
    
    note
    작업 클래스는 BatchedMigrationJob을 상속하여 배치된 마이그레이션 프레임워크에서 올바르게 처리되도록 합니다. BatchedMigrationJob의 하위 클래스는 배치를 실행하고 추적 데이터베이스에 연결하는 데 필요한 인수로 초기화됩니다.
  3. 데이터베이스에 새 트리거를 추가하는 데이터베이스 마이그레이션을 생성하세요. 예시:

    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
    
  4. 필요한 지연 및 배치 크기로 생성된 배포 후 마이그레이션을 업데이트하세요:

    class QueueBackfillRoutesNamespaceId < Gitlab::Database::Migration[2.1]
      MIGRATION = 'BackfillRouteNamespaceId'
      DELAY_INTERVAL = 2.minutes
      BATCH_SIZE = 1000
      SUB_BATCH_SIZE = 100
         
      restrict_gitlab_migration gitlab_schema: :gitlab_main
         
      def up
        queue_batched_background_migration(
          MIGRATION,
          :routes,
          :id,
          job_interval: DELAY_INTERVAL,
          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
     finalize_after: "2023-11-15"
     finalized_by: # version of the migration that ensured this bbm
    
    note
    배치된 백그라운드 마이그레이션을 대기열에 넣을 때 실제 변경 사항을 가장하는 스키마를 제한해야 합니다. 이 경우 레코드를 업데이트하는 routes를 업데이트하므로 restrict_gitlab_migration gitlab_schema: :gitlab_main를 설정합니다. 그러나 CI 데이터 마이그레이션을 수행해야 하는 경우 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정합니다.

    배포 후, 애플리케이션: - 데이터를 이전과 동일하게 사용합니다. - 기존 및 새 데이터가 모두 마이그레이션되었음을 보장합니다.

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

    class FinalizeBackfillRouteNamespaceId < Gitlab::Database::Migration[2.1]
      MIGRATION = 'BackfillRouteNamespaceId'
      disable_ddl_transaction!
         
      restrict_gitlab_migration gitlab_schema: :gitlab_main
         
      def up
        ensure_batched_background_migration_is_finished(
          job_class_name: MIGRATION,
          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
     finalize_after: "2023-11-15"
     finalized_by: 20231115120912
    
    note
    배치된 백그라운드 마이그레이션이 완료되지 않으면 시스템은 배치된 백그라운드 마이그레이션을 인라인으로 실행합니다. 이 동작을 보고 싶지 않은 경우 finalize: false를 전달해야 합니다.

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

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

    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
        # 트리거 및 함수를 추가한 마이그레이션의 up 메서드에서 반전해야 합니다.
      end
    

배치된 마이그레이션이 완료된 후, 안전하게 routes.namespace_id의 데이터에 의존할 수 있습니다.