배치 백그라운드 마이그레이션
GitLab v19.4요약
배치 백그라운드 마이그레이션(Batched background migration)은 마이그레이션이 가이드라인의 시간 제한을 초과할 때마다 데이터 마이그레이션을 수행하는 데 사용해야 합니다. 배치 백그라운드 마이그레이션은 기존 백그라운드 마이그레이션 프레임워크를 대체했습니다.
배치 백그라운드 마이그레이션(Batched background migration)은 마이그레이션이 가이드라인의 시간 제한을 초과할 때마다 데이터 마이그레이션을 수행하는 데 사용해야 합니다. 예를 들어 배치 백그라운드 마이그레이션을 사용하면 단일 JSON 칼럼에 저장된 데이터를 별도의 테이블로 옮길 수 있습니다.
배치 백그라운드 마이그레이션은 기존 백그라운드 마이그레이션 프레임워크를 대체했습니다. 해당 프레임워크와 관련된 변경 사항은 이전 문서를 확인합니다.
배치 백그라운드 마이그레이션 프레임워크는 ChatOps를 지원합니다. ChatOps를 사용하면 GitLab 엔지니어가 시스템에 존재하는 배치 백그라운드 마이그레이션과 상호작용할 수 있습니다.
배치 백그라운드 마이그레이션 사용 시점#
행이 매우 많은 테이블의 데이터를 마이그레이션할 때, 일반 Rails 마이그레이션으로 수행하면 그 과정이 가이드라인의 시간 제한을 초과하는 경우에 배치 백그라운드 마이그레이션을 사용합니다.
- 트래픽이 많은 테이블의 데이터를 마이그레이션할 때는 배치 백그라운드 마이그레이션을 사용해야 합니다.
- 대규모 데이터셋의 모든 항목에 대해 단일 행 쿼리를 다수 실행할 때도 배치 백그라운드 마이그레이션을 사용할 수 있습니다. 일반적으로 단일 레코드 패턴에서는 실행 시간이 데이터셋의 크기에 크게 좌우됩니다. 데이터셋을 적절히 나누어 백그라운드 마이그레이션에 넣습니다.
- 스키마 마이그레이션을 수행하는 데 배치 백그라운드 마이그레이션을 사용하지 않습니다.
백그라운드 마이그레이션은 다음과 같은 경우에 도움이 됩니다.
- 하나의 테이블에 있는 이벤트를 여러 개의 별도 테이블로 마이그레이션하는 경우
- 다른 칼럼에 저장된 JSON을 기반으로 한 칼럼을 채우는 경우
- 외부 서비스(예: API)의 출력에 의존하는 데이터를 마이그레이션하는 경우
참고 사항#
- 배치 백그라운드 마이그레이션이 중요한 업그레이드의 일부라면 릴리스 포스트에 공지해야 합니다. 마이그레이션이 이 범주에 해당하는지 확실하지 않으면 프로젝트 매니저와 논의합니다.
- 중요한 마이그레이션에는 업그레이드 노트를 추가해야 합니다. 이는 GitLab Self-Managed 및 Dedicated 고객이 업그레이드를 계획하는 데 도움이 되며, 가이드라인을 따릅니다.
- 필요한 파일이 기본으로 생성되도록 생성기를 사용해 배치 백그라운드 마이그레이션을 만드는 것이 좋습니다.
배치 백그라운드 마이그레이션 작동 방식#
배치 백그라운드 마이그레이션(BBM)은 perform 메서드를 정의하는
Gitlab::BackgroundMigration::BatchedMigrationJob의 하위 클래스입니다.
첫 단계에서 일반 마이그레이션이 BBM 클래스와 필수 인수를 담은
batched_background_migrations 레코드를 생성합니다. 기본적으로
batched_background_migrations는 active 상태이며,
이 레코드를 Sidekiq 워커가 가져가 실제 일괄 마이그레이션을 실행합니다.
모든 마이그레이션 클래스는 Gitlab::BackgroundMigration 네임스페이스에 정의해야 합니다. 파일은
lib/gitlab/background_migration/ 디렉터리에 둡니다.
실행 메커니즘#
배치 백그라운드 마이그레이션은 큐에 추가된 순서대로 큐에서 선택됩니다. 여러 마이그레이션을 가져와 병렬로 실행하며, 이는 마이그레이션이 active 상태이고 같은 데이터베이스 테이블을 대상으로 하지 않는 경우에 한합니다. 병렬로 처리하는 마이그레이션의 기본 개수는 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 <= MAX_ATTEMPTS
end note
Running --> 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 --> [*]
@endumlMAX_ATTEMPTS는Gitlab::Database::BackgroundMigration클래스에 정의되어 있습니다.can_split?는Gitlab::Database::BatchedJob클래스에 정의되어 있습니다.
실패한 배치 백그라운드 마이그레이션#
다음 중 하나라도 해당하면 배치 백그라운드 마이그레이션 전체가 failed로 표시됩니다
(/chatops gitlab run batched_background_migrations status MIGRATION_ID가
마이그레이션을 failed로 표시합니다).
- 더 이상 처리할 job 이 없고 실패한 job 이 있는 경우
- 백그라운드 마이그레이션이 시작된 이후 job의 절반 넘게 실패한 경우
일괄 마이그레이션 조절#
일괄 마이그레이션은 업데이트 부하가 크고, 데이터베이스 성능이 저하된 상태에서 이러한 마이그레이션의 과부하로 인한 인시던트가 있었으므로, 향후 인시던트를 완화하기 위해 조절(throttling) 메커니즘이 존재합니다.
마이그레이션을 조절할 때는 다음 데이터베이스 지표를 확인합니다. 중지 신호를 받으면 마이그레이션은 정해진 시간(10분) 동안 일시 중지됩니다.
- 아카이브 대기 중인 WAL 큐가 임계값을 넘은 경우
- 마이그레이션이 작업하는 테이블에서 autovacuum 이 활성 상태인 경우(GitLab 18.0부터 기본으로 활성화)
- Patroni apdex SLI가 SLO 아래로 떨어진 경우
- WAL 속도가 임계값을 넘은 경우
테이블의 autovacuum 지표 비활성화 및 활성화#
GitLab 18.0부터 이 상태 지표는 기본으로 활성화되어 있습니다. 비활성화하려면 rails 콘솔에서 다음 명령을 실행합니다.
Feature.disable(:batched_migrations_health_status_autovacuum)
다시 활성화하려면 rails 콘솔에서 다음 명령을 실행합니다.
Feature.enable(:batched_migrations_health_status_autovacuum)
격리#
배치 백그라운드 마이그레이션은 격리되어야 하며 애플리케이션 코드를 사용할 수 없습니다(예를 들어
ApplicationRecord 클래스를 제외하고 app/models에 정의된 모델).
이러한 마이그레이션은 실행하는 데 오래 걸릴 수 있으므로,
마이그레이션이 아직 실행되는 동안 새 버전이 배포될 수 있습니다.
마이그레이션된 데이터에 의존하기#
일반 마이그레이션이나 post 마이그레이션과 달리, 다음 릴리스를 기다리는 것만으로는 데이터가 완전히 마이그레이션되었음을 보장할 수 없습니다.
즉, 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.rbspec/migrations/20230214231008_queue_my_batched_migration_spec.rblib/gitlab/background_migration/my_batched_migration.rbspec/lib/gitlab/background_migration/my_batched_migration_spec.rb
커서 기반 순회 사용(기본)#
커서 기반 순회는 이제 배치 백그라운드 마이그레이션의 기본이자 권장 전략입니다. 기존의 기본 키 기반 방식과 비교해 복합 기본 키를 지원하며 유지 관리가 더 간단합니다.
queue_batched_background_migration 헬퍼는 마이그레이션 job 이 커서 전략을 사용하는지 자동으로 감지하고 그에 맞게 마이그레이션을 구성합니다.
커서 전략 사용 방법#
cursor DSL을 사용해 마이그레이션 job 클래스에 커서 칼럼을 정의합니다.
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_cursor및max_cursor값 계산 - 커서 기반 순회를 사용하도록 마이그레이션 구성
기존 기본 키 기반 순회#
기존 기본 키 기반 순회 전략을 사용해야 하는 경우(새 마이그레이션에는 권장하지 않습니다),
마이그레이션 클래스에서 cursor 정의를 생략하면 됩니다. 그러면 마이그레이션은
기존 순회 방식으로 되돌아갑니다.
배치 백그라운드 마이그레이션 큐 추가#
배치 백그라운드 마이그레이션의 큐 추가는 배포 후(post-deployment)
마이그레이션에서 수행해야 합니다. 마이그레이션이 일괄적으로 실행되도록 큐에 추가하는 다음
queue_batched_background_migration 예시를 사용합니다. 클래스 이름과 인수는 사용자의 마이그레이션에서
가져온 값으로 바꿉니다.
queue_batched_background_migration(
JOB_CLASS_NAME,
TABLE_NAME,
JOB_ARGUMENTS
)
이 헬퍼는 제공한 job 인수의 개수가 JOB_CLASS_NAME에 정의된
job 인수의 개수와 일치하지 않으면 오류를 발생시킵니다.
새로 생성된 데이터가 마이그레이션되거나, 생성 시점에 기존 버전과 새 버전 모두에 저장되도록 합니다. 삭제는 연쇄 삭제(cascading delete)가 있는 외래 키를 정의해 처리할 수 있습니다.
배치 백그라운드 마이그레이션 완료 처리#
배치 백그라운드 마이그레이션의 완료 처리(finalize)는
ensure_batched_background_migration_is_finished를 호출해 수행하며, 마이그레이션이 마지막 필수 중단점(required stop)
또는 그 이전에 추가된 경우에 한합니다. 이렇게 하면 GitLab Self-Managed 인스턴스의
업그레이드 과정이 원활하게 진행됩니다.
안전하게 처리할 수 있는 시점이 되면 모든 배치 백그라운드 마이그레이션을 완료 처리하는 것이 중요합니다. 오래된 배치 백그라운드 마이그레이션을 방치하는 것은 테스트와 애플리케이션 동작에서 계속 관리해야 하는 기술 부채의 한 형태입니다.
배치 백그라운드 마이그레이션은 완료 처리된 이후에야 완료되었다고 의존할 수 있습니다.
다음 조건을 모두 충족한 뒤에 배치 백그라운드 마이그레이션을 완료 처리할 것을 권장합니다.
- 배치 백그라운드 마이그레이션이 GitLab.com에서 완료되었습니다.
- 배치 백그라운드 마이그레이션이 마지막 필수 중단점 또는 그 이전에 추가되었습니다. 예를 들어 17.8 이 필수 중단점이고 마이그레이션이 17.7에 추가되었다면 완료 처리 마이그레이션은 17.9에 추가할 수 있습니다.
ensure_batched_background_migration_is_finished 호출은 마이그레이션을 큐에 추가할 때 사용한 것과
정확히 일치해야 합니다. 다음 항목에 특히 주의합니다.
- job 인수: 정확히 일치해야 하며, 그렇지 않으면 큐에 추가된 마이그레이션을 찾지 못합니다.
gitlab_schema: 정확히 일치해야 하며, 그렇지 않으면 큐에 추가된 마이그레이션을 찾지 못합니다. 그 사이에 테이블의gitlab_schema가gitlab_main에서gitlab_main_org로 바뀌었더라도, 일괄 처리 백그라운드 마이그레이션을 큐에 추가할 때gitlab_main을 사용했다면gitlab_main으로 완료 처리해야 합니다.
배치 백그라운드 마이그레이션을 완료 처리할 때는 해당하는
db/docs/batched_background_migrations 파일의
finalized_by도 갱신해야 합니다. 값은 완료 처리를 위해 추가한
마이그레이션의 타임스탬프/버전이어야 합니다.
실제 마이그레이션 코드가 어떠해야 하는지는 아래 예시에서 자세히 확인합니다.
마이그레이션이 큐에 추가된 이후 필수 중단점 하나를 지나기 전에 완료 처리하면 조기 완료 처리
오류가 발생합니다. 필수 중단점 하나를 지나기 전에 마이그레이션을 완료 처리해야 한다면
skip_early_finalization_validation: true 옵션을 사용해 이 검사를 건너뜁니다.
배치 백그라운드 마이그레이션 코드 삭제#
배치 백그라운드 마이그레이션이 완료되어 완료 처리되었고
다시 큐에 추가되지 않았다면,
완료 처리 이후 다음 필수 중단점이 지나고 나서 lib/gitlab/background_migration/의 마이그레이션 코드와 관련 테스트를 삭제할 수 있습니다.
예시 시나리오는 다음과 같습니다.
- 17.3과 17.5가 필수 중단점입니다.
- 17.1에서 배치 백그라운드 마이그레이션을 큐에 추가합니다.
- 17.4에서 GitLab.com에서 완료된 경우 마이그레이션을 완료 처리할 수 있습니다.
- 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으로 만듭니다. 그렇지 않으면 여러 패치 릴리스를 한 번에 업그레이드하는 시스템에서 배치 백그라운드 마이그레이션이 생성되고, 삭제되고, 다시 생성됩니다. - 배치 백그라운드 마이그레이션을 다시 실행하는 새 배포 후(post-deployment) 마이그레이션을 추가합니다.
- 새 배포 후 마이그레이션에서
#up메서드 시작 부분에delete_batched_background_migration메서드를 사용해 기존 배치 백그라운드 마이그레이션을 삭제하여 기존 실행이 정리되도록 합니다. - 원래 마이그레이션의
db/docs/batched_background_migrations/*.yml파일을 갱신해 다시 큐에 추가한 내용을 포함합니다. - 원래 마이그레이션이 이미 완료 처리되었다면 딕셔너리 파일의
finalized_by값을 비우고(키는 유지) 완료 처리 마이그레이션을 no-op으로 만듭니다.
예시#
원래 마이그레이션:
# 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
일괄 처리 마이그레이션 딕셔너리:
milestone과 queued_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: # leave empty until the requeued migration is finalized
배치 백그라운드 마이그레이션 중지 및 제거#
실행 중 상태의 배치 백그라운드 마이그레이션은 여러 이유로 중지하고 제거할 수 있습니다.
- 제품 사용 사례가 바뀌어 마이그레이션이 더 이상 관련이 없거나 필요하지 않은 경우
- 마이그레이션을 다른 로직의 다른 마이그레이션으로 대체해야 하는 경우
진행 중인 배치 백그라운드 마이그레이션을 중지하고 제거하려면 다음을 수행해야 합니다.
- 릴리스 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에서 기존 일괄 처리 마이그레이션을 삭제하는 배포 후(post-deployment) 마이그레이션을 추가합니다.
#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_from은 name을, copy_to는 name_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를 사용하면 쿼리가 주어진 조건과 일치하는 행만 고려해야 하므로 성능에 영향을 줄 수 있습니다.
범위 조건에 인덱스가 있고 배치 쿼리가 어떤 행도 걸러내지 않는 경우에만 사용해야 합니다.
적절한 인덱스가 있다는 강력한 지표는 쿼리 플랜이 추가 필터 없이 index-only scan을 수행한다는 것입니다.
신중을 기하기 위해 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의 사용도 허용되지 않으며,
가급적 모델 커넥션을 사용하도록 교체해야 합니다.
# 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 칼럼을 배치 칼럼으로 사용하는
배치 백그라운드 마이그레이션을 보여줍니다.
데이터베이스 post 마이그레이션:
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로 정의한 추가 필터는 LooseIndexScanBatchingStrategy와 distinct_each_batch에서 무시됩니다.
파티션된 테이블#
파티션된 테이블을 다룰 때는 마이그레이션을 병렬화해 성능을 높일 수 있습니다. 여러 마이그레이션을 동시에 실행할 수 있으며(GitLab.com에서는 최대 4개), 서로 다른 파티션 또는 파티션 범위를 병렬로 처리할 수 있습니다.
이 섹션에서 설명하는 패턴은 지금까지 GitLab.com 에서만, 그것도 특정 유형의 파티션된 테이블(수동으로 관리하는 CI sliding list 파티션)에만 사용되었습니다. 다음 이유로 자체 관리형 인스턴스에는 권장하지 않습니다.
- 큐에 추가되는 마이그레이션의 집합이 데이터(파티션의 수와 정체)에 따라 달라지므로 자체 관리형 인스턴스마다 서로 다른 마이그레이션 집합이 나타납니다. 이는 이미 백그라운드 마이그레이션 때문에 불편을 겪는 자체 관리형 관리자에게 혼란을 줄 수 있습니다.
- 뷰 기반 병렬화는 프로덕션 데이터를 기준으로 ID 범위를 미리 계산해야 하는데, 이를 자체 관리형 환경에 일반적으로 적용하기는 현실적이지 않습니다.
이러한 패턴을 사용하기 전에 Database 팀과 상의하고 사용 사례에서 트레이드오프를 감수할 수 있는지 확인합니다.
패턴 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를 사용해 마이그레이션에 걸리는 시간을 대략적으로 추정할 수 있습니다.
여기서 interval과 max batch size는 job에 정의된 옵션을 가리키고, total tuple count는 마이그레이션할 레코드 수입니다.
추정치는 마이그레이션 최적화 메커니즘의 영향을 받을 수 있습니다.
배치 백그라운드 마이그레이션 정리#
남아 있는 백그라운드 마이그레이션의 정리는 메이저 또는 마이너 릴리스에서 수행해야 합니다. 패치 릴리스에서는 수행해서는 안 됩니다.
백그라운드 마이그레이션은 오래 걸릴 수 있으므로 큐에 추가한 직후에는 곧바로 정리할 수 없습니다. 예를 들어 마이그레이션 과정에서 사용하는 칼럼은 job 이 실패하므로 삭제할 수 없습니다. 이후 릴리스에 남은 job을 끝낸 뒤 정리하는 별도의 배포 후(post-deployment) 마이그레이션을 추가해야 합니다. (예: 칼럼 제거)
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 이 실패하지 않도록 데이터베이스 헬퍼에 몽키 패치를 적용해야 합니다.
- 스키마 이름을
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
- 스키마 이름을
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}'. " \
데이터베이스 마이그레이션 파이프라인 시작#
변경 사항으로 Draft 머지 리퀘스트를 만들고 수동 db:gitlabcom-database-testing job을 실행합니다.
의존성 설정#
경우에 따라 마이그레이션이 이전에 큐에 추가된 BBM의 완료에 의존합니다. BBM 이 아직 실행 중이면 의존하는 마이그레이션은 실패합니다. 예를 들어 큰 테이블에 고유 인덱스를 도입하려면 중복 레코드를 처리하기 위해 이전에 큐에 추가된 BBM에 의존할 수 있습니다.
마이그레이션을 작성할 때 의존성이 더 잘 드러나도록 다음 프로세스가 구성되어 있습니다.
- BBM을 큐에 추가한 마이그레이션의 버전은
batched_background_migrations테이블과 BBM 딕셔너리 파일에 저장됩니다. - 각 마이그레이션 파일에
DEPENDENT_BATCHED_BACKGROUND_MIGRATIONS상수가 추가됩니다(기본적으로 주석 처리). 의존성을 설정하려면 의존하는 BBM의queued_migration_version을 추가합니다. 그렇지 않으면 주석 처리된 줄을 제거합니다. Migration::UnfinishedDependenciescop은 의존하는 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을 참고합니다. 이 MR은 CI 빌드 메타데이터 마이그레이션을 문서화합니다.
업그레이드 노트 추가 시점#
마이그레이션에 다음 조건 중 하나라도 해당하면 업그레이드 노트를 추가합니다.
- 마이그레이션이 완료되는 데 상당한 시간이 걸릴 수 있는 큰 테이블을 대상으로 합니다.
- 마이그레이션이 고객이 마이그레이션 범위를 제어할 수 있는 구성 옵션을 제공합니다.
- 마이그레이션이 다른 마이그레이션이나 기능에 의존합니다.
업그레이드 노트에 포함할 내용#
업그레이드 노트에는 고객이 마이그레이션을 이해하고 준비하는 데 도움이 되도록 다음 정보를 포함해야 합니다.
- 고객이 영향을 이해할 수 있도록 마이그레이션의 목적과 이점을 설명합니다.
- 마이그레이션이 고객의 데이터에 무엇을 하는지, 어떤 테이블을 순회하는지 설명합니다.
- 일정과 완료 처리를 문서화합니다. 완료 처리 시점을 아직 알 수 없으면 이슈에 링크합니다. 시점을 알게 되면(미래 날짜이더라도) 기존 업그레이드 노트를 완료 처리가 포함된 실제 릴리스로 갱신합니다.
- 업그레이드 전 준비 단계를 설명합니다. 해당하는 경우 적용해 둘 모범 사례와 설정을 권장합니다.
- 마이그레이션 소요 시간을 추정하는 도구(SQL 쿼리 또는 Rails 콘솔 명령)를 제공합니다.
- 가능한 경우 마이그레이션 범위를 줄이는 제어 방법을 문서화합니다. 어떤 데이터가 영향을 받거나 받지 않는지 명확히 알 수 있도록 최종 사용자 관점에서 제어 방법을 설명합니다.
관리#
BBM 관리는 chatops 통합을 통해 이루어지며, GitLab 팀 구성원만 사용할 수 있습니다.
배치 백그라운드 마이그레이션 목록 조회#
시스템의 배치 백그라운드 마이그레이션 목록을 조회하려면 다음 명령을 실행합니다.
/chatops gitlab run batched_background_migrations list
이 명령은 다음 옵션을 지원합니다.
- 데이터베이스 선택:
--database DATABASE_NAME: 지정한 데이터베이스에 연결합니다.main: 메인 데이터베이스를 사용합니다(기본값).ci: CI 데이터베이스를 사용합니다.sec: Security 데이터베이스를 사용합니다.
- 환경 선택:
--dev:dev환경을 사용합니다.--staging:staging환경을 사용합니다.--staging_ref:staging_ref환경을 사용합니다.--production:production환경을 사용합니다(기본값).
- job 클래스로 필터링
--job-class-name JOB_CLASS_NAME: 지정한 job 클래스의 job만 나열합니다.- 이는 백그라운드 마이그레이션의 YAML 정의에 있는
migration_job_name입니다.
출력 예시:

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환경을 사용합니다(기본값).
출력 예시:

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환경을 사용합니다(기본값).
출력 예시:

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환경을 사용합니다(기본값).
출력 예시:

active 상태의 배치 백그라운드 마이그레이션만 재개할 수 있습니다.
백그라운드 마이그레이션 활성화 또는 비활성화#
극히 제한된 상황에서 GitLab 관리자는 기능 플래그를 비활성화할 수 있습니다.
execute_batched_migrations_on_schedule
이 플래그는 기본으로 활성화되어 있습니다. 데이터베이스 호스트 유지 관리처럼 특별한 상황에서 데이터베이스 작업을 제한해야 할 때 최후의 수단으로만 비활성화합니다.
그 영향을 완전히 이해하지 못했다면 이 플래그를 비활성화하지 않습니다.
execute_batched_migrations_on_schedule 기능 플래그를 비활성화하면
GitLab 업그레이드가 실패하고 데이터 손실이 발생할 수 있습니다.
EE 전용 기능을 위한 배치 백그라운드 마이그레이션#
EE 전용 기능을 위한 모든 백그라운드 마이그레이션 클래스는 GitLab FOSS에 있어야 합니다. 이를 위해 GitLab FOSS에는 빈 클래스를 만들고, GitLab EE에서는 Enterprise Edition 기능 구현 가이드라인에 설명된 대로 이를 확장합니다.
EE 전용 기능을 위한 백그라운드 마이그레이션 클래스가 job 인수를 사용한다면 GitLab FOSS 클래스에 인수를 정의해야 합니다. GitLab FOSS 컨텍스트에서 마이그레이션을 스케줄링할 때 job 인수 검증이 실패하지 않도록 정의가 필요합니다.
생성기를 사용하면 새 배치 백그라운드 마이그레이션을 생성할 때
--ee-only 플래그를 전달해 EE 전용 마이그레이션 뼈대를 생성할 수 있습니다.
디버그#
실패 오류 로그 확인#
실패는 두 가지 방법으로 확인할 수 있습니다.
-
GitLab 로그를 통해:
-
배치 백그라운드 마이그레이션을 실행한 뒤 job 이 하나라도 실패하면 Kibana에서 로그를 확인합니다. 프로덕션 Sidekiq 로그를 보고 다음으로 필터링합니다.
json.new_state: failedjson.job_class_name:json.job_arguments:
-
json.exception_class와json.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";
-
테스트#
다음에는 테스트를 작성해야 합니다.
- 배치 백그라운드 마이그레이션의 큐 추가 마이그레이션
- 배치 백그라운드 마이그레이션 자체
- 정리 마이그레이션
RSpec의 before와 after 훅은 데이터베이스를 다운 마이그레이션하고 다시
업 마이그레이션한다는 점을 기억합니다. 이러한 훅 때문에 다른 배치 백그라운드
마이그레이션이 호출될 수 있습니다. it 블록에 정의한 기대 동작이 RSpec 훅에서 호출되는 것과
충돌할 수 있으므로, 일반 테스트 더블 대신 have_received와 함께
spy 테스트 더블을 사용하는 것이 좋습니다.
자세한 내용은 이슈 #35351을
참고합니다.
모범 사례#
-
다루는 데이터의 양을 파악합니다.
-
배치 백그라운드 마이그레이션 job 이 멱등한지 확인합니다.
-
작성한 테스트가 거짓 양성이 아닌지 확인합니다.
-
마이그레이션하는 데이터가 중요해서 손실되어서는 안 된다면, 정리 마이그레이션도 완료하기 전에 데이터의 최종 상태를 확인해야 합니다.
-
데이터베이스 전문가와 수치를 논의합니다. 마이그레이션이 예상보다 데이터베이스에 더 큰 부하를 줄 수 있습니다. 스테이징에서 측정하거나 다른 사람에게 프로덕션에서 측정해 달라고 요청합니다.
-
배치 백그라운드 마이그레이션을 실행하는 데 필요한 시간을 파악합니다.
-
job 클래스 안에서 예외를 조용히 rescue 할 때 주의합니다. 실패 시나리오에서도 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 -
가능하면 각 모델을 개별적으로 갱신하는 대신 서브 배치 전체를 하나의 쿼리로 갱신합니다. 이때 항상 limit 가드를 포함하고 이를 materialized 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 테이블에는 다형성 관계에 사용하는 source_type 필드가 있습니다.
데이터베이스 재설계의 일환으로 다형성 관계를 제거하고 있습니다. 작업의 한 단계는
source_id 칼럼의 데이터를 새로운 단일 외래 키로 마이그레이션하는 것입니다.
나중에 기존 행을 삭제할 예정이므로 백그라운드 마이그레이션의 일부로
기존 행을 갱신할 필요는 없습니다.
-
먼저 생성기를 사용해 배치 백그라운드 마이그레이션 파일을 만듭니다.
bundle exec rails g batched_background_migration BackfillRouteNamespaceId --table_name=routes --column_name=id --feature_category=source_code_management -
마이그레이션 job(
BatchedMigrationJob의 하위 클래스)을 갱신해source_id값을namespace_id로 복사합니다.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[!note] 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[!note] 배치 백그라운드 마이그레이션을 큐에 추가할 때는 실제로 변경을 수행하는 데이터베이스로 스키마를 제한해야 합니다. 이 경우
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[!note] 배치 백그라운드 마이그레이션이 끝나지 않았으면 시스템은 배치 백그라운드 마이그레이션을 인라인으로 실행합니다. 이러한 동작을 원하지 않으면
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에 데이터가 채워져 있다는 점에
안전하게 의존할 수 있습니다.