다중 데이터베이스 마이그레이션
GitLab v19.4요약
이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션을 위한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 다중 데이터베이스 설계는 Geo 데이터베이스를 제외하고 분해된 모든 데이터베이스가 같은 구조(예: 스키마)를 가지되 데이터는 데이터베이스마다 다르다고 전제합니다.
이 문서는 여러 데이터베이스를 사용하는 분해된 GitLab 애플리케이션을 위한 데이터베이스 마이그레이션을 올바르게 작성하는 방법을 설명합니다. 자세한 내용은 다중 데이터베이스를 참고합니다.
다중 데이터베이스 설계는 Geo 데이터베이스를 제외하고 분해된 모든 데이터베이스가 같은 구조(예: 스키마)를 가지되 데이터는 데이터베이스마다 다르다고 전제합니다. 즉, 일부 테이블은 모든 데이터베이스에 데이터를 담고 있지는 않습니다.
작업 유형#
사용하는 구문에 따라 마이그레이션을 다음과 같이 분류할 수 있습니다.
- 구조 변경(DDL - Data Definition Language)입니다(예:
ALTER TABLE). - 데이터 변경(DML - Data Manipulation Language)입니다(예:
UPDATE). - 마이그레이션 관점에서 DML로 취급되는 그 밖의 쿼리 수행입니다(예:
SELECT).
Gitlab::Database::Migration[2.0]을 사용하면 마이그레이션은 항상 한 가지 목적만 가져야 합니다.
애플리케이션은 db/structure.sql에 기술된 구조가 분해된 모든 데이터베이스에서 완전히
동일해야 하므로, 마이그레이션에 DDL과 DML 변경을 섞을 수 없습니다.
Data Definition Language (DDL)#
DDL 마이그레이션은 다음을 수행하는 모든 마이그레이션입니다.
- 테이블을 생성하거나 삭제합니다(예:
create_table). - 인덱스를 추가하거나 제거합니다(예:
add_index,add_concurrent_index). - 외래 키를 추가하거나 제거합니다(예:
add_foreign_key,add_concurrent_foreign_key). - 기본값이 있든 없든 칼럼을 추가하거나 제거합니다(예:
add_column). - 트리거 함수를 생성하거나 삭제합니다(예:
create_trigger_function). - 테이블에 트리거를 연결하거나 분리합니다(예:
track_record_deletions,untrack_record_deletions). - 비동기 인덱스를 준비하거나 준비를 취소합니다(예:
prepare_async_index,unprepare_async_index_by_name). - 테이블을 잘라냅니다(예:
truncate_tables!헬퍼 메서드 사용).
따라서 DDL 마이그레이션에서는 다음이 금지됩니다.
- SQL 문이나 ActiveRecord 모델을 통해 어떤 형태로든 데이터를 읽거나 수정하는 일입니다.
- 칼럼 값 업데이트입니다(예:
update_column_in_batches). - 백그라운드 마이그레이션 예약입니다(예:
queue_batched_background_migration). - 기능 플래그 상태 읽기입니다. 기능 플래그는
main:의features와feature_gates에 저장됩니다. - 애플리케이션 설정 읽기입니다. 설정은
main:에 저장됩니다.
GitLab 코드베이스의 마이그레이션 대부분은 DDL 유형이므로, 이것이 기본 동작 모드이며 마이그레이션 파일을 추가로 변경할 필요가 없습니다.
예시: 모든 데이터베이스에서 DDL 수행#
다음은 구조 변경(DDL)으로 취급되는 동시 인덱스를 추가하는 마이그레이션 예시이며, 구성된 모든 데이터베이스에서 실행됩니다.
class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'
def up
add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
end
end
예시: 단일 데이터베이스에 저장할 새 테이블 추가#
-
db/docs/의 데이터베이스 딕셔너리에 테이블을 추가합니다.table_name: ssh_signatures description: Description example introduced_by_url: Merge request link milestone: Milestone example feature_categories: - Feature category example classes: - Class example gitlab_schema: gitlab_main_org -
스키마 마이그레이션에서 테이블을 생성합니다.
class CreateSshSignatures < Gitlab::Database::Migration[2.1] def change create_table :ssh_signatures do |t| t.timestamps_with_timezone null: false t.bigint :project_id, null: false, index: true t.bigint :key_id, null: false, index: true t.integer :verification_status, default: 0, null: false, limit: 2 t.binary :commit_sha, null: false, index: { unique: true } end end end
Data Manipulation Language (DML)#
DML 마이그레이션은 다음을 수행하는 모든 마이그레이션입니다.
- SQL 문으로 데이터를 읽습니다(예:
SELECT * FROM projects WHERE id=1). - ActiveRecord 모델로 데이터를 읽습니다(예:
User < MigrationRecord). - ActiveRecord 모델로 데이터를 생성, 수정, 삭제합니다(예:
User.create!(...)). - SQL 문으로 데이터를 생성, 수정, 삭제합니다(예:
DELETE FROM projects WHERE id=1). - 칼럼을 배치 단위로 업데이트합니다(예:
update_column_in_batches(:projects, :archived, true)). - 백그라운드 마이그레이션을 예약합니다(예:
queue_batched_background_migration). - 애플리케이션 설정에 접근합니다(예:
main:데이터베이스에서 실행되는 경우ApplicationSetting.last). main:데이터베이스에서 실행되는 경우 기능 플래그를 읽고 수정합니다.
DML 마이그레이션에서는 다음이 금지됩니다.
- DDL 변경입니다. 분해된 모든 데이터베이스에서
structure.sql을 일관되게 유지한다는 규칙이 깨지기 때문입니다. - 다른 데이터베이스에서 데이터를 읽는 일입니다.
DML 마이그레이션 유형임을 나타내려면 마이그레이션 클래스에서 restrict_gitlab_migration gitlab_schema:
구문을 사용해야 합니다. 이 구문은 해당 마이그레이션을 DML로 표시하고 접근을 제한합니다.
예시: 지정된 gitlab_schema를 포함하는 데이터베이스 컨텍스트에서만 DML 수행#
다음은 projects의 archived 칼럼을 업데이트하는 마이그레이션 예시이며,
gitlab_main 스키마를 포함하는 데이터베이스에서만 실행됩니다.
class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
def up
update_column_in_batches(:projects, :archived, true) do |table, query|
query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
end
end
def down
# no-op
end
end
예시: ActiveRecord 클래스 사용#
데이터 조작을 위해 ActiveRecord 클래스를 사용하는 마이그레이션은
MigrationRecord 클래스를 사용해야 합니다. 이 클래스는 해당 마이그레이션 컨텍스트에서
올바른 연결을 제공하도록 보장합니다.
내부적으로는 MigrationRecord == ActiveRecord::Base 이며, db:migrate가 실행되면
ActiveRecord::Base.establish_connection :ci의 활성 연결이 전환됩니다.
ActiveRecord::Base 사용에 따른 혼동을 피하기 위해 MigrationRecord가 필요합니다.
이는 DML 마이그레이션이 다른 데이터베이스에서 데이터를 읽는 것이 금지된다는
뜻입니다. 예를 들어 ci: 컨텍스트에서 실행되는 마이그레이션이 main:의 기능 플래그를
읽는 경우가 그렇습니다. 다른 데이터베이스로 맺어진 연결이 없기 때문입니다.
class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
class Project < MigrationRecord
end
def up
Project.where(archived: false).each_batch of |batch|
batch.update_all(archived: true)
end
end
def down
end
end
gitlab_shared의 특수 목적#
gitlab_schema에 설명된 대로
gitlab_shared 테이블은 모든 데이터베이스에 데이터를 담을 수 있습니다. 따라서 이러한
마이그레이션은 구조 변경(DDL)이나 데이터 변경(DML)을 위해 모든 데이터베이스에서 실행되어야 합니다.
gitlab_shared에 접근하는 마이그레이션은 restrict_gitlab_migration gitlab_schema:를 사용할 필요가 없으므로,
제한이 없는 마이그레이션은 모든 데이터베이스에서 실행되며 각 데이터베이스의 데이터를 수정할 수 있습니다.
restrict_gitlab_migration gitlab_schema:를 지정하면 해당 DML 마이그레이션은
지정된 gitlab_schema를 포함하는 데이터베이스 컨텍스트에서만 실행됩니다.
예시: 모든 데이터베이스에서 DML gitlab_shared 마이그레이션 실행#
다음은 lib/gitlab/database/gitlab_schemas.yml에서 gitlab_shared로 표시된
loose_foreign_keys_deleted_records 테이블을 업데이트하는 마이그레이션 예시입니다.
이 마이그레이션은 구성된 모든 데이터베이스에서 실행됩니다.
class DeleteAllLooseForeignKeyRecords < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
execute("DELETE FROM loose_foreign_keys_deleted_records")
end
def down
# no-op
end
end
예시: 지정된 gitlab_schema를 포함하는 데이터베이스에서만 DML gitlab_shared 실행#
다음은 db/docs/loose_foreign_keys_deleted_records.yml에서 gitlab_shared로 표시된
loose_foreign_keys_deleted_records 테이블을 업데이트하는 마이그레이션 예시입니다.
이 마이그레이션은 gitlab_ci에 대한 제한을 설정하므로 gitlab_ci 스키마를 포함하는
데이터베이스 컨텍스트에서만 실행됩니다.
class DeleteCiBuildsLooseForeignKeyRecords < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_ci
def up
execute("DELETE FROM loose_foreign_keys_deleted_records WHERE fully_qualified_table_name='ci_builds'")
end
def down
# no-op
end
end
마이그레이션 건너뛰기 동작#
건너뛰는 마이그레이션은 DML 변경을 수행하는 마이그레이션뿐입니다. DDL 마이그레이션은 언제나 조건 없이 실행됩니다.
구현된 해결책은
config/database.yml의 어떤 추가 데이터베이스 구성이 같은 기본 데이터베이스를 공유하는지
나타내기 위해 database_tasks:를 사용합니다. database_tasks: false로 표시된
데이터베이스 구성은 해당 구성에 대해 db:migrate를 실행하는 대상에서
제외됩니다.
데이터베이스 구성이 데이터베이스를 공유하지 않으면(모두 database_tasks: true 인 경우),
각 마이그레이션은 모든 데이터베이스 구성에 대해 실행됩니다.
- DDL 마이그레이션은 모든 데이터베이스에 모든 구조 변경을 적용합니다.
- DML 마이그레이션은 지정된
gitlab_schema:를 포함하는 데이터베이스 컨텍스트에서만 실행됩니다. - DML 마이그레이션이 실행 대상이 아니면 건너뜁니다. 그래도
schema_migrations에는 실행된 것으로 표시됩니다.db:migrate를 실행하는 동안 건너뛴 마이그레이션은Current migration is skipped since it modifies 'gitlab_ci' which is outside of 'gitlab_main, gitlab_shared를 출력합니다.
database_tasks: false로 구성했을 때 마이그레이션이 누락되는 것을 막기 위해 전용
Rake 태스크 gitlab:db:validate_config를 사용합니다.
gitlab:db:validate_config는 각 데이터베이스 구성의 데이터베이스 식별자를 확인해 database_tasks:가
올바른지 검증합니다. 데이터베이스를 공유하는 구성은 database_tasks: false로 설정되어
있어야 합니다. gitlab:db:validate_config는 항상 db:migrate 앞에 실행됩니다.
유효성 검사#
유효성 검사는 요약하면 pg_query로 각 쿼리를 분석하고
db/docs/의 정보로 테이블을 분류합니다.
지정된 gitlab_schema가 해당 데이터베이스 연결이 관리하는 스키마 목록
(Gitlab::Database::gitlab_schemas_for_connection) 밖에 있으면 마이그레이션을 건너뜁니다.
Gitlab::Database::Migration[2.0]은 #migrate 메서드를 확장하는
Gitlab::Database::MigrationHelpers::RestrictGitlabSchema를 포함합니다. 마이그레이션이 실행되는 동안에는
restrict_gitlab_migration:으로 정의된 허용 스키마 목록을 받는 전용 쿼리 분석기
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas가 설치됩니다. 실행된 쿼리가
허용 스키마 밖에 있으면 예외가 발생합니다.
예외#
restrict_gitlab_migration을 잘못 사용하거나 빠뜨리면 마이그레이션 실행 중에 여러 예외가
발생해 마이그레이션이 완료되지 못할 수 있습니다.
예외 1: DDL 모드에서 실행되는 마이그레이션이 DML select를 수행하는 경우#
class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
# Missing:
# restrict_gitlab_migration gitlab_schema: :gitlab_main_org
def up
update_column_in_batches(:projects, :archived, true) do |table, query|
query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
end
end
def down
# no-op
end
end
Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode
Modifying of 'projects' (gitlab_main) with 'SELECT * FROM projects...
이 마이그레이션은 restrict_gitlab_migration을 사용하지 않습니다. 이는 DDL 모드로 실행되는
마이그레이션임을 뜻하지만, 실행되는 내용은 projects에서 데이터를 읽는 것으로 보입니다.
해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 추가하는 것입니다.
예외 2: DML 모드에서 실행되는 마이그레이션이 구조를 변경하는 경우#
class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
# restrict_gitlab_migration if defined indicates DML, it should be removed
restrict_gitlab_migration gitlab_schema: :gitlab_main_org
INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'
def up
add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
end
end
DDL queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode.
Modifying of 'merge_request_reviewers' with 'CREATE INDEX...
이 마이그레이션은 restrict_gitlab_migration을 사용합니다. 이는 DML 모드를 뜻하지만,
실행되는 내용은 구조 변경(DDL)을 수행하는 것으로 보입니다.
해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 제거하는 것입니다.
예외 3: DML 모드에서 실행되는 마이그레이션이 다른 스키마의 테이블 데이터에 접근하는 경우#
class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
# Since it modifies `projects` it should use `gitlab_main`
restrict_gitlab_migration gitlab_schema: :gitlab_ci
def up
update_column_in_batches(:projects, :archived, true) do |table, query|
query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
end
end
def down
# no-op
end
end
Select/DML queries (SELECT/UPDATE/DELETE) do access 'projects' (gitlab_main) " \
which is outside of list of allowed schemas: 'gitlab_ci'
이 마이그레이션은 대상을 gitlab_ci로 제한하지만, 실제로는 gitlab_main의 데이터를
수정하는 것으로 보입니다.
해결 방법은 restrict_gitlab_migration gitlab_schema: :gitlab_ci를 변경하는 것입니다.
예외 4: DDL과 DML 모드 혼용#
class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
# This migration is invalid regardless of specification
# as it cannot modify structure and data at the same time
restrict_gitlab_migration gitlab_schema: :gitlab_ci
def up
add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: 'index_on_merge_request_reviewers'
update_column_in_batches(:projects, :archived, true) do |table, query|
query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
end
end
def down
# no-op
end
end
DDL과 DML을 섞는 마이그레이션은 작업 순서에 따라 앞의 예외 중 하나를 발생시킵니다.
다중 데이터베이스 마이그레이션의 향후 변경 사항#
gitlab_schema:를 사용하는 restrict_gitlab_migration은 컨텍스트에 따라 마이그레이션을
선택적으로 실행하는 이 기능의 첫 번째 단계입니다. 구조 일관성은 별도 공지가 있을 때까지
그대로 유지될 가능성이 높으므로, DML 전용 마이그레이션에 실행 시점을 제한하는 추가
제약을 더할 수 있습니다.
확장 가능성 중 하나는 DML 마이그레이션 실행을 특정 환경으로만 제한하는 것입니다.
restrict_gitlab_migration gitlab_schema: :gitlab_main_org, gitlab_env: :gitlab_com
백그라운드 마이그레이션#
다음을 사용하는 경우입니다.
track_jobs가true로 설정된 백그라운드 마이그레이션- 배치 백그라운드 마이그레이션
이 경우 마이그레이션은 jobs 테이블에 써야 합니다. 백그라운드 마이그레이션이 사용하는
jobs 테이블은 모두 gitlab_shared로 표시되어 있습니다.
따라서 어떤 데이터베이스의 테이블을 마이그레이션할 때도 이러한 마이그레이션을 사용할 수 있습니다.
다만 배치를 큐에 넣을 때는 순회하는 테이블을 기준으로 restrict_gitlab_migration을
설정해야 합니다. 예를 들어 모든 projects를 업데이트한다면
restrict_gitlab_migration gitlab_schema: :gitlab_main_org를 설정합니다. 모든 ci_pipelines를
업데이트한다면
restrict_gitlab_migration gitlab_schema: :gitlab_ci를 설정합니다.
모든 DML 마이그레이션과 마찬가지로 restrict_gitlab_migration 이나 gitlab_shared 밖의
다른 데이터베이스를 쿼리할 수 없습니다. 다른 데이터베이스를 쿼리해야 한다면
마이그레이션을 분리합니다.
백그라운드 마이그레이션의 실제 마이그레이션 로직은 큐에 넣는 단계가 아니라 Sidekiq 워커에서 실행되므로, 일반 Sidekiq 워커와 마찬가지로 어떤 데이터베이스의 테이블에도 DML 쿼리를 수행할 수 있습니다.
주어진 테이블의 gitlab_schema 결정 방법#
데이터베이스 딕셔너리를 참고합니다.